文章目录
- 一、前言
- 二、动态编译和适配器
- 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实现类是是如下操作。
1
2
3
4
5
6
7
8
9
10
11
12public 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 接口的适配器。其代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import 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 接口为例,创建了各个场景下的适配器生成代码,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141@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两种实现类
1
2
3
4
5
6@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
2
3
4
5// 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。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 在 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 接口的适配器实例。适配器实例是通过动态编译产生的。下面我们来详细看一看。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37public 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 接口适配器的编译工作。其代码如下:
1
2
3
4
5
6
7
8
9
10private Class<?> getAdaptiveExtensionClass() { // 1. 获取了该扩展类接口的所有实现类的Class对象,并缓存到了cachedClasses z中 getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } // 2. 创建了适配器 Class 并返回 return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
下面我们分步详解:
2.1.1. getExtensionClasses();
该方法的作用是获取SPI 接口对应的所有实现类(包括扩展类)的Class,并进行缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49private 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方法中,其代码如下:复制代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66// 这里的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();
这里的方法才是生成适配器的核心方法,步骤也很清楚,如下:
1
2
3
4
5
6
7
8
9
10
11private 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
2
3
4
51. 至少有一个方法被@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的一个扩展点的实现:扩展点之间的依赖注入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45```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 为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import 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 实现类,如果实现类存在包装类,则会创建器包装类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64// 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 的 包装类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public 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。在这里,只要是缓存的包装类就会被加载使用,而不存在协议类型这一说。
1
2
3
4
5
6
7
8
9
10// 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扩展实现类的:
1
2
3
4// getActivateExtension 方法,会搜索出与 key 和 group 匹配的 SPI 扩展类 List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class) .getActivateExtension(invoker.getUrl(), key, group);
比如,当服务端启动时只会加载 group 为 provider,同时协议类型指定为 url.getParameter(Constants.EXECUTES_KEY) 的 Filter
1
2
3@Activate(group = Constants.PROVIDER, value = Constants.EXECUTES_KEY) public class ExecuteLimitFilter implements Filter
当消费端启动时只会加载 group 为consumer,同时协议类型为 url.getParameter(Constants.ACTIVES_KEY) 的Filter的扩展实现类
1
2
3@Activate(group = Constants.CONSUMER, value = Constants.ACTIVES_KEY) public class ActiveLimitFilter implements Filter
在这里我们需要注意两点
- @Activate 注解的功能。
ExtensionLoader#getActivateExtension(URL, String,String)
方法的具体实现。
下面我们来具体讲解:
1.1 @Activate注解
@Activate称为自动激活扩展点注解,主要使用在有多个扩展点实现、需要同时根据不同条件被激活的场景中。即被 @Activate 注解修饰的类,在满足其过滤条件的情况下会自动被激活。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22@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 的实现过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90// 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)中发生的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 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内容请搜索靠谱客的其他文章。
发表评论 取消回复