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

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

这里写图片描述

ExtensionLoader.getExtensionLoader(),该静态方法接受一个Class对象,返回一个对应Class类型的ExtensionLoader实例,同时该方法是幂等的,即多次接收同一个Class仍然返回同一个ExtensionLoader实例

获取自适应的扩展类

ExtensionLoader实例在第一次调用getAdaptiveExtension方法时,会完整执行一遍获取自适应扩展点实例的逻辑,然后放入自身属性cachedAdaptiveInstance中缓存,后续就直接从cachedAdaptiveInstance获取返回。
下面就是创建一个自适应扩展点实例
这里写图片描述

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

获取自适应扩展点的细节

这里写图片描述

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

这里写图片描述
loadExtensionClasses方主要做了两件事,一是将@SPI注解的value赋值给cachedDefaultName属性,该属性再创建默认扩展实例时会用到;二是调用loadFile方法,扫描文件,整理出该类型的所有扩展点类放入extensionClasses这个map中。会扫描 META-INF/dubbo/internal/、META-INF/dubbo/、META-INF/services/ 这三个路径下的文件

读取文件

文件中每行代表一个扩展点的实现类,同时支持key=value的形式配置,key就是name,这是通过ExtensionLoader的实例方法getExtension获取具体某个扩展类实例的入参,value就是扩展类的全路径,然后会类加载和初始化操作
这里写图片描述

不同的扩展实现类,处理逻辑不一样
  1. 如果扩展点实现类有@Adaptive自适应注解,则将该类赋值给cachedAdaptiveClass属性,一个扩展点接口 只允许有一个标注Adaptive注解的实现类,否则报错
    这里写图片描述
  2. 如果扩展实现类有一个接收该扩展接口类型的构造方法,则认为该扩展实现类是一个wrapper包装类,属性cachedWrapperClasses集合会记录该扩展接口的所有包装实现类
    这里写图片描述
  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是扩展实现类。
    这里写图片描述
小结

loadFile方法,从文件中读取扩展点实现类的name和class全路径,然后根据实现类的特点做不同的处理,ExtensionLoader的实例中有多个map类型的属性,以不同的维度、结构存储加载的信息,在其他实例方法中依赖了这些实例属性

1. Holder<Map<String, Class<?>>> cachedClasses,将缓存loadFile方法的入参extensionClasses,extensionClasses存储的是扩展实现类name和Class对象的映射
2. Class<?> cachedAdaptiveClass,缓存标有@Adaptive的扩展实现类型
3. Set<Class<?>> cachedWrapperClasses,缓存扩展实现类中的wrapper包装类
4. Map<String, Activate> cachedActivates,缓存扩展实现类的name与激活信息的映射
5. ConcurrentMap<Class<?>, String> cachedNames,缓存扩展实现类与name的映射关系
创建自适应扩展类

如果发现了有标注@Adaptive的扩展实现类,则通过cachedAdaptiveClass获取该类型作为自适应扩展类返回。否则将继续执行自适应扩展类的创建操作。
这里写图片描述

创建自适应扩展类的源代码

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
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    @Adaptive
    <T> Invoker<T> refer(Class<T> 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; // 源码中只需要依赖ExtensionLoader

public class Protocol$Adaptive implements Protocol {

    // 接口方法上没有@Adaptive,不是一个自适应方法
    public int getDefaultPort() {
        throw new UnsupportedOperationException("method is not adaptive method!");
    }

    // 参数中没有URL
    public <T> Exporter<T> export(Invoker<T> 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 <T> Invoker<T> refer(Class<T> 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中获取具体实例
这里写图片描述
其中value是作为从URL中获取参数值得key,value值的获取逻辑如下
这里写图片描述

优先从@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的内部实现也依赖了自身的自适应扩展点机制,有了上面的经验,可以尝试找到具体的编译器实现类
这里写图片描述
接口上@SPI的value为javassist,说明默认获取的扩展点实现类的name为javassist
这里写图片描述
有实现类上标注了@Adative

getAdaptiveExtension方法会直接返回AdaptiveCompiler的实例,静态属性 DEFAULT_COMPILER 如果没有手动赋值,则会获取调用loader.getDefaultExtension获取默认扩展实现,就是name为javassist的实现类
这里写图片描述
所以默认是使用JavassistCompiler编译自适应扩展点的源代码,编译后返回Class对象给上层方法,再通过反射获取实例

依赖注入

大致的逻辑就是遍历实例的setter方法,尝试通过ExtensionFactory获取依赖的属性,通过反射注入到实例中
这里写图片描述

ExtensionFactory

初始化一个ExtensionLoader实例,执行构造方法会初始化type属性(扩展点接口类型),如果type 不是 ExtensionFactory,还会初始化objectFactory(扩展点对象工厂)
这里写图片描述
自适应扩展是AdaptiveExtensionFactory
这里写图片描述

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携带的参数信息发现具体实例,然后将委派给该实例执行,有一种活字印刷术的感觉。并且扩展点的实现类只会在真正使用到时才会创建实例对象,减少资源消耗。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值