首页 > 其他 > 详细

dubbo

时间:2020-03-30 09:26:51      阅读:72      评论:0      收藏:0      [点我收藏+]

dubbo扩展机制spi

dubbo扩展机制spi

dubbo模块之间都是基于接口调用,dubbo扩展机制就是通过接口寻找具体实现类的过程。
如果一个扩展接口未指定扩展类,则选择默认的key为dubbo,如下图:
技术分享图片
源码位置:dubbo-common模块下com.alibaba.dubbo.common.extension。

注解@SPI

加在接口上,表明接口为可扩展接口。

注解@Adaptive(适配器)

通过接口找到实现类过程,中间还要经历适配器,该注解用来定义适配器。
当加在类上的时候,表明该类为适配器类。
当加在方法上的时候,dubbo会动态生成适配器类。

注解@Activate

主要用于扩展Filter和Listener。

接口ExtensionFactory(扩展类实例工厂)

他自己也是一个扩展接口,分别是SpiExtensionFactory和SpringExtensionFactory代表着两种不同方式去获取实例,而具体选择哪种方式去获取实现类的实例,则在适配器AdaptiveExtensionFactory中制定了规则。

类ExtensionLoader(扩展加载器)

核心类,所有核心逻辑都在该类中实现。接下来开始分析这个类。

存放扩展相关配置文件的路径

private static final String SERVICES_DIRECTORY = "META-INF/services/";
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

META-INF/services/:dubbo兼容jdk spi的文件路径
META-INF/dubbo/:提供给用户自己扩展的配置文件的路径
META-INF/dubbo/internal/:dubbo自身内部配置文件路径
比如ExtensionFactory接口的配置文件路径如下图:扩展接口的路径 + 上边定义的路径
技术分享图片

扩展加载器集合
key为扩展接口

private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

扩展实现类集合
key为扩展实现类,value为扩展对象,例如key为Class,value为DubboProtocol对象

private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();

以下属性都是cache开头的
都是出于性能和资源的优化,才做的缓存,读取扩展配置后,会先进行缓存,等到真正需要用到某个实现时,再对该实现类的对象进行初始化,然后对该对象也进行缓存。

//缓存的扩展名与扩展类映射,和cachedClasses的key和value对换。
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();

    //缓存的扩展实现类集合
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();

    //扩展名与加有@Activate的自动激活类的映射
    private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();

    //缓存的扩展对象集合,key为扩展名,value为扩展对象
    //例如Protocol扩展,key为dubbo,value为DubboProcotol
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();

    //缓存的自适应( Adaptive )扩展对象,例如例如AdaptiveExtensionFactory类的对象
    private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();

    //缓存的自适应扩展对象的类,例如AdaptiveExtensionFactory类
    private volatile Class<?> cachedAdaptiveClass = null;

    //缓存的默认扩展名,就是@SPI中设置的值
    private String cachedDefaultName;

    //创建cachedAdaptiveInstance异常
    private volatile Throwable createAdaptiveInstanceError;

    //拓展Wrapper实现类集合
    private Set<Class<?>> cachedWrapperClasses;

    //拓展名与加载对应拓展类发生的异常的映射
    private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();

这里提到了Wrapper类的概念。那我就解释一下:Wrapper类也实现了扩展接口,但是Wrapper类的用途是ExtensionLoader 返回扩展点时,包装在真正的扩展点实现外,这实现了扩展点自动包装的特性。通俗点说,就是一个接口有很多的实现类,这些实现类会有一些公共的逻辑,如果在每个实现类写一遍这个公共逻辑,那么代码就会重复,所以增加了这个Wrapper类来包装,把公共逻辑写到Wrapper类中,有点类似AOP切面编程思想。

到现在已经生成了一个适配器类(可以把适配器类理解成代理类),接下来调用调用目标方法的时候,适配器类会进行拦截,根据运行时的参数来决定生成哪个实现类,最后在调用生成的实现类的目标方法.

以扩展接口Transporter举个例子,该接口动态生成适配器类,适配器类如下:
Transporter类

@SPI("netty")
public interface Transporter {

    /**
     * Bind a server.
     *
     * @param url     server url
     * @param handler
     * @return server
     * @throws RemotingException
     * @see com.alibaba.dubbo.remoting.Transporters#bind(URL, ChannelHandler...)
     */
    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;

    /**
     * Connect to a server.
     *
     * @param url     server url
     * @param handler
     * @return client
     * @throws RemotingException
     * @see com.alibaba.dubbo.remoting.Transporters#connect(URL, ChannelHandler...)
     */
    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;

}

Transporter适配器类

public class Transporter$Adaptive implements com.alibaba.dubbo.remoting.Transporter{
    
    public com.alibaba.dubbo.remoting.Client connect(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
        //URL参数为空则抛出异常。
        if (arg0 == null) 
            throw new IllegalArgumentException("url == null");
        
        com.alibaba.dubbo.common.URL url = arg0;
        //这里的getParameter方法可以在源码中具体查看
        String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])");
        //这里我在后面会有详细介绍
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
        
        (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        return extension.connect(arg0, arg1);
    }
    public com.alibaba.dubbo.remoting.Server bind(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
        if (arg0 == null) 
            throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("server", url.getParameter("transporter", "netty"));
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
        (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        
        return extension.bind(arg0, arg1);
    }
}

1、所有扩展点都通过传递URL携带配置信息,所以适配器中的方法必须携带URL参数,才能根据URL中的配置来选择对应的扩展实现。
2、@Adaptive注解中有一些key值,比如connect方法的注解中有两个key,分别为“client”和“transporter”,URL会首先去取client对应的value来作为我上述(一)注解@SPI中写到的key值,如果为空,则去取transporter对应的value,如果还是为空,则会根据SPI默认的key,也就是netty去调用扩展的实现类,如果@SPI没有设定默认值,则会抛出IllegalStateException异常。

具体调用链路如下图:删除部分节点,方便阅读
技术分享图片

返回顶部

dubbo

原文:https://www.cnblogs.com/yanhui007/p/12596071.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!