一、Dubbo内核
Dubbo内核主要包含SPI、AOP、IOC、Compiler。
二、JDK的SPI
1.spi的设计目标:
面向对象的设计里,模块之间是基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可插拔的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候,不在模块里写死代码,就需要一种服务发现机制。Java SPI就提供了这样一种机制:为某个接口寻找服务实现,有点类似IOC思想,将装配的控制权移到代码之外。
2.JDK的SPI的默认约定
当服务的提供者提供了一个接口的多种实现时,一般会在jar包的META-INF/services目录下,创建该接口的同名文件,文件的内容就是该服务接口的具体实现类的全类名。
三、Dubbo为什么不采用JDK的SPI
JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时。但如果没用上也加载,会很浪费资源。针对这个问题,Dubbo增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。
四、Dubbo SPI的默认约定
1.spi 文件存储路径在META-INF\dubbo\internal 目录下并且文件名为接口的全路径名。即接口文件的全类名。
2.每个spi 文件里面的格式定义为: 扩展名=具体的类名,例如 dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtoco。使用时通过key加载(如dubbo),可以实现部分加载。
五、SPI示例
遵循上述第一条第2点,这里Command为接口文件,其中StartCommand和ShutdownCommand为两个实现类。需要在resources目录下建META-INF子目录,在META-INF下建services目录,然后以接口全路径作为文件名创建文件,内容为接口实现类的全类型名。
Command.java
1 | package com.dongqiang.soa.spi; |
StartCommand.java
1 | package com.dongqiang.soa.spi; |
ShutdownCommand.java
1 | package com.dongqiang.soa.spi; |
Main类:
1 | package com.dongqiang.soa.spi; |
六、结合Dubbo源码分析Spi
如前所说,Dubbo SPI的目的是获取一个指定实现类的对象。那么Dubbo是通过什么方式获取的呢?其实是调用ExtensionLoader.getExtension(String name)实现。
具体实现途径有三种:
①getExtensionLoader(Class
②getAdaptiveExtension() 获取一个扩展装饰类的对象,这个类有一个规则,如果它没有@Adaptive注解,就动态创建一个装饰类,例如Protocol$Adaptive对象。
③getExtension(String name) 获取一个指定对象。
(1)分析ExtensionLoader.getExtensionLoader(Class
Dubbo的第一行代码在哪里?
idea导入Dubbo源码,在子模块dubbo-demo-provider/src/test下有DemoProvider.java
1 | package com.alibaba.dubbo.demo.provider; |
这里便是代码的入口。
这里调到com.alibaba.dubbo.container.Main.java
1 | package com.alibaba.dubbo.container; |
可以看到,Main类中定义了一系列的静态成员变量,其中:
1 | private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class); |
在Main类初始化阶段调用了上述第①条方式为Container创建扩展点。
通过断点跟进getExtensionLoader方法,会进行new ExtensionLoader
1 | private ExtensionLoader(Class<?> type) { |
可以看到,这里会进一步调用getExtensionLoader方法,只是这次传入的是ExtensionFactory.class。通过上面的代码知道,等价于如下:
1 | this.type = type; |
执行以上代码完成了2个属性的初始化:
1.每个ExtensionLoader都包含了2个值: type 和 objectFactory
Class<?> type;//构造器初始化时要得到的接口名
ExtensionFactory objectFactory//构造器初始化时设置为AdaptiveExtensionFactory,Dubbo内部默认的实现是SpiExtensionFactory和SpringExtensionFactory。
2.new 一个ExtensionLoader 存储在ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS里。
关于objectFactory
1.objectFactory就是ExtensionFactory,它也是通过ExtensionLoader.getExtensionLoader(ExtensionFactory.class)来实现的,但是它的objectFactory=null
2.objectFactory作用,它就是为dubbo的IOC提供所有对象。
(2)分析getAdaptiveExtension()
为什么要设计Adaptive?
Adaptive注解在类和方法上有什么区别?
①注解在类上,代表人工实现编码,即实现了一个装饰类,如ExtensionFactory。
②注解在方法上,代表自动生成和编译一个动态的adaptive类,如Protocol$Adaptive。
接下来从子模块dubbo-config-spring下的schema包的DubboNamespaceHandler开始分析:
1 | package com.alibaba.dubbo.config.spring.schema; |
先来看
1 | registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true)); |
这里ServiceBean继承自ServiceConfig类。
1 | public class ServiceConfig<T> extends AbstractServiceConfig { |
在这里通过getAdaptiveExtension()获取protocol。
1 | -->getAdaptiveExtension()//为cachedAdaptiveInstance赋值 |
loadFile(..)方法的作用:把SPI配置文件(如META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol)的内容,存储在缓存变量里。使用了四个缓存变量。
①缓存包含Adaptive注解的类
cachedAdaptiveClass 如果这个Class含有adaptive注解就赋值进去,如ExtensionFactory有,而Protocol没有。
②缓存无Adaptive注解的封装类
cachedWrapperClasses 只有当该class无adaptive注解,并且构造方法参数为目标接口(type,如Protocol)类型,如Protocol里的SPI就只有ProtocolFilterWrapper和ProtocolListenerWrapper能命中,如下例:
1 | public class ProtocolFilterWrapper implements Protocol { |
③cachedActivates 剩下的包含Activate注解的类
④cachedName 剩下的类存储在该map中
在loadExtensionClasses()方法中,有三处loadFile()加载SPI文件:
1 | private Map<String, Class<?>> loadExtensionClasses() { |
这里的三处loadFile()实际上起到真正作用的是第一个:路径为META-INF/dubbo/internal/,这个打开dubbo.jar即可看到,这里仍然看com.alibaba.dubbo.rpc.Protocol这个SPI文件:
1 | registry=com.alibaba.dubbo.registry.integration.RegistryProtocol |
上面执行compile时,框架会自动生成如下Protocol$Adpative类代码:
1 | package com.alibaba.dubbo.rpc; |
其实就是根据如下模板生成的:
1 | package <扩展点接口所在包>; |
总结起来,Dubbo的所有对象都是通过ExtensionLoader获取的,SPI是内核。
(3)分析getExtension(String name)
为了进一步分析代理类的扩展类对象生成过程,将Protocol$Adpative类手动创建到dubbo源码子模块dubbo-demo下的dubbo-demo-provider中,test目录下新建包com.alibaba.dubbo.rpc。然后将上述代码拷贝其中。
然后在getExtension(extName)这里设置断点:
通过断点跟踪,调用链如下:
1 | -->getExtension(String name) //指定对象缓存在cachedInstances;get出来的对象可能是wrapper对象,例如protocol就是ProtocolFilterWrapper和ProtocolListenerWrapper其中一个。 |
通过上述分析,总结起来SPI getExtension()的执行流程及设计模式如下: