Dubbo扩展点加载机制
Dubbo 为每一个功能点提供了一个SPI 扩展接口。而为了为了不加载不使用SPI 实例,Dubbo通过适配器模式和动态编译技术,加载指定的SPI 实现。
Dubbo 的扩展点机制是基于SDK 中的SPI 增强而来,解决了以下问题:
- DK标准的SPI 会一次性实例化扩展点的所有实现,如果有些扩展点实现初始化很耗时,但又没用上,那么加载就很浪费资源。比如上面所说的Mysql 和Oracle 数据库驱动,当引入这两个包时,即使我们只需要使用其中一个驱动,另一个驱动实现类也会初始化。
- 如果扩展点加载失败,是不会友好的向用户通知具体异常,异常提示信息可能并不正确。
- 增加了对扩展点 Ioc 和 Aop 的支持,一个扩展点可以直接使用setter() 方法注入其他扩展点,也可以对扩展点使用Wrapper 类进行功能增强。
- Dubbo的SPI实现,通过 适配器模式和动态编译来实现,Dubbo 加载 SPI 实现类,不再是通过 ServiceLoader,而是自定义了ExtensionLoader 类
动态编译和适配器
Dubbo 通过适配器模式 和 动态编译的方式解决了JDK 加载全部SPI 实现类的问题
适配器模式
Dubbo 为每个功能点提供另一个SPI 扩展接口,Dubbo框架在使用扩展点功能时是对接口进行依赖的,而一个扩展接口对应了一系列的扩展实现类,那么如何选择使用哪一个扩展接口作为实现类?这是由适配器模式来做的。Dubbo提供的适配器和传统的适配器有一定不通,因为他是动态编译生成的。
比如Dubbo提供的扩展接口RegistryFactory ,在我们获取该接口SPI实现类是是如下操作。
public class SpiDemo {
public static void main(String[] args) {
// 获取zk 的服务注册中心工厂
ExtensionLoader<RegistryFactory> extensionLoader = ExtensionLoader.getExtensionLoader(RegistryFactory.class);
// 这里返回的实际上是 RegistryFactory 适配器
RegistryFactory registryFactory = extensionLoader.getAdaptiveExtension();
// 在调用getRegistry 之前 RegistryFactory SPI 实现类都没有进行实例化
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://localhost:2181"));
}
}
但是此时返回的 RegistryFactory 并非是一个真正的SPI 实现类,而是一个动态编译生成的针对 RegistryFactory 接口的适配器。其代码如下:
import org.apache.dubbo.common.extension.ExtensionLoader;
public class RegistryFactory$Adaptive implements org.apache.dubbo.registry.RegistryFactory {
@Override
public org.apache.dubbo.registry.Registry getRegistry(org.apache.dubbo.common.URL arg0) {
if (arg0 == null) {
throw new IllegalArgumentException("url == null");
}
org.apache.dubbo.common.URL url = arg0;
// 获取协议内容,默认为 dubbo类型,否则根据协议加载指定的实现类
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null) {
throw new IllegalStateException("Fail to get extension(org.apache.dubbo.registry.RegistryFactory) name from url(" + url.toString() + ") use keys([protocol])");
}
// 根据 extName 去META-INF 目录下找到该接口的SPI文件,根据extName 为key,获取value为实现类的全路径类名。直到调用了该方法,SPI 实现类才真正实现了初始化
org.apache.dubbo.registry.RegistryFactory extension = (org.apache.dubbo.registry.RegistryFactory) ExtensionLoader.getExtensionLoader(org.apache.dubbo.registry.RegistryFactory.class).getExtension(extName);
// 调用真正实现类的方法
return extension.getRegistry(arg0);
}
}
这里需要注意生成的适配器获取扩展名 extName 多种情况,造成这种情况的原因是在 ExtensionLoader#createAdaptiveExtensionClassCode 方法中生成适配器代码时进行了条件判断:
- @Adaptive({“protocol”}) :这里 Adaptive 的value 属性为 protocol,则会通过 url.getProtocol() 来获取 extName,如上 RegistryFactory 的适配器
- @Adaptive({“xxx”}) : @Adaptive 的value 为xxx,这里的xxx不为空 且不为 protocol。此时会根据指定方法是否存在 org.apache.dubbo.rpc.Invocation 类型的入参,如果存在,则通过 url.getMethodParameter(methodName, “xxx”, “null”); 来获取,否则通过 url.getParameter(“xxx”); 来获取。如下定义一个DemoSpi 接口
- @Adaptive : 该场景下 @Adaptive 的value为空。此时也会根据指定方法是否存在 org.apache.dubbo.rpc.Invocation 类型的入参,如果存在,则通过 url.getMethodParameter(methodName, “xxx”, “null”); 来获取,否则通过 url.getParameter(“xxx”); 来获取。这里的 xxx 为 类名驼峰转换后的结果。
我们这里以 DemoSpi 接口为例,创建了各个场景下的适配器生成代码,如下:
@SPI
public interface DemoSpi {
@Adaptive({"protocol"})
void test1(Directory directory);
@Adaptive({"say"})
void test2(Directory directory);
@Adaptive({"say"})
void test3(Directory directory, Invocation invocation);
@Adaptive
void test4(Directory directory);
@Adaptive
void test5(Directory directory, Invocation invocation);
}
public class DemoSpi$Adaptive implements com.kingfish.main.stub.DemoSpi {
/**
* 场景1: @Adaptive({"protocol"})
* 通过 url.getProtocol(); 获取 extName
*
* @param arg0
*/
@Override
public void test1(org.apache.dubbo.rpc.cluster.Directory arg0) {
if (arg0 == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.cluster.Directory argument == null");
}
if (arg0.getUrl() == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.cluster.Directory argument getUrl() == null");
}
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = url.getProtocol();
if (extName == null) {
throw new IllegalStateException("Fail to get extension(com.kingfish.main.stub.DemoSpi) name from url(" + url.toString() + ") use keys([protocol])");
}
com.kingfish.main.stub.DemoSpi extension = (com.kingfish.main.stub.DemoSpi) ExtensionLoader.getExtensionLoader(com.kingfish.main.stub.DemoSpi.class).getExtension(extName);
extension.test1(arg0);
}
/**
* 场景2: @Adaptive({"say"}) & 方法入参没有 Invocation
* 通过 url.getParameter("say"); 获取 extName
*
* @param arg0
*/
@Override
public void test2(org.apache.dubbo.rpc.cluster.Directory arg0) {
if (arg0 == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.cluster.Directory argument == null");
}
if (arg0.getUrl() == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.cluster.Directory argument getUrl() == null");
}
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = url.getParameter("say");
if (extName == null) {
throw new IllegalStateException("Fail to get extension(com.kingfish.main.stub.DemoSpi) name from url(" + url.toString() + ") use keys([say])");
}
com.kingfish.main.stub.DemoSpi extension = (com.kingfish.main.stub.DemoSpi) ExtensionLoader.getExtensionLoader(com.kingfish.main.stub.DemoSpi.class).getExtension(extName);
extension.test2(arg0);
}
/**
* 场景3: @Adaptive({"say"}) & 方法入参有 Invocation
* 通过 url.getMethodParameter(methodName, "say", "null"); 获取 extName
* @param arg0
*/
@Override
public void test3(org.apache.dubbo.rpc.cluster.Directory arg0, org.apache.dubbo.rpc.Invocation arg1) {
if (arg0 == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.cluster.Directory argument == null");
}
if (arg0.getUrl() == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.cluster.Directory argument getUrl() == null");
}
org.apache.dubbo.common.URL url = arg0.getUrl();
if (arg1 == null) {
throw new IllegalArgumentException("invocation == null");
}
String methodName = arg1.getMethodName();
String extName = url.getMethodParameter(methodName, "say", "null");
if (extName == null) {
throw new IllegalStateException("Fail to get extension(com.kingfish.main.stub.DemoSpi) name from url(" + url.toString() + ") use keys([say])");
}
com.kingfish.main.stub.DemoSpi extension = (com.kingfish.main.stub.DemoSpi) ExtensionLoader.getExtensionLoader(com.kingfish.main.stub.DemoSpi.class).getExtension(extName);
extension.test3(arg0, arg1);
}
/**
* 场景4: @Adaptive & 方法入参没有 Invocation
* 通过 url.getParameter("demo.spi"); 获取 extName。其中 demo.spi 是 类名驼峰转换.后的值
* @param arg0
*/
@Override
public void test4(org.apache.dubbo.rpc.cluster.Directory arg0) {
if (arg0 == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.cluster.Directory argument == null");
}
if (arg0.getUrl() == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.cluster.Directory argument getUrl() == null");
}
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = url.getParameter("demo.spi");
if (extName == null) {
throw new IllegalStateException("Fail to get extension(com.kingfish.main.stub.DemoSpi) name from url(" + url.toString() + ") use keys([demo.spi])");
}
com.kingfish.main.stub.DemoSpi extension = (com.kingfish.main.stub.DemoSpi) ExtensionLoader.getExtensionLoader(com.kingfish.main.stub.DemoSpi.class).getExtension(extName);
extension.test4(arg0);
}
/**
* 场景5: @Adaptive & 方法入参有 Invocation
* 通过 url.getMethodParameter(methodName, "demo.spi", "null"); 获取 extName。其中 demo.spi 是 类名驼峰转换.后的值
* @param arg0
*/
@Override
public void test5(org.apache.dubbo.rpc.cluster.Directory arg0, org.apache.dubbo.rpc.Invocation arg1) {
if (arg0 == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.cluster.Directory argument == null");
}
if (arg0.getUrl() == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.cluster.Directory argument getUrl() == null");
}
org.apache.dubbo.common.URL url = arg0.getUrl();
if (arg1 == null) {
throw new IllegalArgumentException("invocation == null");
}
String methodName = arg1.getMethodName();
String extName = url.getMethodParameter(methodName, "demo.spi", "null");
if (extName == null) {
throw new IllegalStateException("Fail to get extension(com.kingfish.main.stub.DemoSpi) name from url(" + url.toString() + ") use keys([demo.spi])");
}
com.kingfish.main.stub.DemoSpi extension = (com.kingfish.main.stub.DemoSpi) ExtensionLoader.getExtensionLoader(com.kingfish.main.stub.DemoSpi.class).getExtension(extName);
extension.test5(arg0, arg1);
}
}
总结: 适配器类会根据传递的协议参数的不同,加载不同的SPI 实现类。
动态编译
在进行进一步分析之前,我们先来解释一下Dubbo的动态编译: 众所周知,Java 程序想要运行首先需要将Java源代码编译成字节码文件,然后JVM把字节码文件加载到内存创建Class 对象后,使用Class对象创建实例。 正常情况下,我们是将所有源文件编译为字节码文件,然后由JVM统一加载,而动态编译则是在JVM进程运行时把源文件编译为字节码文件,然后使用字节码文件创建实例。
在上面,我们讲到Dubbo会为每一个SPI 接口生成一个适配器,但这个适配器并不是写好的,而是动态编译生成的。 Dubbo提供了一个org.apache.dubbo.common.compiler.Compiler 的SPI,并提供了JavassistCompiler(默认使用)、JdkCompiler两种实现类
@SPI("javassist")
public interface Compiler {
// code 为源码, classLoader 为指定的类加载器
Class<?> compile(String code, ClassLoader classLoader);
}
通过 Compiler#compile 编译后,可以将源代码动态编译成Class 对象,之后便可以通过反射直接创建对象。
@Adaptive 注解
引用dubbo官方文档的一段话: Adaptive 可注解在类或方法上。当 Adaptive 注解在类上时,Dubbo 不会为该类生成代理类。注解在方法(接口方法)上时,Dubbo 则会为该方法生成代理逻辑。Adaptive 注解在类上的情况很少,在 Dubbo 中,仅有两个类被 Adaptive 注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory。此种情况,表示拓展的加载逻辑由人工编码完成。更多时候,Adaptive 是注解在接口方法上的,表示拓展的加载逻辑需由框架自动生成。
Dubbo SPI 原理
Dubbo SPI 的加载过程很简单,如下:
// 1. 获取 RegistryFactory 的扩展加载器,这里的ExtensionLoader类似于 JDK 标准 SPI 类中的 ServiceLoader
ExtensionLoader<RegistryFactory> extensionLoader = ExtensionLoader.getExtensionLoader(RegistryFactory.class);
// 2. 通过适配器 + 动态编译获取到 RegistryFactory
RegistryFactory adaptiveExtension = extensionLoader.getAdaptiveExtension();
下面我们来逐步分析
-
ExtensionLoader.getExtensionLoader(RegistryFactory.class); ExtensionLoader类似于 JDK 标准 SPI 类中的 ServiceLoader,下面的过程也很简单,为每个SPI 接口类创建一个自己的 ExtensionLoader。 ```java // 在 Dubbo 中,每个扩展接口对应自己的ExtensionLoader,key为扩展接口的Class 对象,value为对应的ExtensionLoader private static final ConcurrentMap<Class<?>, ExtensionLoader<?» EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?»();
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!"); } // 没有 SPI 注解修饰抛出异常 if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!"); } // 获取 type 对应的 ExtensionLoader对象,如果没有,则创建一个。 ExtensionLoader loader = (ExtensionLoader ) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader (type)); loader = (ExtensionLoader ) EXTENSION_LOADERS.get(type); } return loader; }
从上面的代码可以看出,第一个访问某个扩展接口时需要新建一个 ExtensionLoader 对象放到缓存中,后续从缓存中获取。
## extensionLoader.getAdaptiveExtension()
这一步直接获取到了SPI 接口的适配器实例。适配器实例是通过动态编译产生的。下面我们来详细看一看。
```java
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
// DCL 检测是否存在当前接口对应的适配器
if (instance == null) {
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
// 确定不存在,则开始创建
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
...
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
可以看到,在 createAdaptiveExtension 方法中分为三步:
- getAdaptiveExtensionClass() : 创建并返回当前接口对应适配器的Class 对象
- getAdaptiveExtensionClass().newInstance() : 根据第一步 Class对象,创建出适配器的实例
- injectExtension((T) getAdaptiveExtensionClass().newInstance()); : 进行扩展点的相互依赖注入 其中,由于第二步是直接反射创建实例,所以我们这里重点来看第一步和第三步
getAdaptiveExtensionClass()
getAdaptiveExtensionClass() 完成了 SPI 接口适配器的编译工作。其代码如下:
private Class<?> getAdaptiveExtensionClass() {
// 1. 获取了该扩展类接口的所有实现类的Class对象,并缓存到了cachedClasses z中
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 2. 创建了适配器 Class 并返回
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
下面我们分步详解:
- getExtensionClasses();
该方法的作用是获取SPI 接口对应的所有实现类(包括扩展类)的Class,并进行缓存
private Map<String, Class<?>> getExtensionClasses() { // DCL 确定没有缓存 Map<String, Class<?>> classes = cachedClasses.get(); if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { // 进行 SPI 实现类 扫描 classes = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes; } ... private Map<String, Class<?>> loadExtensionClasses() { // 获取 @SPI 注解上的默认扩展名,@SPI 是Dubbo 提供的注解,用于表示 SPI 实现类的扩展名 final SPI defaultAnnotation = type.getAnnotation(SPI.class); // 如果被 @SPI 注解修饰,则进行处理, if (defaultAnnotation != null) { String value = defaultAnnotation.value(); if ((value = value.trim()).length() > 0) { String[] names = NAME_SEPARATOR.split(value); if (names.length > 1) { throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names)); } // 默认实现类的名称放入cachedDefaultName,当没有指定使用哪个实现类时,使用该协议指定的实现类 if (names.length == 1) { cachedDefaultName = names[0]; } } } // 在指定目录的Jar 里面查找扩展点。这里指定的目录包括 META-INF/dubbo/internal/、META-INF/dubbo/、META-INF/services/。加载了 org.apache 和 com.alibaba 包下的指定SPI 接口(应该和 Dubbo 开始由 阿里开发后面 交由 apache 有关) Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName()); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); return extensionClasses; }
这里解释一下: 以 RegistryFactory为例, SPI 注解为 @SPI(“dubbo”),这里的 defaultAnnotation 则为 dubbo,随后通过 loadDirectory 方法到 META-INF/dubbo/internal/、META-INF/dubbo/、META-INF/services/ 目录下加载具体的扩展类,加载方式和Springboot自动装配类似,defaultAnnotation 为key,value为对应指定的加载类。这里就可以知道 Dubbo 增强SPI 加载SPI 文件的路径与JDK 不完全相同,包括 META-INF/dubbo/internal/、META-INF/dubbo/、META-INF/services/。
loadDirectory方法中完成了对 @Adaptive 注解的解析 和 SPI 的包装类缓存, loadDirectory 方法在数次跳转后会跳转到ExtensionLoader#loadClass方法中,其代码如下:
// 这里的name是在 SPI 文件中,key=value 中的 key
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
// 合法性校验,确定当前加载的clazz 是 type 的实现类,type是我们指定的SPI 接口,clazz 是加载的SPI文件的实现类
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
// 对 Adaptive 注解的处理,如果当前类被 @Adaptive 修饰,则
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if (!cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getClass().getName()
+ ", " + clazz.getClass().getName());
}
}
// 判断当前类是否需要进行包装,这里判断的条件就是是否存在 “以 SPI 接口为入参的构造函数”
// 这是Dubbo提供的一个扩展机制 自动包装,类似于AOP的形式,后面会详解。
else if (isWrapperClass(clazz)) {
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} else {
// 剩下的扩展点的扩展实现类的处理
clazz.getConstructor();
if (name == null || name.length() == 0) {
// 对 @Extension 注解的处理
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
// 下面是对 Activate 注解的处理,如果当前类被 Activate 修饰,则将其缓存到 cachedActivates 中,在后续 ExtensionLoader#getActivateExtension(URL, String, String) 方法加载 SPI 实例时,满足条件的类 会被激活。
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(names[0], activate);
} else {
// support com.alibaba.dubbo.common.extension.Activate
// 对 alibaba @Activate 注解的支持
com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
if (oldActivate != null) {
cachedActivates.put(names[0], oldActivate);
}
}
for (String n : names) {
if (!cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
Class<?> c = extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
createAdaptiveExtensionClass(); 这里的方法才是生成适配器的核心方法,步骤也很清楚,如下:
private Class<?> createAdaptiveExtensionClass() {
// 1. 动态生成 适配器源代码
String code = createAdaptiveExtensionClassCode();
// 2. 寻找当前类的类加载器,使用的是 ExtensionLoader的类加载器
ClassLoader classLoader = findClassLoader();
// 3. 通过 适配器模式获取到编译类
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
// 4. 进行源码编译,生成 Class 文件
return compiler.compile(code, classLoader);
}
下面我们分步讲解: createAdaptiveExtensionClassCode(); : 生成 SPI 适配器的源码,这里生成源码的逻辑没有想象的那么负责,直接使用的字符串拼接。关于该方法,需要注意的是:
1. 至少有一个方法被@Adaptive修饰,因为只有被@Adapter 注解修饰的方法才会被适配器增强
2. 被@Adaptive修饰得方法得参数 必须满足参数中有一个是URL类型,或者有至少一个参数有一个“公共且非静态的返回URL的无参get方法”,否则无法给适配器方法注入参数类型
3. @Adaptive注解中的value,value可以是一个数组,如果为空的话,则使用以下规则从接口的类名生成一个名称:将大写字符的类名分成几部分,并用点号“.”分开,例如: org.apache.dubbo.xxx.YyyInvokerWrapper ,其默认名称为String[] {"yyy.invoker.wrapper"} , 此名称将用于从URL搜索参数。
如 方法被@Adaptive("demo")修饰, 适配器会获取 URL 中属性名为 demo的值,作为协议名。
findClassLoader() : 寻找编译使用的类加载器。这里和JDK 中不一样,JDK中由于 ServiceLoader 是 Bootstrap ClassLoader 类加载的,而用户类需要使用 AppClassLoader,所以使用了当前线程上下文的类加载器。而这里由于都是Dubbo提供的类,所以需要做这个措施,直接获取ExtensionLoader的类加载器即可。 ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); 获取到 Compiler 的实现类。Compiler 也是 Dubbo提供的一个SPI 接口,并提供了JavassistCompiler(默认使用)、JdkCompiler两种实现类。 compiler.compile(code, classLoader) : 在获取到源码、类加载器、编译类之后,就可以直接编译,返回SPI适配器的Class对象,后续通过反射即可获取到适配器对象。
injectExtension
该方法也是 Dubbo SPI的一个扩展点的实现:扩展点之间的依赖注入。
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
// 遍历扩展点实现类的所有方法
for (Method method : instance.getClass().getMethods()) {
// 发现set方法 && 只有一个参数 && 是公共的
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
// 被DisableInject 注解修饰的属性不需要注释
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
// set 方法的参数是原始类型,不需要自动注入,包括String、Boolean、Number 等
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
// 获取需要set 的属性名称的那个
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
// 查看该属性类是否存在扩展实现,这里的pt 是set方法的入参类型; property 是set 后面的属性
// 如 public void setCluster(Cluster cluster); 方法,这里的pt为org.apache.dubbo.rpc.cluster.Cluster,property 为 cluster
Object object = objectFactory.getExtension(pt, property);
// 如果存在则反射调用 sett方法设值
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
至此,我们才创建出 SPI 适配器对象,当前还并没有创建出来 SPI 实现类对象。 我们先来总结一下通过 用户调用 ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension(); 来获取适配器 适配器的逻辑:
- 程序启动,创建当前SPI 接口的ExtensionLoader 对象(如果已有,则使用缓存)
- 在不存在适配器缓存的情况下,着手创建适配器对象。
- 创建适配器第一步 : 加载出所有SPI接口实现类的Class对象,并进行缓存。
- 创建适配器第二步 : 动态拼接SPI 适配器源码,随后进行动态编译,获取到 适配器的Class对象,并通过newInstance 进行反射获取适配器对象实例
- 创建适配器第三步 :对适配器对象进行 依赖注入(仅能通过setter方法注入)。 需要注意的是,此时 SPI 的实现类,尚未实例化,我们仅仅创建了SPI 适配器对象。 因此,下面我们来看看 SPI对象的实例化过程。
SPI 实现类的初始化
SPI 实现类的实例化过程是在第一次调用SPI 接口方法时,适配器会进行实例化。以下以RegistryFactory的适配器 RegistryFactory$Adaptive 为例:
import org.apache.dubbo.common.extension.ExtensionLoader;
public class RegistryFactory$Adaptive implements org.apache.dubbo.registry.RegistryFactory {
@Override
public org.apache.dubbo.registry.Registry getRegistry(org.apache.dubbo.common.URL arg0) {
if (arg0 == null) {
throw new IllegalArgumentException("url == null");
}
org.apache.dubbo.common.URL url = arg0;
// 获取协议内容,默认为 dubbo类型,否则根据协议加载指定的实现类
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null) {
throw new IllegalStateException("Fail to get extension(org.apache.dubbo.registry.RegistryFactory) name from url(" + url.toString() + ") use keys([protocol])");
}
// 根据 extName去META-INF 目录下找到该接口的SPI文件,根据extName 为key,获取value为实现类的全路径类名。直到调用了该方法,SPI 实现类才真正实现了初始化
org.apache.dubbo.registry.RegistryFactory extension = (org.apache.dubbo.registry.RegistryFactory) ExtensionLoader.getExtensionLoader(org.apache.dubbo.registry.RegistryFactory.class).getExtension(extName);
// 调用真正实现类的方法
return extension.getRegistry(arg0);
}
}
比如RegistryFactory,默认其Protocol 为 “dubbo”,这里获取寻找 org.apache.dubbo.registry.RegistryFactory 文件中 key为 dubbo 的value值作为实现类,默认会加载 org.apache.dubbo.registry.dubbo.DubboRegistryFactory 为SPI 实现类。
getExtension(extName);
ExtensionLoader#getExtension 根据 extName(协议类型) 加载了对应的SPI 实现类,如果实现类存在包装类,则会创建器包装类。
// org.apache.dubbo.common.extension.ExtensionLoader#getExtension
public T getExtension(String name) {
// SPI 实现类命名的合法性
if (name == null || name.length() == 0) {
throw new IllegalArgumentException("Extension name == null");
}
// 如果为true,则使用默认扩展,即使用 cachedDefaultName 名称的 SPI 实现类
if ("true".equals(name)) {
return getDefaultExtension();
}
// 从缓存中获取实例
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 不存在缓存实例,则进行创建
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
...
// 这里开始创建 SPI实现类的实例对象
private T createExtension(String name) {
// 根据name查找对应的扩展实现的Class对象
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
// 如果缓存中不存在实例,则使用Class创建实例。
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// IOC 注入,如果当前SPI 依赖其他SPI,则会通过setter进行注入,上面已有详解。
injectExtension(instance);
// Wrapper 对扩展实现进行功能增强,这里的 cachedWrapperClasses 是 2.1.1 中我们解析了 loadDirectory 方法,其中包括对包装类的缓存
// 如果 cachedWrapperClasses不为空,则说明存在该实现类的包装类,则对其包装类进行初始化。将SPI实现类作为构造入参注入
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
// 对包装类进行 IOC 注入,同时通过SPI 实现类构造包装类。
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}
需要注意的是,这里存在一个 Wrapper 操作,因为Dubbo 的SPI 在IOC之外还扩展了扩展点的自动包装功能,下面详解。
扩展点的自动包装
在Spring Aop 总,我们可以使用多个切面对指定类的方法进行增强,在Dubbo 中也提供了类似的功能,在DUbbo中可以指定多个 Wrapper 类对指定的扩展点的实现类的方法进行增强。
如下,创建一个 RegistryFactory 的 包装类:
public class RegistryFactoryWrapper implements RegistryFactory {
private RegistryFactory registryFactory;
// 构造函数必须,Dubbo通过有无构造函数来判定是否是包装类
public RegistryFactoryWrapper(RegistryFactory registryFactory) {
this.registryFactory = registryFactory;
}
@Override
public Registry getRegistry(URL url) {
// 调用过程
System.out.println("这里是 RegistryFactory 的包装类");
return registryFactory.getRegistry(url);
}
}
需要注意的是,对于SPI Wrapper 类来说,不存在协议类型匹配一说, SPI Wrapper仅根据SPI 接口类型生效 。即使将 org.apache.dubbo.registry.RegistryFactory 文件的内容 改成了aaa=com.kingfish.main.spi.RegistryFactoryWrapper 或者 com.kingfish.main.spi.RegistryFactoryWrapper,依然会使用该包装类进行包装。
所以整个Dubbo SPI 流程总结下来就是 :
- 程序启动,创建当前SPI 接口的ExtensionLoader 对象(如果已有,则使用缓存)
- 在不存在适配器缓存的情况下,着手创建适配器对象。
- 创建适配器第一步 : 加载出所有SPI接口实现类的Class对象,并进行缓存。同时缓存了所有SPI 接口包装类Class对象
- 创建适配器第二步 : 动态拼接SPI 适配器源码,随后进行动态编译,获取到 适配器的Class对象,并通过newInstance 进行反射获取适配器对象实例
- 创建适配器第三步 :对适配器对象进行 依赖注入(仅能通过setter方法注入)。
- 调用适配器的SPI 接口方法,在调用时 如果SPI 实现类没有初始化,则会通过ExtensionLoader#getExtension 进行初始化。
- 初始化过程中,会获取 包装类的缓存,如果存在包装类缓存,则会将SPI实现类作为一个包装器的一个构造入参,构造出包装器类并返回。也即是说在允许创建适配器和存在包装了的情况下,整个调用过程是 : SPI Adapter -> SPI wrapper -> SPI 实现类
加载多个扩展类
在getExtension(extName); 方法只会加载某一个扩展接口实现的Class对象,但有些情况下我们需要全部创建或者创建其中一部分。
比如 Dubbo Filter 链的实现,就可能需要一次性执行多个的Filter的实现方法,就需要加载出多个Filter 实现,而其实现是在ProtocolFilterWrapper 类中的buildInvokerChain() 方法在建立 Filter 责任链时,把属于某一个group的所有Filter都放到责任链里。其通过如下方式来获取属于某一个组的Filter扩展实现类的:
// getActivateExtension 方法,会搜索出与 key 和 group 匹配的 SPI 扩展类
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class)
.getActivateExtension(invoker.getUrl(), key, group);
比如,当服务端启动时只会加载 group 为 provider,同时协议类型指定为 url.getParameter(Constants.EXECUTES_KEY) 的 Filter
@Activate(group = Constants.PROVIDER, value = Constants.EXECUTES_KEY)
public class ExecuteLimitFilter implements Filter
当消费端启动时只会加载 group 为consumer,同时协议类型为 url.getParameter(Constants.ACTIVES_KEY) 的Filter的扩展实现类
@Activate(group = Constants.CONSUMER, value = Constants.ACTIVES_KEY)
public class ActiveLimitFilter implements Filter
在这里我们需要注意两点
- @Activate 注解的功能。
- ExtensionLoader#getActivateExtension(URL, String,String)方法的具体实现。
@Activate注解
@Activate称为自动激活扩展点注解,主要使用在有多个扩展点实现、需要同时根据不同条件被激活的场景中。即被 @Activate 注解修饰的类,在满足其过滤条件的情况下会自动被激活。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
// group 过滤条件
String[] group() default {};
// value过滤条件
String[] value() default {};
@Deprecated
String[] before() default {};
@Deprecated
String[] after() default {};
// 排序信息
int order() default 0;
}
如果当前类被 Activate 修饰,则将其缓存到 ExtensionLoader#cachedActivates中,等待合适的时机被激活。
ExtensionLoader#getActivateExtension(URL, String,String)
@Activate 的实现逻辑如下: Dubbo在加载 SPI 所有实现类的时候会将被 @Activate 注解修饰的SPI 实现类缓存到 cachedActivates 中。而在通过 ExtensionLoader#getActivateExtension(URL, String, String)加载 SPI 实现类的时候,会判断是否满足 @Activate 注解的条件,如果满足,则会将其一并返回(这里便将 Filter 返回)。
// org.apache.dubbo.common.extension.ExtensionLoader#getActivateExtension(org.apache.dubbo.common.URL, java.lang.String, java.lang.String)
public List<T> getActivateExtension(URL url, String key, String group) {
// 根据key 从 url 中获取 value
String value = url.getParameter(key);
return getActivateExtension(url, value == null || value.length() == 0 ? null : Constants.COMMA_SPLIT_PATTERN.split(value), group);
}
...
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> exts = new ArrayList<T>();
List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
// 获取 SPI 接口的所有实现类,这一步是为了防止SPI 接口尚未解析成 Class
getExtensionClasses();
// 遍历所有被@Activate 注解修饰的的缓存,key为 SPI 文件中的key,value为 Activate 注解秀信息
for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Object activate = entry.getValue();
String[] activateGroup, activateValue;
// 如果含有注解 @Activate ,则获取group 和value 值
if (activate instanceof Activate) {
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
} else {
continue;
}
// 如果当前扩展类的 group和 Activate 注解上的group相同,且value值在URL中存在
if (isMatchGroup(group, activateGroup)) {
// 获取该SPI 实现类,并保存到exts 中准备返回
T ext = getExtension(name);
if (!names.contains(name)
&& !names.contains(Constants.REMOVE_VALUE_PREFIX + name)
&& isActive(activateValue, url)) {
exts.add(ext);
}
}
}
// 进行排序
Collections.sort(exts, ActivateComparator.COMPARATOR);
}
List<T> usrs = new ArrayList<T>();
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX)
&& !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {
if (Constants.DEFAULT_KEY.equals(name)) {
if (!usrs.isEmpty()) {
exts.addAll(0, usrs);
usrs.clear();
}
} else {
T ext = getExtension(name);
usrs.add(ext);
}
}
}
if (!usrs.isEmpty()) {
exts.addAll(usrs);
}
return exts;
}
....
private boolean isActive(String[] keys, URL url) {
if (keys.length == 0) {
return true;
}
// 遍历当前扩展类实现的value值
for (String key : keys) {
// 遍历当前URL 存在的所有属性值
for (Map.Entry<String, String> entry : url.getParameters().entrySet()) {
String k = entry.getKey();
String v = entry.getValue();
// 如果url 中属性对的key 等于扩展接口实现的value且值相等,则返回true
if ((k.equals(key) || k.endsWith("." + key))
&& ConfigUtils.isNotEmpty(v)) {
return true;
}
}
}
return false;
}
- Dubbo 在创建 SPI 接口的 适配器类 Spi$Adapive 时,会扫描指定文件,获取到SPI 实现类的全路径名,在利用反射获取Class 时会判断Class 是否被 @Activate修饰,如果被修饰则缓存到 ExtensionLoader#cachedActivates 中。
- 当通过 getActivateExtension(URL url, String key, String group) 调用时,会遍历ExtensionLoader#cachedActivates 并从中获取到Class, 并根据 参数中的key,group 信息进行匹配,如果匹配,则创建对应实例。
- 第二步遍历结束后,将满足条件的SPI 实例集合返回。
服务提供者的自动包装
Dubbo 会给每个服务提供者的实现类创建一个Wrapper类,这个类最终调用服务提供者的接口实现类,Wrapper类的存在时为了减少反射的调用。当服务提供者收到消费者发来的请求后,根据请求方法和参数反射调用提供者的实现类,这样在每次调用时都需要进行反射,而反射本身具有性能开销,Dubbo把每个服务提供者的实现类通过JavaAssist 包装成一个Wrapper类以减少反射性能开销。而这个包装过程是在服务暴露过程(org.apache.dubbo.config.ServiceConfig#export)中发生的
// org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory#getInvoker
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
// 生成 Wrapper类
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
Dubbo 会为 每个SPI 接口生成唯一一个 ExtensionLoader 实现类,通过 ExtensionLoader 可以获取到 SPI 适配器类(是否生成适配方法取决于方法是否被@Adaptive 注解修饰),当调用SPI 接口的方法时,在SPI适配器中从 URL 中获取扩展类型(获取扩展类型的多种情况在上面 1. 适配器模式 中详细介绍了 ),从找到合适的 SPI接口实现类,通过反射创建SPI实现类并缓存,随后调用SPI 接口方法。
对于每一个SPI 接口,仅存在一个与其对应的ExtensionLoader 实例、SPI 适配器实例,对于该接口的不同协议,每个协议实现类也仅存在一个实例
比如对于 RegistryFactory SPI 接口,Dubbo 创建了RegistryFactory 的 ExtensionLoader 实例 ExtensionLoader