Dubbo源码分析系列---扩展点加载

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dubbo源码分析系列---扩展点加载相关的知识,希望对你有一定的参考价值。

扩展点配置:

约定:

在扩展类的jar包内,放置扩展点配置文件:META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。(摘自dubbo文档)

示例:

假如我现在想使用自己定义的协议Myprotocol,在resources目录下新建META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol目录文件,文件内容定义:

myprotocol=com.selrain.MyProtocol

实现类内容:

public class MyProtocol implements Protocol {
    @Override
    public int getDefaultPort() {
        return 1111111;
    }
   ...
}

在我们的xml配置protocol属性时就可以配置myprotocol啦,现在为了测试我们手动获取下:

@SpringBootApplication
public class Chapter2Application {

    public static void main(String[] args) {
        SpringApplication.run(Chapter2Application.class, args);

        Protocol p= ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myprotocol");
        System.out.print(p.getDefaultPort());
    }
}

住:为了少写点代码直接上spring boot了

看看自己的控制台输出吧,是不是调用了我们自定义的类了?

 

扩展点自动包装:

自动Wrap扩展点的Wrapper类
ExtensionLoader会把加载扩展点时(通过扩展点配置文件中内容),如果该实现有拷贝构造函数,则判定为扩展点Wrapper类。

Wrapper类同样实现了扩展点接口(摘自dubbo文档)

什么意思呢?比方说Dubbo里面定义了ProtocolListenerWrapper、ProtocolFilterWrapper2各Wrapper类:

public class ProtocolListenerWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolListenerWrapper(Protocol protocol){
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }
    ...

    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
            return protocol.export(invoker);
        }
        return new ListenerExporterWrapper<T>(protocol.export(invoker), 
                Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
                        .getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
    }

    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            return protocol.refer(type, url);
        }
        return new ListenerInvokerWrapper<T>(protocol.refer(type, url), 
                Collections.unmodifiableList(
                        ExtensionLoader.getExtensionLoader(InvokerListener.class)
                        .getActivateExtension(url, Constants.INVOKER_LISTENER_KEY)));
    }
   ...

}

public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolFilterWrapper(Protocol protocol){
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }

    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
       ...
        return protocol.export(invoker);
    }

    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
       ...
       return protocol.refer(type, url);
    }

    public void destroy() {
        protocol.destroy();
    }

   ...
    
}

2个Wrapper类共同点就是都实现了Protocol接口,构造函数也都时Protocol参数,这样做的好处是可以通过层层包装来使各个类的逻辑处理分开;

ProtocolListenerWrapper===》ProtocolFilterWrapper===》DubboProtocol,Protocol的调用过程就是这样的。

现在来测试一下:

@SpringBootApplication
public class Chapter2Application {

    public static void main(String[] args) {
        SpringApplication.run(Chapter2Application.class, args);

        Protocol p= ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myprotocol");
        System.out.print(p.getClass().getSimpleName());
    }
}

控制台输出了ProtocolListenerWrapper,也就是说当我们调用myprotocol的方法时,会先经过层层包装,处理,最后才调用myprotocol的方法,当然这个Wrapper我们自己也可以定义,当我们需要额外的做一些逻辑处理的时候,和上面的扩展点配置一样。

扩展点自动装配:

加载扩展点时,扩展点实现类的成员如果为其它扩展点类型,ExtensionLoader在会自动注入依赖的扩展点

对应文章底部图中的injectExtension方法

private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                method.invoke(instance, object);
                            }
                        ...
    }

objectFactory 也是基于dubbo的spi扩展机制获取,它有3个实现类,分别是AdaptiveExtensionFactory、SpiExtensionFactory、SpringExtensionFactory

objectFactory 在我们初始化ExtensionLoader类的时候已经初始化:(因为AdaptiveExtensionFactory打上了Adaptive注解,所以值为AdaptiveExtensionFactory)

private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

AdaptiveExtensionFactory持有所有ExtensionFactory对象的集合,dubbo内部默认实现的对象工厂是SpiExtensionFactory和SpringExtensionFactory,他们经过TreeMap排好序的查找顺序是优先先从SpiExtensionFactory获取,如果返回空在从SpringExtensionFactory获取。

1) SpiExtensionFactory工厂获取要被注入的对象,就是要获取dubbo spi扩展的实现,所以传入的参数类型必须是接口类型并且接口上打上了@SPI注解,返回的是一个设配类对象。

public class SpiExtensionFactory implements ExtensionFactory {

    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (loader.getSupportedExtensions().size() > 0) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

}

 2)SpringExtensionFactory,Dubbo利用spring的扩展机制跟spring做了很好的融合。在发布或者去引用一个服务的时候,会把spring的容器添加到SpringExtensionFactory工厂集合中去, 当SpiExtensionFactory没有获取到对象的时候会遍历SpringExtensionFactory中的spring容器来获取要注入的对象

public class SpringExtensionFactory implements ExtensionFactory {
    ...

    @SuppressWarnings("unchecked")
    public <T> T getExtension(Class<T> type, String name) {
        for (ApplicationContext context : contexts) {
            if (context.containsBean(name)) {
                Object bean = context.getBean(name);
                if (type.isInstance(bean)) {
                    return (T) bean;
                }
            }
        }
        return null;
    }

扩展点自适应

扩展点的Adaptive实例
ExtensionLoader注入的依赖扩展点是一个Adaptive实例,直到扩展点方法执行时才决定调用是一个扩展点实现。
Dubbo使用URL对象(包含了Key-Value)传递配置信息。
扩展点方法调用会有URL参数(或是参数有URL成员)
这样依赖的扩展点也可以从URL拿到配置信息,所有的扩展点自己定好配置的Key后,配置信息从URL上从最外层传入。URL在配置传递上即是一条总线(摘自Dubbo官方文档)

比如我们的Protocol接口,由于没有实现类打上Adaptive注解,所以生成javassist字节码的方式创建的实现类:

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {


public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
    if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
    if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl(); String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.export(arg0); } public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException { if (arg1 == null) throw new IllegalArgumentException("url == null"); com.alibaba.dubbo.common.URL url = arg1; String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.refer(arg0, arg1); } }

可以看到通过url传播,在url中拿到最终扩展点名字,最终调用扩展点的export方法

 

内部实现(重要方法):

技术分享

 

参考:

http://blog.csdn.net/quhongwei_zhanqiu/article/details/41577235

 

以上是关于Dubbo源码分析系列---扩展点加载的主要内容,如果未能解决你的问题,请参考以下文章

Dubbo底层源码分析之SPI扩展点

dubbo 源码 v2.7 分析:核心机制

02.dubbo源码解析之Dubbo扩展点加载

DUBBO源码-自适应扩展机制

Dubbo源码分析系列-扩展机制的实现

dubbo源码分析之过滤器Filter-12