深入dubbo之ExtensionLoader,灵活的扩展点加载机制

在准备阅读dubbo源码的过程中,必须要先弄清楚ExtensionLoader——扩展点加载,dubbo的整体架构风格采用Microkernel + Plugin,最大程度的面向接口不依赖具体实现,dubbo自身的功能就是通过组装扩展点实现的,官方文档中给出了26个扩展点,在遵守扩展点契约的前提下,用户可以自行扩展任意一个扩展点。
深入dubbo之ExtensionLoader,灵活的扩展点加载机制
文章图片

在dubbo源码中,类似上面这样通过ExtensionLoader获取实例的代码随处可见,如果不理解ExtensionLoader的实现机制,源码用到扩展接口的方法时,不能明确到底是用的哪个实现类,仅凭猜想,这样就很难顺畅的阅读源码,更不用说如何扩展了
简述Java SPI——ServiceLoader
Java SPI(service provider interface)提供了一种机制,自动发现接口的实现类。
使用方式:首先ServiceLoader.load, 该静态方法接收一个class类型,返回一个ServiceLoader实例,然后调用serviceLoader.iterator实例方法获取一个迭代器,然后遍历这个迭代器。由于ServiceLoader实现了Iterable接口,可以直接使用foreach语法直接遍历serviceLoader。
实现原理:ServiceLoader在构造实例时会初始化一个私有内部类LazyIterator的实例,该类实现了Iterator接口,是一个具体的迭代器实现,所以ServiceLoader实例在迭代遍历时实际是委托给LazyIterator的,而LazyIterator则会扫描classpath下META-INF/services/class类型全名称的文件,例如JDBC驱动 META-INF/services/java.sql.Driver,该文件里每一行代表一个该接口的实现类。
分析:应用程序通过遍历serviceLoader,获取接口的实例,配置了多少实现类就会返回多少个实例,该使用哪个实现类需要应用程序自行判断,即使永远不会用到的接口实现,也会被实例化。
dubbo的ExtensionLoader,更强大的扩展点加载机制 深入dubbo之ExtensionLoader,灵活的扩展点加载机制
文章图片

【深入dubbo之ExtensionLoader,灵活的扩展点加载机制】ExtensionLoader.getExtensionLoader(),该静态方法接受一个Class对象,返回一个对应Class类型的ExtensionLoader实例,同时该方法是幂等的,即多次接收同一个Class仍然返回同一个ExtensionLoader实例
获取自适应的扩展类 ExtensionLoader实例在第一次调用getAdaptiveExtension方法时,会完整执行一遍获取自适应扩展点实例的逻辑,然后放入自身属性cachedAdaptiveInstance中缓存,后续就直接从cachedAdaptiveInstance获取返回。
下面就是创建一个自适应扩展点实例
深入dubbo之ExtensionLoader,灵活的扩展点加载机制
文章图片

  1. 首先是获取自适应扩展点的Class对象,然后通过反射获取实例,将对象创建的权利交付给框架,这就是控制反转
  2. 然后,对该实例进行依赖注入,这也是ExtensionLoader的一个特性,具体行为和实现原理,后面再分析
获取自适应扩展点的细节
深入dubbo之ExtensionLoader,灵活的扩展点加载机制
文章图片

获取扩展点的所有实现类
getExtensionClasses会同步加载该扩展点的所有扩展实现类

深入dubbo之ExtensionLoader,灵活的扩展点加载机制
文章图片

loadExtensionClasses方主要做了两件事,一是将@SPI注解的value赋值给cachedDefaultName属性,该属性再创建默认扩展实例时会用到;二是调用loadFile方法,扫描文件,整理出该类型的所有扩展点类放入extensionClasses这个map中。会扫描 META-INF/dubbo/internal/、META-INF/dubbo/、META-INF/services/ 这三个路径下的文件
读取文件 文件中每行代表一个扩展点的实现类,同时支持key=value的形式配置,key就是name,这是通过ExtensionLoader的实例方法getExtension获取具体某个扩展类实例的入参,value就是扩展类的全路径,然后会类加载和初始化操作
深入dubbo之ExtensionLoader,灵活的扩展点加载机制
文章图片

不同的扩展实现类,处理逻辑不一样
  1. 如果扩展点实现类有@Adaptive自适应注解,则将该类赋值给cachedAdaptiveClass属性,一个扩展点接口 只允许有一个标注Adaptive注解的实现类,否则报错
    深入dubbo之ExtensionLoader,灵活的扩展点加载机制
    文章图片
  2. 如果扩展实现类有一个接收该扩展接口类型的构造方法,则认为该扩展实现类是一个wrapper包装类,属性cachedWrapperClasses集合会记录该扩展接口的所有包装实现类
    深入dubbo之ExtensionLoader,灵活的扩展点加载机制
    文章图片
  3. 如果上述都不满足,则证明只是一个普通的扩展点实现类,这时才会使用从文件内容中获取的name值,如果name为空会拿一个name的缺省值 例如Protocol 的扩展点实现类 RegistryProtocol,如果name为空,则会截取前缀registry作为默认name。 如果实现类标注有@Activate注解,则会放到cachedActivates这个map中,key是name,value是@Activate的实例。然后存入cachedNames这个map,key是扩展实现类,value是name。最后存入extensionClasses这个map,key是name,value是扩展实现类。
    深入dubbo之ExtensionLoader,灵活的扩展点加载机制
    文章图片
小结 loadFile方法,从文件中读取扩展点实现类的name和class全路径,然后根据实现类的特点做不同的处理,ExtensionLoader的实例中有多个map类型的属性,以不同的维度、结构存储加载的信息,在其他实例方法中依赖了这些实例属性
1. Holder> cachedClasses,将缓存loadFile方法的入参extensionClasses,extensionClasses存储的是扩展实现类name和Class对象的映射 2. Class cachedAdaptiveClass,缓存标有@Adaptive的扩展实现类型 3. Set> cachedWrapperClasses,缓存扩展实现类中的wrapper包装类 4. Map cachedActivates,缓存扩展实现类的name与激活信息的映射 5. ConcurrentMap, String> cachedNames,缓存扩展实现类与name的映射关系

创建自适应扩展类 如果发现了有标注@Adaptive的扩展实现类,则通过cachedAdaptiveClass获取该类型作为自适应扩展类返回。否则将继续执行自适应扩展类的创建操作。
深入dubbo之ExtensionLoader,灵活的扩展点加载机制
文章图片

创建自适应扩展类的源代码 createAdaptiveExtensionClassCode方法会创建一份扩展点接口的自适应扩展类源代码,首先扩展点接口类型必须包含一个标注了@Adaptive的方法,否则会拒绝创建自适应扩展类,接口上标注了@Adaptive的方法,在自适应扩展类中会包装这个方法。
com.alibaba.dubbo.common.URL,这个类的作用是携带配置信息,在流程中传播,类似于上下文,自适应毕竟只是一个代理,扩展点接口的自适应方法实现里最终需要发现具体的扩展实现类,然后调用该实现类的对应方法,期间就是依靠URL携带的参数作为name从ExtensionLoader获取对应的扩展点实现类,上面提过的extensionClasses就是name和对应Class对象的映射,缓存在cachedClasses中。
package com.alibaba.dubbo.rpc; import com.alibaba.dubbo.common.URL; import com.alibaba.dubbo.common.extension.Adaptive; import com.alibaba.dubbo.common.extension.SPI; @SPI("dubbo") public interface Protocol {int getDefaultPort(); @Adaptive Exporter export(Invoker invoker) throws RpcException; @Adaptive Invoker refer(Class type, URL url) throws RpcException; void destroy(); }

上面是dubbo的协议扩展点接口,自适应扩展类获取URL对象的方式有两种:
  1. 接口方法的参数中直接包含URL类型的参数,如refer(Class type, URL url)
  2. 逐个遍历参数列表,某个参数类型如果有get方法的返回类型为URL,则从该参数中获取URL,如export(Invoker invoker),Invoker继承Node接口,Node接口中定义了getUrl方法,返回URL对象,故而自适应扩展类在实现export方法时会从invoker中获取URL信息
为Protocol创建一个自适应扩展类,大致伪代码如下
package com.alibaba.dubbo.rpc; // 和扩展点接口在一个包 import com.alibaba.dubbo.common.extension.ExtensionLoader; // 源码中只需要依赖ExtensionLoaderpublic class Protocol$Adaptive implements Protocol {// 接口方法上没有@Adaptive,不是一个自适应方法 public int getDefaultPort() { throw new UnsupportedOperationException("method is not adaptive method!"); }// 参数中没有URL public Exporter export(Invoker invoker) throws RpcException { // 首先包含URL对象的参数不能为空 if (invoker == null) throw new IllegalArgumentException("xxx"); // 然后URL对象不能为空 if (invoker.getUrl() == null) throw new IllegalArgumentException("xxx"); com.alibaba.dubbo.common.URL url = invoker.getUrl(); // 获取URl对象// 由于是Protocol的自适应扩展类,url对protocol的获取方式和其他参数的获取方式不一样 String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); // 获取具体的扩展实现类实例 Protocol extension = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName); // 委派给具体扩展实现类 return extension.export(invoker); }// 参数中有URL public Invoker refer(Class type, URL url) throws RpcException { if (url == null) throw new IllegalArgumentException("xxx"); // 由于是Protocol的自适应扩展类,url对protocol的获取方式和其他参数的获取方式不一样 String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); // 获取具体的扩展实现类实例 Protocol extension = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName); // 委派给具体扩展实现类 return extension.refer(invoker, url); } }

关键点就是从URL中获取extName,再根据extName从ExtensionLoader中获取具体实例
深入dubbo之ExtensionLoader,灵活的扩展点加载机制
文章图片

其中value是作为从URL中获取参数值得key,value值的获取逻辑如下
深入dubbo之ExtensionLoader,灵活的扩展点加载机制
文章图片

优先从@Adaptive中获取,如果为空,则设置默认值。例如Protocol接口默认的value值为protocol,ProxyFactory接口默认的value值就是proxy.factory
以上构建自适应扩展源代码就完成了
编译源码
// 获取自适应的编译器 com.alibaba.dubbo.common.compiler.Compiler compiler=ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();

可以看出ExtensionLoader的内部实现也依赖了自身的自适应扩展点机制,有了上面的经验,可以尝试找到具体的编译器实现类
深入dubbo之ExtensionLoader,灵活的扩展点加载机制
文章图片

接口上@SPI的value为javassist,说明默认获取的扩展点实现类的name为javassist
深入dubbo之ExtensionLoader,灵活的扩展点加载机制
文章图片

有实现类上标注了@Adative
getAdaptiveExtension方法会直接返回AdaptiveCompiler的实例,静态属性 DEFAULT_COMPILER 如果没有手动赋值,则会获取调用loader.getDefaultExtension获取默认扩展实现,就是name为javassist的实现类
深入dubbo之ExtensionLoader,灵活的扩展点加载机制
文章图片

所以默认是使用JavassistCompiler编译自适应扩展点的源代码,编译后返回Class对象给上层方法,再通过反射获取实例
依赖注入 大致的逻辑就是遍历实例的setter方法,尝试通过ExtensionFactory获取依赖的属性,通过反射注入到实例中
深入dubbo之ExtensionLoader,灵活的扩展点加载机制
文章图片

ExtensionFactory 初始化一个ExtensionLoader实例,执行构造方法会初始化type属性(扩展点接口类型),如果type 不是 ExtensionFactory,还会初始化objectFactory(扩展点对象工厂)
深入dubbo之ExtensionLoader,灵活的扩展点加载机制
文章图片

自适应扩展是AdaptiveExtensionFactory
深入dubbo之ExtensionLoader,灵活的扩展点加载机制
文章图片

dubbo-common模块中发现扩展类
adaptive=com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory
dubbo-config-spring模块中发现
spring=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory
所以应用程序如果加载了dubbo-config-spring,就可以在spring容器中发现依赖注入给扩展点实例,但是一般来说注入依赖还是依靠SpiExtensionFactory,如果扩展点实现类的属性也是一个扩展点接口,则会自动创建一个该扩展点接口的自适应扩展实例注入给该实例
总结
dubbo的ExtensionLoader更为灵活的应用了服务发现机制,并且将粒度细化到方法级别,调用自适应扩展类实例的自适应方法,利用URL携带的参数信息发现具体实例,然后将委派给该实例执行,有一种活字印刷术的感觉。并且扩展点的实现类只会在真正使用到时才会创建实例对象,减少资源消耗。

    推荐阅读