zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    C#.NET常见问题(FAQ)-如何给Listbox添加右键菜单
    C#.NET常见问题(FAQ)-如何捕捉窗体关闭的事件,弹窗确认是否退出
    C#.NET常见问题(FAQ)-控制台程序如何输出Messagebox
    C#.NET常见问题(FAQ)-TabControl如何隐藏和显示页面
    C#.NET常见问题(FAQ)-SplitPanel如何设置上下和左右
    Oracle 存储过程
    Oracle的存储过程
    Oracle通用分页存储过程的创建与使用
    winform窗体间传值
    多线程下访问控件的方式
  • 原文地址:https://www.cnblogs.com/Non-Tecnology/p/6882903.html
Copyright © 2011-2022 走看看