2. Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现(转)

释放双眼,带上耳机,听听看~!

 

SPI接口定义

定义了@SPI注解


1
2
3
4
5
6
1public @interface SPI {
2
3  String value() default ""; //指定默认的扩展点
4
5}
6

只有在接口打了@SPI注解的接口类才会去查找扩展点实现

会依次从这几个文件中读取扩展点

META-INF/dubbo/internal/   //dubbo内部实现的各种扩展都放在了这个目录了

META-INF/dubbo/

META-INF/services/

 

我们以Protocol接口为例, 接口上打上SPI注解,默认扩展点名字为dubbo


1
2
3
4
5
6
1@SPI("dubbo")
2
3public interface Protocol{
4
5}
6

 

dubbo中内置实现了各种协议如:DubboProtocol InjvmProtocolHessianProtocol WebServiceProtocol等等

2. Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现(转)

Dubbo默认rpc模块默认protocol实现DubboProtocol,key为dubbo

2. Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现(转)


下面我们来细讲ExtensionLoader类

1.      ExtensionLoader.getExtensionLoader(Protocol.class)

每个定义的spi的接口都会构建一个ExtensionLoader实例,存储在

ConcurrentMap<Class<?>,ExtensionLoader<?>> EXTENSION_LOADERS 这个map对象中

 

2.      loadExtensionClasses 读取扩展点中的实现类

a)       先读取SPI注解的value值,有值作为默认扩展实现的key

b)       依次读取路径的文件


1
2
3
4
5
6
1META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
2
3META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol
4
5META-INF/services/ com.alibaba.dubbo.rpc.Protocol
6

 

3.      loadFile逐行读取com.alibaba.dubbo.rpc.Protocol文件中的内容,每行内容以key/value形式存储的。

a)    判断类实现(如:DubboProtocol)上有米有打上@Adaptive注解,如果打上了注解,将此类作为Protocol协议的设配类缓存起来,

 读取下一行;否则适配类通过javasisit修改字节码生成,关于设配类功能作用后续介绍

b)    如果类实现没有打上@Adaptive, 判断实现类是否存在入参为接口的构造器(就是DubbboProtocol类是否还有入参为Protocol的构造器),

 有的话作为包装类缓存到此ExtensionLoader的Set<Class<?>>集合中,这个其实是个装饰模式

2. Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现(转)

c)  如果即不是设配对象也不是wrapped的对象,那就是扩展点的具体实现对象

查找实现类上有没有打上@Activate注解,有缓存到变量cachedActivates的map中

      将实现类缓存到cachedClasses中,以便于使用时获取

4.      获取或者创建设配对象getAdaptiveExtension

a)如果cachedAdaptiveClass有值,说明有且仅有一个实现类打了@Adaptive, 实例化这个对象返回

b) 如果cachedAdaptiveClass为空, 创建设配类字节码。

为什么要创建设配类,一个接口多种实现,SPI机制也是如此,这是策略模式,但是我们在代码执行过程中选择哪种具体的策略呢。Dubbo采用统一数据模式com.alibaba.dubbo.common.URL(它是dubbo定义的数据模型不是jdk的类),它会穿插于系统的整个执行过程,URL中定义的协议类型字段protocol,会根据具体业务设置不同的协议。url.getProtocol()值可以是dubbo也是可以webservice, 可以是zookeeper也可以是redis。

设配类的作用是根据url.getProtocol()的值extName,去ExtensionLoader. getExtension( extName)选取具体的扩展点实现。

所以能够利用javasist生成设配类的条件

1)接口方法中必须至少有一个方法打上了@Adaptive注解

2)打上了@Adaptive注解的方法参数必须有URL类型参数或者有参数中存在getURL()方法

下面给出createAdaptiveExtensionClassCode()方法生成javasist用来生成Protocol适配类后的代码


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
11 package com.alibaba.dubbo.demo.rayhong.test;
2 2
3 3 import com.alibaba.dubbo.common.extension.ExtensionLoader;
4 4
5 5 public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
6 6
7 7     // 没有打上@Adaptive的方法如果被调到抛异常
8 8     public void destroy() {
9 9         throw new UnsupportedOperationException(
1010                 &quot;method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() &quot;
1111                 + &quot;of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!&quot;);
1212
1313     }
1414
1515     // 没有打上@Adaptive的方法如果被调到抛异常
1616     public int getDefaultPort() {
1717         throw new UnsupportedOperationException(
1818                 &quot;method public abstractint com.alibaba.dubbo.rpc.Protocol.getDefaultPort() &quot;
1919                 + &quot;of interfacecom.alibaba.dubbo.rpc.Protocol is not adaptive method!&quot;);
2020     }
2121
2222     // 接口中export方法打上@Adaptive注册
2323     public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) {
2424         if (arg0 == null)
2525             throw new IllegalArgumentException(&quot;com.alibaba.dubbo.rpc.Invokerargument == null&quot;);
2626        
2727         // 参数类中要有URL属性
2828         if (arg0.getUrl() == null)
2929             throw new IllegalArgumentException(&quot;com.alibaba.dubbo.rpc.Invokerargument getUrl() == null&quot;);
3030    
3131         // 从入参获取统一数据模型URL
3232         com.alibaba.dubbo.common.URL url = arg0.getUrl();
3333         String extName = (url.getProtocol() == null ? &quot;dubbo&quot; : url.getProtocol());
3434        
3535         // 从统一数据模型URL获取协议,协议名就是spi扩展点实现类的key
3636         if (extName == null)
3737             throw new IllegalStateException(&quot;Fail to getextension(com.alibaba.dubbo.rpc.Protocol) &quot;
3838                     + &quot;name from url(&quot; + url.toString() + &quot;) usekeys([protocol])&quot;);
3939
4040         // 利用dubbo服务查找机制根据名称找到具体的扩展点实现
4141         com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)
4242                 ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
4343
4444         // 调具体扩展点的方法
4545         return extension.export(arg0);
4646     }
4747
4848     // 接口中refer方法打上@Adaptive注册
4949     public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) {
5050
5151         // 统一数据模型URL不能为空
5252         if (arg1 == null)
5353             throw new IllegalArgumentException(&quot;url == null&quot;);
5454
5555         com.alibaba.dubbo.common.URL url = arg1;
5656        
5757         // 从统一数据模型URL获取协议,协议名就是spi扩展点实现类的key
5858         String extName = (url.getProtocol() == null ? &quot;dubbo&quot; : url.getProtocol());
5959         if (extName == null)
6060             throw new IllegalStateException(&quot;Fail to get extension(com.alibaba.dubbo.rpc.Protocol) &quot;
6161                     + &quot;name from url(&quot; + url.toString() + &quot;) use keys([protocol])&quot;);
6262
6363         // 利用dubbo服务查找机制根据名称找到具体的扩展点实现
6464         com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)
6565                 ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
6666         // 调具体扩展点的方法
6767
6868         return extension.refer(arg0, arg1);
6969
7070     }
7171
7272 }
73

 

5. 通过createAdaptiveExtensionClassCode() 生成如上的java源码代码,

    要被java虚拟机加载执行必须得编译成字节码,
dubbo提供两种方式去执行代码的编译:

   1)利用JDK工具类编译

   2)利用javassit根据源代码生成字节码。

2. Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现(转)

如上图:

1)生成Adaptive代码code

2)利用dubbo的spi扩展机制获取compiler的设配类

3)编译生成的adaptive代码

在此顺便介绍下 @Adaptive注解打在实现类上跟打在接口方法上的区别

1)如果有打在接口方法上,调ExtensionLoader.getAdaptiveExtension()获取设配类,会先通过前面的过程生成java的源代码,

    在通过编译器编译成class加载。但是Compiler的实现策略选择也是通过ExtensionLoader.getAdaptiveExtension(),

    如果也通过编译器编译成class文件那岂不是要死循环下去了吗?

ExtensionLoader.getAdaptiveExtension(),对于有实现类上去打了注解@Adaptive的dubbo spi扩展机制,它获取设配类不在通过前面过程生成设配类java源代码,而是在读取扩展文件的时候遇到实现类打了注解@Adaptive就把这个类作为设配类缓存在ExtensionLoader中,调用是直接返回

 

6.  自动Wrap上扩展点的Wrap类

这是一种装饰模式的实现,在jdk的输入输出流实现中有很多这种设计,在于增强扩展点功能。这里我们拿对于Protocol接口的扩展点实现作为实例讲解。

 2. Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现(转)

如图Protocol继承关系ProtocolFilterWrapper, ProtocolListenerWrapper这个两个类是装饰对象用来增强其他扩展点实现的功能。ProtocolFilterWrapper功能主要是在refer 引用远程服务的中透明的设置一系列的过滤器链用来记录日志,处理超时,权限控制等等功能;ProtocolListenerWrapper在provider的exporter,unporter服务和consumer 的refer服务,destory调用时添加监听器,dubbo提供了扩展但是没有默认实现哪些监听器。

 

Dubbo是如何自动的给扩展点wrap上装饰对象的呢?

1)在ExtensionLoader.loadFile加载扩展点配置文件的时候

对扩展点类有接口类型为参数的构造器就是包转对象,缓存到集合中去

2)在调ExtensionLoader的createExtension(name)根据扩展点key创建扩展的时候, 先实例化扩展点的实现,

     在判断时候有此扩展时候有包装类缓存,有的话利用包转器增强这个扩展点实现的功能。如下图是实现流程

2. Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现(转)

7. IOC 大家所熟知的ioc是spring的三大基础功能之一, dubbo的ExtensionLoader在加载扩展实现的时候

    内部实现了个简单的ioc机制来实现对扩展实现所依赖的参数的注入,dubbo对扩展实现中公有的set方法且入参个数为一个的方法,

    尝试从对象工厂ObjectFactory获取值注入到扩展点实现中去。

2. Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现(转)

 

   上图代码应该不能理解,下面我们来看看ObjectFactory是如何根据类型和名字来获取对象的,ObjectFactory也是基于dubbo的spi扩展机制的

 2. Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现(转)

它跟Compiler接口一样设配类注解@Adaptive是打在类AdaptiveExtensionFactory上的不是通过javassist编译生成的。

AdaptiveExtensionFactory持有所有ExtensionFactory对象的集合,dubbo内部默认实现的对象工厂是SpiExtensionFactory和SrpingExtensionFactory,

他们经过TreeMap排好序的查找顺序是优先先从SpiExtensionFactory获取,如果返回空在从SpringExtensionFactory获取。

1) SpiExtensionFactory工厂获取要被注入的对象,就是要获取dubbo spi扩展的实现,

所以传入的参数类型必须是接口类型并且接口上打上了@SPI注解,返回的是一个设配类对象。

2. Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现(转)

2) SpringExtensionFactory,Dubbo利用spring的扩展机制跟spring做了很好的融合。在发布或者去引用一个服务的时候,会把spring的容器添加到SpringExtensionFactory工厂集合中去, 当SpiExtensionFactory没有获取到对象的时候会遍历SpringExtensionFactory中的spring容器来获取要注入的对象

 2. Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现(转)

8. 下面 给出整体活动图

 2. Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现(转)

 

给TA打赏
共{{data.count}}人
人已打赏
安全网络

CDN安全市场到2022年价值76.3亿美元

2018-2-1 18:02:50

安全经验

编译器

2021-11-28 16:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索