Dubbo SPI 机制基本操作与源码深入理解

从上一篇文章java spi的原理中可以了解到,java的spi机制有着如下的弊端:

  • 只能遍历所有的实现,并全部实例化。
  • 配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们。
  • 扩展如果依赖其他的扩展,做不到自动注入和装配。
  • 扩展很难和其他的框架集成,比如扩展里面依赖了一个Spring bean,原生的Java SPI不支持。

想了解JAVA SPI 的基本思想和应用可以参考:

JAVA SPI机制深入详解

我们都是知道一个合格的开源框架对于扩展的支持都要是相当弹性的,Dubbo 也不例外。Dubbo采用了简单的扩展方式,基于SPI机制。但是自己另外实现了一套,对以下几点进行了改进

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。

  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。

  • 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。

基本上讲到这里大家对于SPI可能有个大致的认识,但是要真正理解Dubbo的SPI,还是要仔细看一下源码才可以。

在理解Dubbo的SPI之前,要明确几个核心概念

    (1)扩展点:一个接口。

Dubbo作用灵活的框架,并不会强制所有用户都一定使用Dubbo提供的某些架构。例如注册中心(Registry),Dubbo提供了zk和redis,但是如果我们更倾向于其他的注册中心的话,我们可以替换掉Dubbo提供的注册中心。针对这种可被替换的技术实现点我们称之为扩展点,类似的扩展点有很多,例如Protocol,Filter,Loadbalance等等。

    (2)扩展:扩展(接口)的实现。

Dubbo在加载某个接口的扩展类时候,如果某个实现中有一个拷贝类构造函数,那么该接口实现就是该接口的包装类,此时Dubbo会在真正的实现类上层包装上盖Wrapper。即这个时候从ExtensionLoader中返回的实际扩展类是被Wrapper包装的接口实现类

    (3)扩展自适应实例:其实就是一个Extension的代理

它实现了扩展点接口。在调用扩展点的接口方法时,会根据实际的参数来决定要使用哪个扩展。dubbo会根据接口中的参数,自动地决定选择哪个实现。

    (4)@SPI:该注解作用于扩展点的接口上,表明该接口是一个扩展点。

    (5)@Adaptive:@Adaptive注解用在扩展接口的方法上。

表示该方法是一个自适应方法。Dubbo在为扩展点生成自适应实例时,如果方法有@Adaptive注解,会为该方法生成对应的代码。

这个自适应的扩展点比较难理解,所以这里直接以一个例子来讲解:在RegistryProtocol中有一个属性为Cluster,其中Protocol和Cluster都是Dubbo提供的扩展点,所以这时候当我们真正在操作中使用cluster的时候究竟使用的哪一个cluster的实现类呢?是FailbackCluster还是FailoverCluster?Dubbo在加载一个扩展点的时候如果发现其成员变量也是一个扩展点并且有相关的set方法,就会在这时候将该扩展点设置为一个自适应的扩展点,自适应扩展点(Adaptive)会在真正使用的时候从URL中获取相关参数,来调用真正的扩展点实现类。具体的实现会在下面的源码中详细解释。对于Adaptive的理解其实个人推荐的是Dubbo开发者指南,指南中有对于Adaptive的明确介绍。

     (6)Activate 官网的叫法是自激活,其实这个更合适的叫法个人认为是条件激活。

Activate的作用主要是:提供一种选择性激活的条件,可以是我们通过相关的配置来确定激活哪些功能。

     (7)ExtensionLoader,是dubbo的SPI机制的查找服务实现的工具类,类似与Java的ServiceLoader,可做类比。dubbo约定扩展点配置文件放在classpath下的/META-INF/dubbo,/META-INF/dubbo/internal,/META-INF/services目录下,配置文件名为接口的全限定名,配置文件内容为配置名=扩展实现类的全限定名。

说了这么多,我们简单先操作一把,看下代码如果写

pom文件中包依赖

<!-- https://mvnrepository.com/artifact/com.alibaba/dubbo -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>dubbo</artifactId>
      <version>2.6.8</version>
    </dependency>

接口

package org.alexsotob.dubbo.SPI;

import com.alibaba.dubbo.common.extension.SPI;

@SPI
public interface Animal {

    String getName();

}

实现类Dog

package org.alexsotob.dubbo.SPI.impl;

import org.alexsotob.dubbo.SPI.Animal;

public class Dog implements Animal {
    public Dog() {
        System.out.println("dog init !!!");
    }

    @Override
    public String getName() {
        return "I am dog";
    }
}

实现类cat

package org.alexsotob.dubbo.SPI.impl;

import org.alexsotob.dubbo.SPI.Animal;

public class Cat implements Animal {
    public Cat() {
        System.out.println("cat init !!!");
    }

    @Override
    public String getName() {
        return "I am cat";
    }
}

重要的一步,ExtensionLoader,是dubbo的SPI机制的查找服务实现的工具类,类似与Java的ServiceLoader,可做类比。dubbo约定扩展点配置文件放在classpath下的/META-INF/dubbo,/META-INF/dubbo/internal,/META-INF/services目录下,配置文件名为接口的全限定名,配置文件内容为配置名=扩展实现类的全限定名。

配置的格式与java原生的不一样,类似于property文件的格式: 

cat=org.alexsotob.dubbo.SPI.impl.Cat
dog=org.alexsotob.dubbo.SPI.impl.Dog

编写测试类 入口为ExtensionLoader

package org.alexsotob.dubbo.SPI;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class DubboSPITest {

    public static void main(String[] args) {
        ExtensionLoader<Animal> carExtensionLoader = ExtensionLoader.getExtensionLoader(Animal.class);        //按需获取实现类对象
        Animal animal = carExtensionLoader.getExtension("cat");
        System.out.println(animal.getName());
    }

}

这里只是一个简单的本地Demo,用于介绍dubbo SPI思想,dubbo还实现了与Spring IOC 进行兼容的等其他功能

基本流程如下,记住它,后面源码分析思路才能比较清晰:

下面将进行源码分析: 

在dubbo SPI示例方法中,我们首先通过 ExtensionLoader的  getExtensionLoader 方法获取一个接口的  ExtensionLoader 实例,然后再通过  ExtensionLoader 的  getExtension 方法获取拓展类对象,源码如下,首先是 getExtensionLoader 方法:

 /**
     * 扩展类加载器缓存,就是扩展点ExtendsLoader实例缓存; key=扩展接口 value=扩展类加载器
     */
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();

    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {        
        //校验传进的type类是否为空
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        }        
        //校验传进的type类是否为接口
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
        }        
        //校验传进的type类是否有@SPI注解
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        }        
        //从ExtensionLoader缓存中查询是否已经存在对应类型的ExtensionLoader实例
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {            //没有就new一个ExtensionLoader实例,并存入本地缓存
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

getExtensionLoader会对传进的接口进行校验,其中包括是否有 @SPI注解校验,这也是在接口上需加 @SPI的原因。然后从 EXTENSION_LOADERS缓存中获取该接口类型的 ExtensionLoader,如果获取不到,则创建一个该接口类型的 ExtensionLoader放入到缓存中,并返回该 ExtensionLoader

注意这里创建 ExtensionLoader对象的构造方法如下:ExtensionLoader.getExtensionLoader获取ExtensionFactory接口的拓展类,再通过 getAdaptiveExtension从拓展类中获取目标拓展类。它会设置该接口对应的  objectFactory常量为 AdaptiveExtensionFactory。因为 AdaptiveExtensionFactory类上加了@Adaptive注解,为什么是 AdaptiveExtensionFactory原因在之后的文章会解释,且 objectFactory也会在后面用到。

private ExtensionLoader(Class<?> type) {
        this.type = type;        
        //type通常不为ExtensionFactory类,
        // 则objectFactory为ExtensionFactory接口的默认扩展类AdaptiveExtensionFactory
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

 当通过 ExtensionLoader.getExtensionLoader取到接口的加载器Loader之后,在通过 getExtension方法获取需要拓展类对象。

 /**
     * 扩展点实例缓存 key=扩展点名称,value=扩展实例的Holder实例
     */
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();

    /**
     * 获取接口拓展类实例
     * 1.检查缓存中是否存在
     * 2.创建并返回拓展类实例
     *
     * @param name 需要获取的配置文件中拓展类的key
     * @return
     */
    public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {            
            // 获取默认的拓展实现类,即@SPI注解上的默认实现类, 如@SPI("benz")
            return getDefaultExtension();
        }        
        // Holder,顾名思义,用于持有目标对象,从缓存中拿,没有则创建
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();        // 双重检查
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {                   
                    // 创建拓展实例
                    instance = createExtension(name);                    
                    // 设置实例到 holder 中
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

    /**
     * 获取或者创建一个Holder对象
     */
    private Holder<Object> getOrCreateHolder(String name) {        
        // 首先通过扩展名从扩展实例缓存中获取Holder对象
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {            
            //如果没有获取到就new一个空的Holder实例存入缓存
            cachedInstances.putIfAbsent(name, new Holder<>());
            holder = cachedInstances.get(name);
        }
        return holder;
    }

上面代码的逻辑比较简单,首先检查缓存,缓存未命中则创建拓展对象。dubbo中包含了大量的扩展点缓存。这个就是典型的使用空间换时间的做法。也是Dubbo性能强劲的原因之一,包括

  1. 扩展点Class缓存 ,Dubbo SPI在获取扩展点时,会优先从缓存中读取,如果缓存中不存在则加载配置文件,根据配置将Class缓存到内存中,并不会直接初始化。
  2. 扩展点实例缓存 ,Dubbo不仅会缓存Class,还会缓存Class的实例。每次取实例的时候会优先从缓存中取,取不到则从配置中加载,实例化并缓存到内存中。

下面我们来看一下创建拓展对象的过程

/**
     * 扩展实例存入内存中缓存起来; key=扩展类 ; value=扩展类实例
     */
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();

    /**
     * 创建拓展类实例,包含如下步骤
     * 1. 通过 getExtensionClasses 获取所有的拓展类,从配置文件加载获取拓展类的map映射
     * 2. 通过反射创建拓展对象
     * 3. 向拓展对象中注入依赖(IOC)
     * 4. 将拓展对象包裹在相应的 Wrapper 对象中(AOP)
     *
     * @param name 需要获取的配置文件中拓展类的key
     * @return 拓展类实例
     */
    private T createExtension(String name) {       
        // 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的map,
        // 再根据拓展项名称从map中取出相应的拓展类即可
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {            
            //从扩展点缓存中获取对应实例对象
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {                
                // //如果缓存中不存在此类的扩展点,就通过反射创建实例,并存入缓存
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());                
                //然后从缓存中获取对应实例
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }            
            // 向实例中注入依赖,通过setter方法自动注入对应的属性实例
            injectExtension(instance);            
            //从缓存中取出所有的包装类,形成包装链
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {               
                // 循环创建 Wrapper 实例,形成Wrapper包装链
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }            
            //初始化实例并返回
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException(".....");
        }
    }

创建拓展类对象步骤分别为:

  1. 通过 getExtensionClasses 从配置文件中加载所有的拓展类,再通过名称获取目标拓展类
  2. 通过反射创建拓展对象
  3. 向拓展对象中注入依赖
  4. 将拓展对象包裹在相应的 Wrapper 对象中

第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。我们先重点分析getExtensionClasses 方法的逻辑 getExtensionClasses 方法的逻辑。

从配置文件中加载所有的拓展类

  • 在通过name获取拓展类之前,首先需要根据配置文件解析出拓展项名称与拓展类的映射map,之后再根据拓展项名称从map中取出相应的拓展类即可。 getExtensionClasses 方法源码如下 

/**
     * 扩展点Class缓存 key=扩展名 ,value=对应的class对象
     */
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

    /**
     * 解析配置文件中接口的拓展项名称与拓展类的映射表map
     *
     * @return
     */
    private Map<String, Class<?>> getExtensionClasses() {
        // 从缓存中获取已加载的拓展点class
        Map<String, Class<?>> classes = cachedClasses.get();
        //双重检查
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // 加载拓展类
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

这里也是先检查缓存,若缓存未命中,则通过 loadExtensionClasses 加载拓展类,缓存避免了多次读取配置文件的耗时。下面分析 loadExtensionClasses方法加载配置文件的逻辑

/**
     * 三个dubbo SPI默认扫描的路径
     */
    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/";

    private Map<String, Class<?>> loadExtensionClasses() {      
        //获取并缓存接口的@SPI注解上的默认实现类,@SPI("value")中的value
        cacheDefaultExtensionName();
        Map<String, Class<?>> extensionClasses = new HashMap<>();      
        // 加载指定文件夹下的配置文件,常量包含META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/三个文件夹
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName(), true);      
        //兼容历史版本
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"), true);
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
    }

loadExtensionClasses 方法总共做了两件事情。首先该方法调用 cacheDefaultExtensionName对 SPI 注解进行解析,获取并缓存接口的 @SPI注解上的默认拓展类在 cachedDefaultName。再调用  loadDirectory 方法加载指定文件夹配置文件。

SPI 注解解析过程比较简单,源码如下。只允许一个默认拓展类。

 private void cacheDefaultExtensionName() {        
        // 获取 SPI 注解,这里的 type 变量是在调用 getExtensionLoader 方法时传入,代表接口类
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation == null) {
            return;
        }
        String value = defaultAnnotation.value();
        if ((value = value.trim()).length() > 0) {
            String[] names = NAME_SEPARATOR.split(value);            
            // 检测 SPI 注解内容是否合法(至多一个默认实现类),不合法则抛出异常
            if (names.length > 1) {
                throw new IllegalStateException("...");
            }            
            // 设置默认拓展类名称
            if (names.length == 1) {
                cachedDefaultName = names[0];
            }
        }
    }

 从源码中可以看出 loadExtensionClasses方法加载配置文件的路径有3个,分别为 META-INF/dubbo/internal/, META-INF/dubbo/, META-INF/services/三个文件夹。方法源码如下: 

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        loadDirectory(extensionClasses, dir, type, false);
    }

    /**
     * 加载配置文件内容
     *
     * @param extensionClasses                拓展类map
     * @param dir                             文件夹路径
     * @param type                            接口名称
     * @param extensionLoaderClassLoaderFirst 是否先加载ExtensionLoader的ClassLoader
     */
    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst) {       
        // fileName = 文件夹路径 + type 全限定名
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls = null;
            //获取当前线程的类加载器
            ClassLoader classLoader = findClassLoader();
            // try to load from ExtensionLoader's ClassLoader first
            if (extensionLoaderClassLoaderFirst) {
                //获取加载ExtensionLoader.class这个类的类加载器
                ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader(); 
                //如果extensionLoaderClassLoaderFirst=true时,且这两个类加载器不同,就优先使用 extensionLoaderClassLoader
                if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
                    urls = extensionLoaderClassLoader.getResources(fileName);
                }
            }
            // 根据文件名加载所有的同名文件
            if (urls == null || !urls.hasMoreElements()) {
                if (classLoader != null) {
                    urls = classLoader.getResources(fileName);
                } else {
                    urls = ClassLoader.getSystemResources(fileName);
                }
            }
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    // 解析并加载配置文件中配置的实现类到extensionClasses中去
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error(""). ", t);
        }
    }

首先找到文件夹下的配置文件,文件名需为接口全限定名。利用类加载器获取文件资源链接,再解析配置文件中配置的实现类添加到 extensionClasses中。我们继续看loadResource是如何加载资源的。

 private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        try {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
                String line;                
                // 按行读取配置内容
                while ((line = reader.readLine()) != null) {
                    final int ci = line.indexOf('#');
                    if (ci >= 0) {                        
                        // 截取 # 之前的字符串,# 之后的内容为注释,需要忽略
                        line = line.substring(0, ci);
                    }
                    line = line.trim();
                    if (line.length() > 0) {
                        try {
                            String name = null;
                            int i = line.indexOf('=');
                            if (i > 0) {                                
                                // 以等于号 = 为界,截取键与值
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {                                
                                // 通过反射加载类,并通过 loadClass 方法对类进行缓存
                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                            }
                        } catch (Throwable t) {
                            .....
                        }
                    }
                }
            }
        } catch (Throwable t) {
            logger.error(....);
        }
    }

loadResource 方法用于读取和解析配置文件,按行读取配置文件,每行以等于号 = 为界,截取键与值,并通过反射加载类,最后通过 loadClass方法加载扩展点实现类的class到map中,并对加载到的class进行分类缓存。 loadClass方法实现如下

/**
     * 加载扩展点实现类的class到map中,并对加载到的class进行分类缓存
     * 比如 cachedAdaptiveClass、cachedWrapperClasses 和 cachedNames 等等
     *
     * @param extensionClasses 装载配置文件类的容器
     * @param resourceURL      配置文件资源URL
     * @param clazz            扩展点实现类的class
     * @param name             扩展点实现类的名称,配置文件一行中的key
     * @throws NoSuchMethodException
     */
    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        //判断配置的实现类是否是实现了type接口
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("...");
        }
        //根据配置中实现类的类型来分类缓存起来
        // 检测目标类上是否有 Adaptive 注解,表示这个类就是一个自适应实现类,缓存到cachedAdaptiveClass
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz);
            // 检测 clazz 是否是 Wrapper 类型,判断依据是是否有参数为该接口类的构造方法,缓存到cachedWrapperClasses
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            // 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
            clazz.getConstructor();
            // 如果配置文件中key的name 为空,则尝试从Extension注解中获取 name,或使用小写的类名作为name。
            // 已经弃用,就不在讨论这种方式
            if (StringUtils.isEmpty(name)) {
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("...");
                }
            }
            //使用逗号将name分割为字符串数组
            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                //如果扩展点配置的实现类使用了@Activate注解,就将对应的注解信息缓存起来
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    //缓存扩展点实现类class和扩展点名称的对应关系
                    cacheName(clazz, n);
                    //最后将class存入extensionClasses
                    saveInExtensionClass(extensionClasses, clazz, n);
                }
            }
        }
    }

loadClass方法实现了扩展点的分类缓存功能,如包装类,自适应扩展点实现类,普通扩展点实现类等分别进行缓存。需要注意的是自适应扩展点实现类@Adaptive注解,该注解源码如下

/**
 * For example, given <code>String[] {"key1", "key2"}</code>:
 * <ol>
 * <li>find parameter 'key1' in URL, use its value as the extension's name</li>
 * <li>try 'key2' for extension's name if 'key1' is not found (or its value is empty) in URL</li>
 * <li>use default extension if 'key2' doesn't exist either</li>
 * <li>otherwise, throw {@link IllegalStateException}</li>
 *
 * @return
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    String[] value() default {};
}

该注解的作用是决定哪个自适应拓展类被注入,该目标拓展类是由URL中的参数决定,URL中参数key由该注解的value给出,该key的value作为目标拓展类名称。

  • 如果注解中有多个值,则根据下标从小到大去URL中查找有无对应的key,一旦找到就用该key的value作为目标拓展类名称。
  • 如果这些值在url中都没有对应的key,使用spi上的默认值。

@Adaptive注解可以作用的类上与方法上, 绝大部分情况下,该注解是作用在方法上,当 Adaptive 注解在类上时,Dubbo 不会为该类生成代理类。注解在方法(接口方法)上时, Dubbo 则会为该方法生成代理类。 Adaptive 注解在接口方法上,表示拓展的加载逻辑需由框架自动生成。注解在类上,表示拓展的加载逻辑由人工编码完成。

上述的 loadClass扫描的是作用在类上。在 Dubbo 中,仅有两个类被@Adaptive注解了,分别是  AdaptiveCompiler 和  AdaptiveExtensionFactory
loadClass方法设置缓存 cacheAdaptiveClass会导致接口的 cacheAdaptiveClass不为空,后面都会默认用这个拓展类,优先级最高。

回到主线,当执行完 loadClass方法,配置文件中的所有拓展类已经被加载到map中,到此,关于缓存类加载的过程就分析完了。

Dubbo IOC

当 getExtensionClasses()方法执行流程完成后,再根据拓展项name从map中取出相应的拓展类即可获取扩展类Class,通过反射创建实例,并通过 injectExtension(instance);方法向实例中注入依赖 ,将后面总结

DUBBO AOP

当执行完 injectExtension(T instance)方法,在 createExtension(String name)就开始执行 wrapper的包装,类似于spring中的AOP,dubbo运用了装饰器模式。

 Set<Class<?>> wrapperClasses = cachedWrapperClasses;        
    if(CollectionUtils.isNotEmpty(wrapperClasses)){            // 循环创建 Wrapper 实例,形成Wrapper包装链
        for (Class<?> wrapperClass : wrapperClasses) {
            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
        }
    }

这里的 cachedWrapperClasses通过前面的分析已经知道,就是在解析配置文件时判断是否是 Wrapper类型的拓展类,++判断依据为构造方法中是否有参数为该接口类++,则缓存到cachedWrapperClasses。

执行 wrapperClass.getConstructor(type).newInstance(instance)将获取包装类的构造方法,方法的参数就是该接口类型,并通过反射生成一个包含该拓展类实例的包装对象,再通过 injectExtension注入包装对象的依赖。如此循环,得到成Wrapper包装链。这里需注意的是, 配置文件中内容靠后的包装类会包装在相对外层。下面是DUBBO AOP的例子,我们继续使用上面的接口与实现类,同时新增一个实现类,代码如下

package org.alexsotob.dubbo.SPI.wrapper;

import org.alexsotob.dubbo.SPI.Animal;

public class AnimalWrapper implements Animal {
    private Animal animal;
    public AnimalWrapper(Animal animal) {
        this.animal = animal;
    }

    @Override
    public String getName() {
        System.out.println("数据校验");
        String result = animal.getName();
        System.out.println("日志记录");
        return result;
    }
}

 META-INF/dubbo 文件内容修改:

cat=org.alexsotob.dubbo.SPI.impl.Cat
dog=org.alexsotob.dubbo.SPI.impl.Dog
org.alexsotob.dubbo.SPI.wrapper.AnimalWrapper #包装类

 使用之前的测试类测试,结果如下

 与我们预想的一致,实现了Wrapper类的切面功能

如果有什么不正确的地方,希望能够得到大家的纠正

本文参考了博客(注:大量的copy了别人的东西,因为该博主写得很优秀)

http://blog.itpub.net/31559758/viewspace-2678246/

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值