概述
文章目录
- 一、前言
- 二、动态编译和适配器
- 1. 适配器模式
- 2. 动态编译
- 3. @Adaptive 注解
- 三、Dubbo SPI 原理
- 1. ExtensionLoader.getExtensionLoader(RegistryFactory.class);
- 2. extensionLoader.getAdaptiveExtension()
- 2.1 getAdaptiveExtensionClass()
- 2.1.1. getExtensionClasses();
- 2.1.2. createAdaptiveExtensionClass();
- 2.2. injectExtension
- 3. SPI 实现类的初始化
- 3.1 getExtension(extName);
- 四、扩展点的自动包装
- 五、其他
- 1. 加载多个扩展类
- 1.1 @Activate注解
- 1.2 ExtensionLoader#getActivateExtension(URL, String,String)
- 2. 服务提供者的自动包装
- 六、总结
一、前言
本系列为个人Dubbo学习笔记衍生篇,是正文篇之外的衍生内容,内容来源于《深度剖析Apache Dubbo 核心技术内幕》, 过程参考官方源码分析文章。仅用于个人笔记记录。本文分析基于Dubbo2.7.0版本,由于个人理解的局限性,若文中不免出现错误,感谢指正。
Dubbo 为每一个功能点提供了一个SPI 扩展接口。而为了为了不加载不使用SPI 实例,Dubbo通过适配器模式和动态编译技术,加载指定的SPI 实现。
Dubbo 的扩展点机制是基于SDK 中的SPI 增强而来,解决了以下问题:
- JDK标准的SPI 会一次性实例化扩展点的所有实现,如果有些扩展点实现初始化很耗时,但又没用上,那么加载就很浪费资源。比如上面所说的Mysql 和Oracle 数据库驱动,当引入这两个包时,即使我们只需要使用其中一个驱动,另一个驱动实现类也会初始化。
- 如果扩展点加载失败,是不会友好的向用户通知具体异常,异常提示信息可能并不正确。
- 增加了对扩展点 Ioc 和 Aop 的支持,一个扩展点可以直接使用setter() 方法注入其他扩展点,也可以对扩展点使用Wrapper 类进行功能增强。
Dubbo的SPI实现,通过 适配器模式和动态编译来实现,Dubbo 加载 SPI 实现类,不再是通过 ServiceLoader,而是自定义了ExtensionLoader 类
二、动态编译和适配器
Dubbo 通过适配器模式 和 动态编译的方式解决了JDK 加载全部SPI 实现类的问题
1. 适配器模式
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 实现类。
2. 动态编译
在进行进一步分析之前,我们先来解释一下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 对象,之后便可以通过反射直接创建对象。
3. @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();
下面我们来逐步分析
1. ExtensionLoader.getExtensionLoader(RegistryFactory.class);
ExtensionLoader类似于 JDK 标准 SPI 类中的 ServiceLoader,下面的过程也很简单,为每个SPI 接口类创建一个自己的 ExtensionLoader。
// 在 Dubbo 中,每个扩展接口对应自己的ExtensionLoader,key为扩展接口的Class 对象,value为对应的ExtensionLoader
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> 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<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
从上面的代码可以看出,第一个访问某个扩展接口时需要新建一个 ExtensionLoader 对象放到缓存中,后续从缓存中获取。
2. extensionLoader.getAdaptiveExtension()
这一步直接获取到了SPI 接口的适配器实例。适配器实例是通过动态编译产生的。下面我们来详细看一看。
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());
: 进行扩展点的相互依赖注入
其中,由于第二步是直接反射创建实例,所以我们这里重点来看第一步和第三步
2.1 getAdaptiveExtensionClass()
getAdaptiveExtensionClass() 完成了 SPI 接口适配器的编译工作。其代码如下:
private Class<?> getAdaptiveExtensionClass() {
// 1. 获取了该扩展类接口的所有实现类的Class对象,并缓存到了cachedClasses z中
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 2. 创建了适配器 Class 并返回
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
下面我们分步详解:
2.1.1. 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()); } } } } }
2.1.2. 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对象,后续通过反射即可获取到适配器对象。
2.2. injectExtension
该方法也是 Dubbo SPI的一个扩展点的实现:扩展点之间的依赖注入。
```java
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对象的实例化过程。
3. 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 实现类。
3.1 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 接口文件
在调用真正的 RegistryFactory 时会先调用 RegistryFactoryWrapper
需要注意的是,对于SPI Wrapper 类来说,不存在协议类型匹配一说, SPI Wrapper仅根据SPI 接口类型生效 。即使将 org.apache.dubbo.registry.RegistryFactory
文件的内容 改成了aaa=com.kingfish.main.spi.RegistryFactoryWrapper
或者 com.kingfish.main.spi.RegistryFactoryWrapper
,依然会使用该包装类进行包装。
在 2.1.1 中我们解析了 loadDirectory 方法,其中包括对包装类的缓存 cachedWrapperClasses,此时可以发现,这里做的仅仅是缓存包装类,但是并没有解析包装类对应什么协议。
在 3.1 中 我们在这一块代码使用到了 cachedWrapperClasses。在这里,只要是缓存的包装类就会被加载使用,而不存在协议类型这一说。
// Wrapper 对扩展实现进行功能增强,这里的 cachedWrapperClasses 是 2.1.1 中我们解析了 loadDirectory 方法,其中包括对包装类的缓存
// 如果 cachedWrapperClasses不为空,则说明存在该实现类的包装类,则对其包装类进行初始化。将SPI实现类作为构造入参注入
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
// 对包装类进行 IOC 注入
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
所以整个Dubbo SPI 流程总结下来就是 :
- 程序启动,创建当前SPI 接口的ExtensionLoader 对象(如果已有,则使用缓存)
- 在不存在适配器缓存的情况下,着手创建适配器对象。
- 创建适配器第一步 : 加载出所有SPI接口实现类的Class对象,并进行缓存。同时缓存了所有SPI 接口包装类Class对象
- 创建适配器第二步 : 动态拼接SPI 适配器源码,随后进行动态编译,获取到 适配器的Class对象,并通过newInstance 进行反射获取适配器对象实例
- 创建适配器第三步 :对适配器对象进行 依赖注入(仅能通过setter方法注入)。
- 调用适配器的SPI 接口方法,在调用时 如果SPI 实现类没有初始化,则会通过ExtensionLoader#getExtension 进行初始化。
- 初始化过程中,会获取 包装类的缓存,如果存在包装类缓存,则会将SPI实现类作为一个包装器的一个构造入参,构造出包装器类并返回。也即是说在允许创建适配器和存在包装了的情况下,整个调用过程是 : SPI Adapter -> SPI wrapper -> SPI 实现类
五、其他
1. 加载多个扩展类
在 3.1 中的 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)
方法的具体实现。
下面我们来具体讲解:
1.1 @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中,等待合适的时机被激活。这一点我们在 2.1.1. getExtensionClasses();
章节中有过分析
1.2 ExtensionLoader#getActivateExtension(URL, String,String)
在上面我们讲到了 :@Activate 的实现逻辑如下: 在 2.1.2 中我们知道了Dubbo在加载 SPI 所有实现类的时候会将被 @Activate 注解修饰的SPI 实现类缓存到 cachedActivates 中。而在通过 ExtensionLoader#getActivateExtension(URL, String, String)
加载 SPI 实现类的时候,会判断是否满足 @Activate 注解的条件,如果满足,则会将其一并返回(这里便将 Filter 返回)。
下面我们从代码层面看一下 @Activate 的实现过程:
// 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 实例集合返回。
2. 服务提供者的自动包装
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<RegistryFactory>
(该实例会被缓存,因此只会创建一次),同时通过 ExtensionLoader<RegistryFactory>#getAdaptiveExtension
可以获取到 RegistryFactory 的适配器类 RegistryFactory$Adaptive
(适配器是否生成相应的方法取决于方法是否被@Adaptive 注解修饰,如果方法被修饰,则会在适配器中创建该方法的代理,否则则不会),在 调用 SPI 方法时会从 接口参数中通过 URL.getProtocol() 中获取本次调用的 协议类型,根据协议类型的不同获取不同的 SPI 实现类(这里会将SPI 实现类缓存起来,也即是说一个SPI实现类只会存在一个实例)。
以上:内容部分参考
《深度剖析Apache Dubbo 核心技术内幕》
https://blog.csdn.net/weixin_33690963/article/details/94609561
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正
最后
以上就是飘逸羽毛为你收集整理的Dubbo笔记衍生篇②:Dubbo SPI 原理一、前言二、动态编译和适配器三、Dubbo SPI 原理四、扩展点的自动包装五、其他六、总结的全部内容,希望文章能够帮你解决Dubbo笔记衍生篇②:Dubbo SPI 原理一、前言二、动态编译和适配器三、Dubbo SPI 原理四、扩展点的自动包装五、其他六、总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复