Dubbo源码分析-微内核插件式开发(ExtensionLoader)

一、实现原理说明

上一个章节说道Dubbo的微内核插件式开发其实就是在SPI设计思路上作了优化升级。咱们都知道SPI设计思路是当咱们要获取某个接口的实现时,使用ServiceLoader去扫描第三方依赖JAR包的META-INF/services/目录,若是找到名字为这个接口全路径的文件,那么就会读取这个文件中的内容,而后SPI约定这个内容就是实现类的全路径名称,因此ServiceLoader就能够根据这个实现类路径来实例化提供服务。而Dubbo是本身实现了加载器,名字叫ExtensionLoader。Dubbo约定只有标记了@SPI注解的接口,才能使用ExtensionLoader去加载实现类。ExtensionLoader会依次扫描META-INF/dubbo/internal/目录、META-INF/dubbo/目录、META-INF/services/目录,扫描出名字为接口的全路径名的文件,而后文件内容约定key=value的形式出现,key就是这个实现类的别名,value才是实现类的全路径。固然当一个项目里面,对同一个接口可能会不少种实现,那到底使用哪一种实现,因此这里Dubbo作了特别的设计,每一个支持SPI接口会对应须要一个适配实现(能够本身实现,也能够系统帮你生成),  而后用户能够根据这个设配实现来决定最后到底须要选择哪一个具体实现。ExtensionLoader在扫描到实现类时,并发现这个实现类上标记了@Adaptive注解时,就会把这个实现类做为适配实现看待,若是发现扫描出来的实现类里面一个都没有标记这个注解,那么ExtensionLoader会自动生成一个实现类做为该接口的设配实现,因此全部的@SPI接口在使用时都会存在一个适配实现。java

二、@SPI注解

上面说到了,只有注解了SPI注解的接口,ExtensionLoader才支持加载扩展实现,SPI有个value参数,这个value指定的是扩展实现的的别名,指定后默认使用的就是这个别名对应的扩展实现。SPI源码以下:数组

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
	String value() default "";
}

Dubbo内部的不少实现都基于这种方式来扩展实现的。咱们都知道Dubbo支持不少序列化协议,我就拿这个例子来讲明,看Protocol协议接口,他标记了SPI注解,并指定了默认实现是别名叫dubbo的协议实现。因此系统里面经过ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();获取协议实现时,若是没有特别指定用哪一种实现,默认就会使用别名叫dubbo的协议。缓存

@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();
}

三、@Adaptive注解

做用一:@Adaptive注解主要用来标记适配类实现,上面说了,ExtensionLoader在各个依赖JAR寻找实现类时,会检查实现类有没有打上@Adaptive标记,若是打上了,说明该实现类就是适配实现。就会缓存到ExtensionLoader的cachedAdaptiveClass变量里面。例如我想要本身实现一个Protocol接口的适配实现,能够这样写:并发

@Adaptive
public class AdapterProtocol implements Protocol {
    @Override
    int getDefaultPort(){//省略...}
    
    @Override
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException{//省略...}

    @Override
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException{//省略...}
    
    @Override
    void destroy(){//省略...}
}

而后在META-INF/services/目录下放置文件:com.alibaba.dubbo.rpc.Protocol。里面的内容设置成adapterProtocol=mypackage.AdapterProtocol。这样咱们就是本身实现了一个Protocol的设配实现,当系统使用ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();去加载实现时,拿到的就是咱们实现的适配类了。ide

做用二:@Adaptive还能够标记在接口的方法上。这种状况是,当ExtensionLoader扫描了全部实现类以后,发现没有一个是标记了@Adaptive的实现类。因而ExtensionLoader会使用javasist帮咱们自动生成一个设配类。在自动实现各个接口的方式时,会根据该方法上标记的@Adaptive注解的value[]数组值来进行生成。若是没有标记@Adaptive的就抛出异常。例如上面的Protocol自动实现的类反编译后长这样:优化

public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
    //没有打上@Adaptive的方法若是被调到抛异常
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void 
        com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not 
        adaptive method!")
    }
    
    //接口中export方法打上@Adaptive注册
    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws 
                                                              com.alibaba.dubbo.rpc.Invoker
    {
        if (arg0 == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        //参数类中要有URL属性
        if(arg0.getUrl() == null) 
            throw newIllegalArgumentException( "com.alibaba.dubbo.rpc.Invoker argument 
            getUrl() == null");
            
        //从入参获取统一数据模型URL
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
            
        //从统一数据模型URL获取协议,协议名就是SPI扩展点实现类的key
        if(extName == null) 
            throw new IllegalStateException( "Fail to 
            getextension(com.alibaba.dubbo.rpc.Protocol) name from url("  + url.toString() + 
            ") usekeys([protocol])");
          
        //利用dubbo服务查找机制根据名称找到具体的扩展点实现
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.
        getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
    }
    //其余方法省略...
}

自动实现的设配代码大体意思是,若是方法上没有标记@Adaptive注解的,就抛出一个异常,若是有标记@Adaptive注解的,就判断接口方法有没有URL参数,若是没有就找依附在参数中的URL,而后根据@Adaptive指定的value[]数组,合成代码,代码意思大体就是在使用这个适配类时,会依次使用value[]指定的实现,若是都找不到就最后使用@SPI指定的默认实现。url

四、@Activate

关于这个注解,我简单说一下。当咱们实现扩展时,若是加上这个注解。而后经过这个注解能够配置一些限制条件。当系统查找扩展实现会把标记了这个注解的实现缓存起来。而后能够在适当时候,过滤出要激活的实现。@Activate注解源码:spa

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
    //group过滤条件,没有group就不过滤
    String[] group() default {};
    //key过滤条件,如没有设置,则不过滤
    String[] value() default {};
    //排序信息,能够不提供
    String[] before() default {};
    //排序信息,能够不提供
    String[] after() default {};
    //排序信息,能够不提供
    int order() default 0;
}

例如,源码中有个CacheFilter的,就使用了这个注解。插件

@Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY)
public class CacheFilter implements Filter {
    //省略...
}

当使用系统要使用时,这样调用就能够过滤出要被激活的实现设计

ExtensionLoader.getExtensionLoader(Filter.class)
                .getActivateExtension(url, new String[]{}, "value");

五、ExtensionLoader扫描缓存扩展实现流程说明

相关文章
相关标签/搜索