一、JDK的SPI机制
SPI 的全名为 Service Provider Interface,java设计出SPI目的是为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。这样程序运行的时候,该机制就会为某个接口寻找服务的实现,有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。例如,JDBC驱动,可以加载MySQL、Oracle、或者SQL Server等,目前有不少框架用它来做服务的扩张发现。
通过一张图来看看,用SPI需要遵循哪些规范,因为SPI毕竟是JDK的一种标准:
SPI约定:
- 在工程的META-INF/services/目录下,以接口的全限定名作为文件名,文件内容为实现接口的服务类;
- 使用ServiceLoader动态加载META-INF/services下的实现类;
- 接口的实现类需含无参构造函数;(因为类默认包含无参构造函数,如果我们没有重载构造函数所以此处可忽略)
如果在META-INF/services下有接口实现类,存在多个(例如jar包下面也有相应),系统如何处理?Jdk会全部加载,java.util.ServiceLoader在加载资源文件时,已经考虑了这个问题。譬如Mysql的驱动包就包含两个驱动:
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
mysql中驱动包的具体实现:
java中的ServiceLoader就会根据该路径去加载装配模块。但是JDK的SPI实现有着以下缺点:
- 获取实现类的方式只能通过Iterator遍历,不能通过具体参数来获取。
- 接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。
二、DUBBO-SP机制
dubbo spi的目的也是一样:可以获取类的实例对象,和java的SPI机制非常相似,但是又增加了如下功能:
- 可以方便的获取某一个想要的扩展实现,java的SPI机制就没有提供这样的功能
- 增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。
约定:
在扩展类的jar包内,放置扩展点配置文件:META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。 (注意:这里的配置文件是放在你自己的jar包内,不是dubbo本身的jar包内,Dubbo会全ClassPath扫描所有jar包内同名的这个文件,然后进行合并)
扩展Dubbo的协议示例:
在协议的实现jar包内放置文本文件:META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol,内容为:
xxx=com.alibaba.xxx.XxxProtocol
实现内容为:
package com.alibaba.xxx;
import com.alibaba.dubbo.rpc.Protocol;
public class XxxProtocol implemenets Protocol {
// ...
}
1.dubbo源码分析
dubbo的扩展点框架主要位于这个包下:com.alibaba.dubbo.common.extension
大概结构如下:
com.alibaba.dubbo.common.extension
|--factory
| |--AdaptiveExtensionFactory #稍后解释
| |--SpiExtensionFactory #稍后解释
|
|--support
| |--ActivateComparator
|
|--Activate #自动激活加载扩展的注解
|--Adaptive #自适应扩展点的注解
|--ExtensionFactory #扩展点对象生成工厂接口
|--ExtensionLoader #扩展点加载器,扩展点的查找,校验,加载等核心逻辑的实现类
|--SPI #扩展点注解
其中最核心的类就是ExtensionLoader
,几乎所有特性都在这个类中实现,先来看下他的结构:
ExtensionLoader
没有提供public的构造方法,但是提供了一个public static的getExtensionLoader
,这个方法就是获取ExtensionLoader
实例的工厂方法。其public成员方法中有三个比较重要的方法:
- getActivateExtension :根据条件获取当前扩展可自动激活的实现
- getExtension : 根据名称获取当前扩展的指定实现
- getAdaptiveExtension : 获取当前扩展的自适应实现
这三个方法将会是我们重点关注的方法;每一个ExtensionLoader
实例仅负责加载特定SPI扩展的实现。因此想要获取某个扩展的实现,首先要获取到该扩展对应的ExtensionLoader
实例,下面我们就来看一下获取ExtensionLoader
实例的工厂方法getExtensionLoader
:
public static ExtensionLoader getExtensionLoader(Class type) {
if (type == null)
throw new IllegalArgumentException("Extension type == null");
if(!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
if(!withExtensionAnnotation(type)) { // 只接受使用@SPI注解注释的接口类型
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
// 先从静态缓存中获取对应的ExtensionLoader实例
ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
if (loader == null) {
// 为Extension类型创建ExtensionLoader实例,并放入静态缓存
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
}
return loader;
}
该方法需要一个Class类型的参数,该参数表示希望加载的扩展点类型,该参数必须是接口,且该接口必须被@SPI注解注释,否则拒绝处理。检查通过之后首先会检查ExtensionLoader
缓存中是否已经存在该扩展对应的ExtensionLoader
,如果有则直接返回,否则创建一个新的ExtensionLoader
负责加载该扩展实现,同时将其缓存起来。可以看到对于每一个扩展,dubbo中只会有一个对应的ExtensionLoader
实例。
接下来看下ExtensionLoader的私有构造函数:
private ExtensionLoader(Class> type) {
this.type = type;
// 如果扩展类型是ExtensionFactory,那么则设置为null
// 这里通过getAdaptiveExtension方法获取一个运行时自适应的扩展类型
// (每个Extension只能有一个@Adaptive类型的实现,如果没有dubbo会动态生成一个类)
objectFactory = (type == ExtensionFactory.class ?
null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
这里保存了对应的扩展类型,并且设置了一个额外的objectFactory属性,是一个ExtensionFactory类型,ExtensionFactory主要用于加载扩展的实现:
@SPI
public interface ExtensionFactory {
/**
* Get extension.
*
* @param type object type.
* @param name object name.
* @return object instance.
*/
T getExtension(Class type, String name);
}
同时ExtensionFactory
也被@SPI注解注释,说明他也是一个扩展点,从前面com.alibaba.dubbo.common.extension
包的结构图中可以看到,dubbo内部提供了两个实现类:SpiExtensionFactory
和 AdaptiveExtensionFactory
,实际上还有一个SpringExtensionFactory
,不同的实现可以已不同的方式来完成扩展点实现的加载,这块稍后再来学习。从ExtensionLoader
的构造函数中可以看到,如果要加载的扩展点类型是ExtensionFactory
,object字段被设置为null。由于ExtensionLoader
的使用范围有限(基本上局限在ExtensionLoader中),因此对他做了特殊对待:在需要使用ExtensionFactory
的地方,都是通过对应的自适应实现来代替。
默认的ExtensionFactory
实现中,AdaptiveExtensionFactotry
被@Adaptive
注解注释,也就是它就是ExtensionFactory
对应的自适应扩展实现(每个扩展点最多只能有一个自适应实现,如果所有实现中没有被@Adaptive
注释的,那么dubbo会动态生成一个自适应实现类),也就是说,所有对ExtensionFactory
调用的地方,实际上调用的都是AdpativeExtensionFactory
,那么我们看下他的实现代码:
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
private final List factories;
public AdaptiveExtensionFactory() {
ExtensionLoader loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List list = new ArrayList();
for (String name : loader.getSupportedExtensions()) { // 将所有ExtensionFactory实现保存起来
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
public T getExtension(Class type, String name) {
// 依次遍历各个ExtensionFactory实现的getExtension方法,一旦获取到Extension即返回
// 如果遍历完所有的ExtensionFactory实现均无法找到Extension,则返回null
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
}
扩展点加载机制(ExtensionLoader)
dubbo 大白话系列-扩展点机制
dubbo之SPI解析