zoukankan      html  css  js  c++  java
  • 重学Dubbo——SPI机制

    关于Dubbo的一些概念

    服务暴露,为某个服务创建一个中转对象(能接触到网络/能调用到本地 service)来接受网络请求,外部系统把请求的目标/方法/参数发送到中转对象,中转对象就能执行方法并返回结果到网络;

     

    服务引入,找寻到中转对象,创建一个代理对象,代理对象向中转对象传请求参数,等待返回值;

     

    在 Dubbo 的核心领域模型中:

    • Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理;

       

    • Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现;

     

    • Invocation 是会话域,它持有调用过程中的变量,比如方法名,参数等;

     

    在Dubbo中,服务,注册中心,消费者,配置信息,元数据(元数据信息包括服务接口,及接口的方法信息)都可用URL表示为资源;Dubbo中URL 作为配置信息的统一格式,URL在Dubbo中被当作公共契约,所有扩展点都通过传递 URL 携带配置信息;

     

    关于Dubbo的架构设计[http://dubbo.apache.org/zh-cn/docs/dev/design.html]

     

    SPI

    SPI全称Service Provider Interface,是JDK提供的一套被第三方实现或扩展API的一种服务提供发现机制,为某个接口找寻接口的实现,动态替换的发现机制可用于框架扩展和替换组件;

        

    Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,调用者根据实际使用需要,启用、扩展、或者替换框架的实现策略

     

    • Java SPI

      Java的SPI实现类java.util.ServiceLoader

      

      java.util.ServiceLoader.LazyIterator#hasNextService

      

      Java的SPI会加载/META-INF/services/ + 接口的全路径名的文件,文件里面是该接口的实现类的全路径名;

    public class SPIServiceTest {
        public static void main(String[] args) {
            //获取所有的实现
            ServiceLoader<SPIService> serviceLoader = ServiceLoader.load(SPIService.class);
    
            Iterator<SPIService> iterator = serviceLoader.iterator();
            while (iterator.hasNext()) {
                //循环执行所有的实现
                SPIService spiService = iterator.next();
                spiService.doService();
            }
        }
    }
    

      

    • Dubbo SPI

      官方SPI的讲解 [http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html]

      Dubbo版本为 2.6.2

     

      Dubbo 的扩展机制和Java的SPI机制非常相似,增加了如下功能:

      • 可以方便地获取某一个想要的扩展实现;

      • 对于扩展实现增加了IOCAOP

      举例来说:

      接口A,实现类A1,A2

      接口B,实现类B1,B2

      实现类A1含有setB的方法,会自动注入一个接口B的实现类,此时注入的的是一个动态生成的接口B的实现类B$Adpative,该实现类能够根据参数的不同,自动引入B1或B2来完成相应的功能;

     

      在Dubbo中, 接口上有 @SPI标注的,都表明此接口支持扩展,用于定义默认实现类,比如Protocol接口定义有@SPI("dubbo")注解,默认调用的是DubboProtocol类;

      引用官方自适应扩展机制的讲解[http://dubbo.apache.org/zh-cn/docs/source_code_guide/adaptive-extension.html]

      在 Dubbo 中,很多拓展都是通过 SPI 机制进行加载的,比如 Protocol、Cluster、LoadBalance 等。有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载(如通过URL对象参数进行加载);当拓展未被加载,那么拓展方法就无法被调用(静态方法除外);拓展方法未被调用,拓展就无法被加载;对于这个矛盾的问题,Dubbo 通过自适应拓展机制(@Adaptive)很好的解决了;

     

      @Adaptive

      该注解一般使用在方法上,代表自动生成和编译一个动态的Adpative类,一般是没有人工的代理类的实现,需要依靠Dubbo自动生成代理类,而这个代理类所对应的实例在调用某个方法时,如果该方法被@Adaptive修饰,则会从URL中取值作为扩展点名去加载实现类并实例化,最后再使用这个实例调用相应的方法,用来指定从URL中的哪个Key中获取(它主要用于SPI,因为SPI的类是不固定的,未知的扩展类,所以设计了动态的$Adpative类);扩展未知类它设计了Porotocol$Adaptive的类,通过ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(SPI类) 来提取对象;

      如果该注解使用在类上,则代表一个适配类,不会生成代理类对象,用于固定已知类;

       

      com.alibaba.dubbo.common.extension.ExtensionLoader加载dubbo扩展点;

      Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactorySpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展;

      

        

      ExtensionFactory继承结构

      

      

    Jdk的SPI与 Dubbo 的SPI区别如下

      区别一

      Jdk的SPI会一次性实例化扩展点的所有实现(也就是每次都要要遍历所有的实现),如果有的扩展实现初始化是耗时的,但不需要用到,这会浪费资源;

      Dubbo 的 SPI对于实例化的扩展点增加了缓存,在缓存中使用指定的key就可以换取,如果缓存中没有该实例,就会创建一个实例并放入到缓存;当获取扩展点没有实例会创建实例并放入缓存,类似一种按需加载;

    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
    

      

      com.alibaba.dubbo.common.extension.ExtensionLoader#getExtension

      

      getExtensionLoader 方法用于从缓存中获取与拓展类对应的 ExtensionLoader,若缓存未命中,则调用createExtension方法创建一个新的实例;

     

      区别二

      Dubbo SPI 带有 IOCAOP 功能;

      AOP

      创建拓展对象

      com.alibaba.dubbo.common.extension.ExtensionLoader#createExtension

    private T createExtension(String name) {
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                //实例不存在,则以Class为key,实例化一个对象为value,存入map
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            //向实例进行依赖注入
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    //将wrapperClass实例替换原有的instance,将原有的instance实例作为参数传入构造方法
                    //这里是dubbo SPI的AOP,使用装饰者模式将原实例增强
                    //当有多个wrapper对象,上一次的实例会传入到本次构造方法的参数中,一层一层的包起来,执行构造方法
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                                            type + ")  could not be instantiated: " + t.getMessage(), t);
        }
    }
    

      

      关于扩展点加载的入口,从com.alibaba.dubbo.common.extension.ExtensionLoader#loadExtensionClasses这里看起,loadExtensionClasses方法在getExtensionClasses方法上同步;

      

      

      上面的extensionClasses是一个map,key是别名,value是具体的实现类,会从不同的地方寻找接口的所有实现类,这就是扩展的实现, 加载路径从上到下分别对应META-INF/dubbo/META-INF/dubbo/internal/META-INF/services/META-INF/dubbo/internal是Dubbo内部实现的各种扩展都放在这个目录,可以看出/META-INF/services/的优先级最高

     

      createExtension方法执行流程如下

    1. 通过 getExtensionClasses 获取所有的拓展类

    2. 通过反射创建拓展对象

    3. 向拓展对象中注入依赖

    4. 将拓展对象包裹在相应的 Wrapper 对象中(对应的是上面的instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));

      例子如下:

    • 创建一个扩展Dubbo SPI的接口

    @SPI
    public interface SPIServiceDemo {
        void run();
    }
    

      

    • 创建一个SPIServiceDemo的实现类
    public class SPIServiceDemoImpl implements SPIServiceDemo {
        private final Logger logger = LoggerFactory.getLogger(getClass());
    
        @Override
        public void run() {
            logger.info("自定义SPI run...");
        }
    }
    

      

    • META-INFdubbo目录下创建com.alibaba.dubbo.demo.SPIServiceDemo文件
    spiservicedemo=com.alibaba.dubbo.demo.spi.SPIServiceDemoImpl
    

      

    • 创建一个测试类
    public class SPIServiceDemoTest {
        public static void main(String[] args) {
            ExtensionLoader<SPIServiceDemo> loader = ExtensionLoader.getExtensionLoader(SPIServiceDemo.class);
            SPIServiceDemo serviceDemo = loader.getExtension("spiservicedemo");
            serviceDemo.run();
        }
    }
    

      

      打印如下:

      

      AOP切面增强,类似如下

    //环绕
    try {
        //前置
        ...
        //后置
    }catch (Exception e){
        //异常
    }finally {
        //最终
    }
    

      

      AOP的增强类型的使用场景大致如下:

    增强类型场景
    前置增强 权限控制、记录调用日志
    后置增强 统计分析结果数据
    异常增强 通过日志记录方法异常信息
    最终增强 释放资源
    环绕增强 缓存、性能、权限、事务管理

      如果要AOP增强,需要创建一个装饰类,之后会被isWrapperClass这个方法检查,判断是否有构造方法

    public class SPIServiceDemoWrapper implements SPIServiceDemo {
        private final Logger logger = LoggerFactory.getLogger(getClass());
    
        private SPIServiceDemo spiServiceDemo;
    
        public SPIServiceDemoWrapper(SPIServiceDemo spiServiceDemo) {
            this.spiServiceDemo = spiServiceDemo;
        }
    
        @Override
        public void run() {
            logger.info("环绕增强 -- 1");
            try {
                logger.info("前置增强..");
                spiServiceDemo.run();
                logger.info("后置增强..");
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("异常增强..");
            } finally {
                logger.info("最终增强..");
                logger.info("环绕增强 -- 2");
            }
        }
    }
    

      

      并在com.alibaba.dubbo.demo.SPIServiceDemo文件添加如下:

    spiservicedemowrapper=com.alibaba.dubbo.demo.spi.SPIServiceDemoWrapper
    

      

      重新执行main方法,执行结果如下:

      

      当需要自适应扩展实现,可使用@Adaptive,使用@Adaptive需要传递 URL 携带配置信息;官方文档讲解[http://dubbo.apache.org/zh-cn/docs/source_code_guide/adaptive-extension.html]

      入口方法为getAdaptiveExtension,该方法会调用createAdaptiveExtension生成代理类,实现自适应;

      AdaptiveExtensionFactory实现ExtensionFactory接口,且用@Adaptive注解标注,在com.alibaba.dubbo.common.extension.ExtensionLoader#loadClass会检测注解,并赋值给cachedAdaptiveClass

    if (clazz.isAnnotationPresent(Adaptive.class)) {
        if (cachedAdaptiveClass == null) {
            cachedAdaptiveClass = clazz;
        } else if (!cachedAdaptiveClass.equals(clazz)) {
            throw new IllegalStateException("More than 1 adaptive class found: "
                                            + cachedAdaptiveClass.getClass().getName()
                                            + ", " + clazz.getClass().getName());
        }
    }
    

      

      之后在com.alibaba.dubbo.common.extension.ExtensionLoader#createAdaptiveExtension会创建AdaptiveExtensionFactory实例;

    private T createAdaptiveExtension() {
        try {
            //getAdaptiveExtensionClass获取代理类
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }
    

      

      com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory#AdaptiveExtensionFactory

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        //存储ExtensionFactory所有的实现类
        factories = Collections.unmodifiableList(list);
    }
    

      

      createAdaptiveExtension会调用getAdaptiveExtensionClass 方法,当调用createAdaptiveExtensionClass方法会检测注解,并解析URL的配置信息;

    private Class<?> getAdaptiveExtensionClass() {
        // 通过 SPI 获取所有的拓展类
        getExtensionClasses();
        // 检查缓存,若缓存不为空,则直接返回缓存
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        // 创建自适应拓展类
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
    

      

      将上面测试接口修改如下:

    @SPI
    public interface SPIServiceDemo {
        @Adaptive({"k1"})
        void run(URL url);
    }
    

      

      修改实现类

    public class SPIServiceDemoImpl implements SPIServiceDemo {
        private final Logger logger = LoggerFactory.getLogger(getClass());
    
        @Override
        public void run(URL url) {
            logger.info("url:" + url + "自定义SPI run...");
        }
    }
    

      

    public class SPIServiceDemoImpl2 implements SPIServiceDemo {
        private final Logger logger = LoggerFactory.getLogger(getClass());
    
        @Override
        public void run(URL url) {
            logger.info("url:" + url + "自定义SPI2 run...");
        }
    }
    

      

      测试类  

    public class SPIServiceDemoTest {
        public static void main(String[] args) {
            ExtensionLoader<SPIServiceDemo> loader = ExtensionLoader.getExtensionLoader(SPIServiceDemo.class);
            SPIServiceDemo adaptiveExtension = loader.getAdaptiveExtension();
    
            Map<String, String> map = new HashMap<String, String>();
            map.put("k1", "spiservicedemo2");
            URL url = new URL("test", "127.0.0.1", 1, map);
            adaptiveExtension.run(url);
        }
    }
    

      

      打印如下:

      

      IOC

      Dubbo SPI的 IOC 是通过 setter方法注入依赖,Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter方法特征。若有,则通过 objectFactory 获取代理对象,最后通过反射调用 setter 方法将依赖设置到目标对象中;

    private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                // 遍历目标类的所有方法
                for (Method method : instance.getClass().getMethods()) {
                    // 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
                    if (method.getName().startsWith("set")
                        && method.getParameterTypes().length == 1
                        && Modifier.isPublic(method.getModifiers())) {
                        // 获取 setter 方法参数类型
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            // 获取属性名,比如 setName 方法对应属性名 name
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            // pt为注入的代理对象,从 ObjectFactory 中获取代理对象
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                // 通过反射调用 setter 方法设置依赖
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                         + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }
    

      

      对于objectFactory 为SpiExtensionFactory类型,可注入的对象只能为接口类型且接口带有@SPI标注

      

      上面的objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory;

      

      com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory#getExtension 返回一个待注入的代理对象

    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
            //factory为ExtensionFactory的实现类,getExtension返回一个待注入的代理对象
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }
    

      

      org.apache.dubbo.common.extension.ExtensionLoader#createAdaptiveExtensionClass动态生成编译类,注:2.7+版本将生成代理类的封装到org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator#AdaptiveClassCodeGenerator,之前都是在该方法进行逻辑处理;

    private Class<?> createAdaptiveExtensionClass() {
        // 生成代理类的代码
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        ClassLoader classLoader = findClassLoader();
    	// 动态生成编译类
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }
    

      

      

      Compiler是SPI接口类,通过ExtensionLoader进行加载;

      org.apache.dubbo.common.compiler.Compiler的内容如下:

    adaptive=com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler
    jdk=com.alibaba.dubbo.common.compiler.support.JdkCompiler
    javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler
    

      

      Compiler接口默认的实现类为JavassistCompiler

    @SPI("javassist")
    public interface Compiler {
    
        /**
         * Compile java source code.
         *
         * @param code        Java source code
         * @param classLoader classloader
         * @return Compiled class
         */
        Class<?> compile(String code, ClassLoader classLoader);
    
    }
    

       

       

       这三个Compiler使用JavassistCompiler作为Compiler接口的实现类,但在AdaptiveCompiler类定义上面有一个@Adaptive注解,表示是一个装饰模式的类,AdaptiveCompiler起包装作用,在里面获取当前的实现类JavassistCompiler,然后执行compiler方法产生默认的自适应扩展类(代理类);

      ExtensionLoader大致流程如下:

      

  • 相关阅读:
    认证功能装饰器
    装饰器升级版
    装饰器
    闭包函数
    名称空间与作用域
    嵌套函数
    函数对象
    命名关键字参数
    函数单数的使用
    函数的定义与调用
  • 原文地址:https://www.cnblogs.com/coder-zyc/p/13200603.html
Copyright © 2011-2022 走看看