dubbo提供扩展功能依赖的是自己实现的SPI机制,原理类似JDK的SPI.这次我们啃一下这块.
开始
dubbo的启动类DubboBootstrap的构造函数中ApplicationModel.getConfigManager()这行其实就是ExtensionLoader的最早使用,所以ExtensionLoader还是初始化比较早的.
ExtensionLoader
我们的惯例是先看构造方法,可以看到构造方法里传达了两个信息
- 构造方法是private的所以肯定不是new对象使用的(基本算是废话因为ApplicationModel.getConfigManager()里就能看到ExtensionLoader.getExtensionLoader(FrameworkExt.class)这就是用法)
- 如果传的type是ExtensionFactory的话this.objectFactory = null.
getExtensionLoader()
这个方法比较简单,先做了参数校验略过,然后就是把一个static的Map中取某个类是不是有对应的缓存,没有就new一个ExtensionLoader放进去下次直接用.
所以这块代码里就一个重点,ExtensionLoader的构造方法.也就是这一行
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension())
可以看到,如果需要的是ExtensionFactory类型的ExtensionLoader,objectFactory就是null,否则的话objectFactory=ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()
不难发现,重点转到了ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension(),所以我们接下来看getAdaptiveExtension()方法.
getAdaptiveExtension()
这个方法看起来挺长,其实就两个思想
- 属性cachedAdaptiveInstance是不是有值了,有的话直接return,没有的话初始化.
- 初始化的时候为了线程安全加双重锁校验.
所以其实这个方法里就一行代码核心代码: createAdaptiveExtension()
createAdaptiveExtension()
从方法名上就可以看出来,这个是真正实例化Extension实例的方法
核心有两部分:
- getAdaptiveExtensionClass().newInstance() 找到interface指定的实现类并且加载为Class对象,并且调用Class对象的newInstance()方法实例化
- injectExtension(T) 注入属性,这个注入有点像是spring的属性注入
getAdaptiveExtensionClass()
这个方法进来就执行了一个方法: getExtensionClasses()
getExtensionClasses()
dubbo的SPI机制: 读取【META-INF/dubbo/internal/等 + 接口全限定名】为文件名的文件内容获取所有接口的实现,而这个方法就是这个机制的真正入口.
文件内容示例
cat dubbo-2.7.6.jar!/META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory
spring=org.apache.dubbo.config.spring.extension.SpringExtensionFactory
adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory
我们看下调用链:
getExtensionClasses() -> loadExtensionClasses() -> cacheDefaultExtensionName() & loadDirectory()
getExtensionClasses(): 还是看起来代码长,只有一行核心,其他都是双重锁校验的代码(为了多线程安全)
loadExtensionClasses(): 两件事,1. cacheDefaultExtensionName(). 2. 遍历所有strategies然后执行loadDirectory()❶
cacheDefaultExtensionName(): check一下接口是不是添加了SPI注解,如果加了的话,value有没有值,有的话按照逗号分隔,然后切分后长度是1的话默认扩展名就是这个value(这里切分一次非常奇怪,后边再深究吧❷)
strategies: 有三个实现 1. DubboInternalLoadingStrategy(路径前缀META-INF/dubbo/internal/) 2. DubboLoadingStrategy(路径前缀META-INF/dubbo/) 3. ServicesLoadingStrategy(路径前缀META-INF/services/)
loadClass(): 三种情况, 1. 有Adaptive注解❸ 2. 类没有构造方法 3. 如果配置中一行只有类名没有name=的话,取Extension直接指定的name,没有注解的话取类的getSimpleName,如果类名是已Xxx接口名,只截取Xxx部分,否则不截取,然后变小写
现在我们已经拿到了接口具体实现的Class实例和对应的name
然后我们要做几件事:
- cacheActivateClass() 如果列上标注了Activate注解,就缓存到一个专用map了,暂时还没捋清楚用来干什么❹
- cacheName() 把接口的每个具体实现的Class实例和其对应的name缓存,方便之后获取实现对应的name
- saveInExtensionClass() 这个就比较重要了,存的就是每个name对应的接口实现的Class实例,最简单理解的话,就是之后取这个Class然后getConstructor().newInstance()就能创建对象了.
getExtension()
这个就是根据指定的name取对应实现的对象的方法.
还是几个步骤:
- 之前加载的时候已经在cachedClasses中缓存了所有的name对应的Class了,现在只用调用newInstance()方法就会创建实例,创建之前,老规矩,双重锁校验
- 缓存到EXTENSION_INSTANCES中,这是一个 ConcurrentMap<Class, Object>, 非常好理解.
- injectExtension() 这个还挺有意思,找到这个对象的所有setter方法,然后取出来属性名作为name,参数类型作为Class,委托SpiExtensionFactory找看有没有,找到之后执行这个setter方法注入,没找到的话再委托SpringExtensionFactory在spring的容器中找,还是找到了的话就执行setter注入,没找到就用null作为参数注入.
TODO
❶. 研究一下是不是可以通过添加LoadingStrategy实现添加其他扫描路径(META-INF/dubbo/internal/等默认路径之外的路径)
❷. 这个比较奇怪,看起来像是不想要name里的英文逗号,但是仅此而已的话使用正则然后切分是不是有点杀鸡用牛刀了.
❸. 每个ExtensionLoader会缓存一个cachedAdaptiveClass,调用getAdaptiveExtension()方法的时候会优先使用这个对象. 一个细节, META-INF/dubbo/internal/ 和 META-INF/dubbo/下的类每个接口的实现可以有多个Adaptive注解,从代码看,配置文件中是最后一行有Adaptive直接的类
❹. Wapper机制会用到,dubbo会将符合Wrapper机制的类组装成一个典型的装饰器模式,感兴趣的话可以看Protocol接口和对应的实现,其中,配置文件中XxxWrapper就是实现.这个具体我们后续需要的话再展开,可以先参考 https://www.cnblogs.com/caoxb/p/13140345.html
最后
现在我们开始总结:
首先dubbo在启动的时候,DubboBootstrap的后遭方法会调用ApplicationModel.getConfigManager(),调用什么方法不重要ApplicationModel在加载的时候会初始化static属性LOADER,这个时候调用ExtensionLoader.getExtensionLoader(FrameworkExt.class)真正进入ExtensionLoader的开始逻辑.
此时会在EXTENSION_LOADERS中找是不是有缓存的FrameworkExt.class对应的ExtensionLoader,有的话直接用,没有的话new一个然后用.(这个ExtensionLoader可以类比Spring IoC中的BeanDefinition)
new当然会执行ExtensionLoader的构造方法,type赋值不重要关键会执行 objectFactory = ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension(),开始套娃了.
ExtensionLoader.getExtensionLoader(ExtensionFactory.class)还是要在EXTENSION_LOADERS中找ExtensionFactory.class对用的ExtensionLoader,没有new一个.
new的时候还是执行ExtensionLoader的构造方法,type依然不重要还是走到objectFactory赋值这句,此时,type是ExtensionFactory.class所以objectFactory=null,构造方法执行完成.
然后调用getAdaptiveExtension()开始加载所有实现类(这个方法有点类似Spring的getBean()方法).集体逻辑上边说了就不展开了.
然后取到bean之后注入.ExtensionFactory初始化就完成了.
这里略过了一些细节,后续的文章我们展开一下.
ExtensionFactory初始化完了之后再回到FrameworkExt初始化的逻辑,此时我们还在FrameworkExt.class对应的ExtensionLoader初始化时候的构造方法,ExtensionFactory初始化完成赋值给ExtensionLoader<FrameworkExt.class>对象的objectFactory属性.
现在回到了ApplicationModel.getConfigManager()方法,ExtensionLoader.getExtensionLoader(FrameworkExt.class).getExtension(ConfigManager.NAME).
over.
列一下几个关键属性
- ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS 全局一个,每个SPI接口对应的实现的factory
- Holder<Map<String, Class<?>>> cachedClasses 每个SPI接口对应一个,Holder不重要关键还是这个map,缓存的是接口每种实现name和Class的对用关系
- ConcurrentMap<Class<?>, String> cachedNames 每个SPI接口对应一个,和上边相反,缓存的是每个实现Class和name的对应关系
- ConcurrentMap<String, Holder<Object>> cachedInstances 同样每个SPI接口对应一个,缓存name对应的已经组装好的对象,也就是getExtension()最终获取到的对象.