【dubbo系列】 08-dubbo SPI 机制

简介

Dubbo 未使用 Java SPI,而是重新实现了一套更强的 SPI 机制。其相关逻辑封装在 ExtensionLoader 类中,通过 ExtensionLoader 可以加载指定的实现类。其所需要的配置文件需放置在META-INF/dubbo 目录下。

如何使用

  1. 定义接口,使用 @Spi 注解。

    还是使用 java spi 中的代码,在 HelloService 接口中增加 @Spi 注解。

     package com.joyxj.spi;
    
     import com.alibaba.dubbo.common.extension.SPI;
    
     /**
     * 定义一个接口,用于spi
     * @author xiaoj
     * @version 1.0
     *
     */
     @SPI
     public interface HelloService {
    
         void sayHello();
     }
    
    
  2. 增加接口的实现类。

    这里定义了二个接口的实现类,直接使用 java spi 文章的实现类。

    HelloServiceImpl

     package com.joyxj.spi;
    
     /**
     * Hello Service 实现类
     *
     * @author xiaoj
     * @version 1.0
     */
     public class HelloServiceImpl implements HelloService {
         @Override
         public void sayHello() {
             System.out.println("hello");
         }
     }
    
    

    HelloServiceSecondImpl

     package com.joyxj.spi;
    
     /**
     * Hello Service 实现类
     *
     * @author xiaoj
     * @version 1.0
     */
     public class HelloServiceSecondImpl implements HelloService {
         @Override
         public void sayHello() {
             System.out.println("你好");
         }
     }
    
    
  3. META-INF/dubbo 目录下新增配置文件 com.joyxj.spi.HelloService

    文件内容如下:

     helloServiceImpl = com.joyxj.spi.HelloServiceImpl # HelloService实现类
     helloServiceSecondImpl = com.joyxj.spi.HelloServiceSecondImpl
    

    其中 # 后面的为注释。

  1. 测试

     package com.joyxj.spi;
    
     import com.alibaba.dubbo.common.extension.ExtensionLoader;
     import org.junit.jupiter.api.Test;
    
     /**
     * 【功能描述】
     *
     * @author xiaoj
     * @version 1.0
     * @date 2019-06-18
     */
     public class HelloServiceDubboSpiDemo {
    
         @Test
         public void test() {
             ExtensionLoader<HelloService> loader = ExtensionLoader.getExtensionLoader(HelloService.class);
    
             HelloService helloService = loader.getExtension("helloServiceImpl");
             helloService.sayHello();
             helloService = loader.getExtension("helloServiceSecondImpl");
             helloService.sayHello();
         }
     }
    
    

    输出:

     hello
     你好
    

源码分析

从测试案例中,我们通过 ExtensionLoader.getExtensionLoader 加载一个 ExtensionLoader 实例。然后通过 ExtensionLoadergetExtension 加载一个接口的拓展实现类。

  1. 首先分析 ExtensionLoadergetExtensionLoader 的方法。

     public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
         if (type == null)
             throw new IllegalArgumentException("Extension type == null");
         // 判断是否是接口
         if (!type.isInterface()) {
             throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
         }
         // 判断是否包含 SPI 注解。
         if (!withExtensionAnnotation(type)) {
             throw new IllegalArgumentException("Extension type(" + type +
                     ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
         }
    
         // 从缓存中获得一个 ExtensionLoader 对象
         ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
         if (loader == null) {
             // 创建一个 ExtensionLoader 对象
             EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
             loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
         }
         return loader;
     }
    

    getExtensionLoader 方法比较简单,主要是做一些验证,主要是接口和注解 SPI 的验证,然后首先从缓存中读取 ExtensionLoader 实例,如果没有则生成一个新的 ExtensionLoader ,然后把实例放入 缓存中。其中 EXTENSION_LOADERS 是一个 ConcurrentMap

  2. 缓存中没有 ExtensionLoader 实例时,需要生成一个 ExtensionLoader 实例,生成 ExtensionLoader 实例时,做了哪些操作呢?现在分析其构造方法。

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

    一个私有的构造方法,仅有二个初始化操作。ExtensionLoader.getExtensionLoader(ExtensionFactory.class) 这里也用到了 Dubbo SPI 机制。

  3. getExtension(String name) 方法是用于根据名称获得一个拓展实现类。如果不存在则抛出异常。这也是对 Java SPI 的增强。Java SPI 只能遍历获得所有。

     public T getExtension(String name) {
         // 判空
         if (name == null || name.length() == 0)
             throw new IllegalArgumentException("Extension name == null");
         if ("true".equals(name)) {
             // 获取默认的拓展实现类
             return getDefaultExtension();
         }
         // Holder,顾名思义,用于持有目标对象,
         // cachedInstances是一个ConcurrentMap,用于做缓存
         Holder<Object> holder = cachedInstances.get(name);
         if (holder == null) {
             cachedInstances.putIfAbsent(name, new Holder<Object>());
             holder = cachedInstances.get(name);
         }
         Object instance = holder.get();
         // 双重校验
         if (instance == null) {
             synchronized (holder) {
                 // 同步获得拓展实例对象
                 instance = holder.get();
                 if (instance == null) {
                     // 创建拓展实例对象
                     instance = createExtension(name);
                     holder.set(instance);
                 }
             }
         }
         return (T) instance;
     }
    
  4. 分析 createExtension 如何创建一个拓展实例对象。

     private T createExtension(String name) {
         // 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的映射关系表,然后根据配置名称获得配置类
         Class<?> clazz = getExtensionClasses().get(name);
         if (clazz == null) {
             throw findException(name);
         }
         try { 
             // 先从从缓存中获得,EXTENSION_INSTANCES 也是一个 ConcurrentMap
             T instance = (T) EXTENSION_INSTANCES.get(clazz);
             if (instance == null) {
                 // 缓存中没有则通过反射获得生成一个对象
                 EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                 instance = (T) EXTENSION_INSTANCES.get(clazz);
             }
             // 向实例注入依赖
             injectExtension(instance);
             // 获取接口的Wrapper类,
             //Wrapper类是一个有复制构造函数的类,也是典型的装饰者模式,可参考 ProtocolFilterWrapper
             Set<Class<?>> wrapperClasses = cachedWrapperClasses;
             if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                 for (Class<?> wrapperClass : wrapperClasses) {
                     // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例
                     // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
                     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);
         }
     }
    
    

    createExtension 方法主要有以下几个步骤:

    • 通过 getExtensionClasses 获得所有的扩展类
    • 通过反射创建扩展对象
    • 向扩展对象注入依赖。这是Dubbo IOC 的实现。
    • 将扩展对象包裹在相应的 Wrapper 对象中。这是Dubbo AOP 的具体实现。
  5. getExtensionClasses 获得所有的扩展类

    在第 4 步中第一个步骤就是获得所有的扩展类,下面分析下这个 getExtensionClasses 方法的具体实现。这个方法会获得一个“配置项名称”到“配置类”的映射关系表。

     private Map<String, Class<?>> getExtensionClasses() {
         Map<String, Class<?>> classes = cachedClasses.get();
         // 先检查缓存
         if (classes == null) {
             // 加锁
             synchronized (cachedClasses) {
                 // 再次检查缓存,并判空
                 classes = cachedClasses.get();
                 if (classes == null) {
                     // 如果仍然没有,则加载
                     classes = loadExtensionClasses();
                     cachedClasses.set(classes);
                 }
             }
         }
         return classes;
     }
    

    getExtensionClasses 方法很简单,先判断缓存中是否有扩展类,没有则加锁后,再次检查缓存。如果仍然没有的话,则通过loadExtensionClasses 加载扩展类。

     private Map<String, Class<?>> loadExtensionClasses() {
         //提取和缓存默认的扩展类。
         cacheDefaultExtensionName();
    
         Map<String, Class<?>> extensionClasses = new HashMap<>();
         // 加载指定文件夹下的内容
         loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
         loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
         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;
     }
    
    

    cacheDefaultExtensionName 用于提取和缓存默认的扩展类名称,并且验证是否含有SPI注解,并验证其正确性,看下面的源码:

     private void cacheDefaultExtensionName() {
         // 获得SPI注解,这里的type值是调用 getExtensionLoader 时传入的的一个Class。
         final SPI defaultAnnotation = type.getAnnotation(SPI.class);
         if (defaultAnnotation != null) {
             String value = defaultAnnotation.value();
             if ((value = value.trim()).length() > 0) {
                 // 对SPI 注解进行拆分
                 String[] names = NAME_SEPARATOR.split(value);
                 // 验证注解是否合法,该注解只有一个value值。
                 if (names.length > 1) {
                     throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
                             + ": " + Arrays.toString(names));
                 }
                 // 设置默认名称
                 if (names.length == 1) {
                     cachedDefaultName = names[0];
                 }
             }
         }
     }
    

    loadDirectory 方法加载指定文件夹下的内容。这里加载的文件夹包含了:META-INF/dubbo/internalMETA-INF/dubboMETA-INF/services。方法的具体实现如下:

     private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
         // 获得具体加载文件的名称,等于文件夹路径 + type 全限定名
         String fileName = dir + type;
         try {
             Enumeration<java.net.URL> urls;
             ClassLoader classLoader = findClassLoader();
             // 加载所有的同名文件
             if (classLoader != null) {
                 urls = classLoader.getResources(fileName);
             } else {
                 urls = ClassLoader.getSystemResources(fileName);
             }
             if (urls != null) {
                 while (urls.hasMoreElements()) {
                     java.net.URL resourceURL = urls.nextElement();
                     // 加载资源
                     loadResource(extensionClasses, classLoader, resourceURL);
                 }
             }
         } catch (Throwable t) {
             logger.error("Exception occurred when loading extension class (interface: " +
                     type + ", description file: " + fileName + ").", t);
         }
     }
    
     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(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                             }
                         } catch (Throwable t) {
                             IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                             exceptions.put(line, e);
                         }
                     }
                 }
             }
         } catch (Throwable t) {
             logger.error("Exception occurred when loading extension class (interface: " +
                     type + ", class file: " + resourceURL + ") in " + resourceURL, t);
         }
     }
    
     private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
         // 类的类型判断,判定此 type 对象所表示的类或接口与指定的 clazz 参数所表示的类或接口是否相同,或是否是其超类或超接口
         if (!type.isAssignableFrom(clazz)) {
             throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                     type + ", class line: " + clazz.getName() + "), class "
                     + clazz.getName() + " is not subtype of interface.");
         }
    
         // 判断是否包含 Adaptive 注解,判断其是否是自适应扩展类
         if (clazz.isAnnotationPresent(Adaptive.class)) {
             // 缓存自适应扩展类
             cacheAdaptiveClass(clazz);
         // 判断扩展类是否是包装类
         } else if (isWrapperClass(clazz)) {
             // 缓存包装类
             cacheWrapperClass(clazz);
         } else {
             // 表示是一个普通的扩展类
             // 判断是否包含一个默认的构造函数,如果没有的话,则抛出异常
             clazz.getConstructor();
             if (StringUtils.isEmpty(name)) {
                 // 如果 name 为空的话,则尝试从 Extension 注解中获取 name,或使用小写的类名作为 name
                 name = findAnnotationName(clazz);
                 if (name.length() == 0) {
                     throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                 }
             }
    
             // 拆分 name
             String[] names = NAME_SEPARATOR.split(name);
             if (ArrayUtils.isNotEmpty(names)) {
                 // 根据扩展类是否有 activate 注解缓存激活类
                 cacheActivateClass(clazz, names[0]);
                 for (String n : names) {
                     // 缓存名称
                     cacheName(clazz, n);
                     // 保存扩展类到 extensionClasses ,extensionClasses 是一个 Map。
                     saveInExtensionClass(extensionClasses, clazz, name);
                 }
             }
         }
     }
    
     // 判断是否是一个包装类,通过判断是否包含一个拷贝的构造函数。
     private boolean isWrapperClass(Class<?> clazz) {
         try {
             clazz.getConstructor(type);
             return true;
         } catch (NoSuchMethodException e) {
             return false;
         }
     }
    
     // 缓存自适应扩展类
     private void cacheAdaptiveClass(Class<?> clazz) {
         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());
         }
     }
    
     // 缓存包装类
     private void cacheWrapperClass(Class<?> clazz) {
         if (cachedWrapperClasses == null) {
             cachedWrapperClasses = new ConcurrentHashSet<>();
         }
         cachedWrapperClasses.add(clazz);
     }
    
     /**
      * 从扩展类中获得名称
      */
     private String findAnnotationName(Class<?> clazz) {
         // 获得扩展类中的 Extension 注解的值(已经不建议使用 Extension 注解)
         org.apache.dubbo.common.Extension extension = clazz.getAnnotation(org.apache.dubbo.common.Extension.class);
         if (extension == null) {
             String name = clazz.getSimpleName();
             if (name.endsWith(type.getSimpleName())) {
                 name = name.substring(0, name.length() - type.getSimpleName().length());
             }
             return name.toLowerCase();
         }
         return extension.value();
     }
    
     /**
      * 缓存激活类,兼容新旧版本的dubbo
      */
     private void cacheActivateClass(Class<?> clazz, String name) {
    
         //判断是否有 Activate 注解。
         Activate activate = clazz.getAnnotation(Activate.class);
         if (activate != null) {
             // 缓存
             cachedActivates.put(name, activate);
         } else {
             // support com.alibaba.dubbo.common.extension.Activate
             // 兼容旧版本的 Activate 注解
             com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
             if (oldActivate != null) {
                 cachedActivates.put(name, oldActivate);
             }
         }
     }
    
     /**
     * 缓存名称
     */
     private void cacheName(Class<?> clazz, String name) {
         if (!cachedNames.containsKey(clazz)) {
             cachedNames.put(clazz, name);
         }
     }
    
     /**
     * 保存扩展类
     */
     private void saveInExtensionClass(Map<String, Class<?>> extensionClasses, Class<?> clazz, String name) {
         Class<?> c = extensionClasses.get(name);
         if (c == null) {
             extensionClasses.put(name, clazz);
         } else if (c != clazz) {
             // 表示有同名的二个不同的扩展类,抛出异常
             throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + name + " on " + c.getName() + " and " + clazz.getName());
         }
     }
    
    
  6. Dubbo IOC

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

     private T injectExtension(T instance) {
         try {
             if (objectFactory != null) {
                 // 遍历目标类的所有方法
                 for (Method method : instance.getClass().getMethods()) {
                     // 判断是否是 setter 方法,以set 开头,只有一个参数,且方法修饰符是 public
                     if (isSetter(method)) {
                         /**
                          * Check {@link DisableInject} to see if we need auto injection for this property
                          */
                          // 判断方法是否包含 DisableInject 注解
                         if (method.getAnnotation(DisableInject.class) != null) {
                             continue;
                         }
                         // 获得方法参数的类
                         Class<?> pt = method.getParameterTypes()[0];
                         // 判断是否是基本类型,这个在jdk的基础上做了增加,包括 String,Boolean,Character,Number,Date 都不做注入。
                         if (ReflectUtils.isPrimitives(pt)) {
                             continue;
                         }
                         try {
                             // 获得参数名
                             String property = getSetterProperty(method);
                             // 从 objectFactory 获得对象
                             Object object = objectFactory.getExtension(pt, property);
                             if (object != null) {
                                 // 通过反射调用 setter 方法设置依赖
                                 method.invoke(instance, object);
                             }
                         } catch (Exception e) {
                             logger.error("Failed to inject via method " + method.getName()
                                     + " of interface " + type.getName() + ": " + e.getMessage(), e);
                         }
                     }
                 }
             }
         } catch (Exception e) {
             logger.error(e.getMessage(), e);
         }
         return instance;
     }
    
     /**
     * 判断方法是否是setter方法
     */
     private boolean isSetter(Method method) {
         return method.getName().startsWith("set")
                 && method.getParameterTypes().length == 1
                 && Modifier.isPublic(method.getModifiers());
     }
    

    上面的代码中,objectFactory 变量的类型为 AdaptiveExtensionFactory , AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其它类型的 ExtensionFactory。Dubbo 目前提供了二种 ExtensionFactory,分别是 SpiExtensionFactorySpringExtensionFactory。 前者用于创建自适应的拓展,后者用于创建从Spring IOC 容器中获得所需的扩展。

    Dobbo IOC 目前仅支持 setter 方式注入。


   转载规则


《【dubbo系列】 08-dubbo SPI 机制》 孤独如梦 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
【dubbo系列】 09-dubbo AOP 机制 【dubbo系列】 09-dubbo AOP 机制
前言对于使用过 Spring 技术的同学一定对 AOP 不会陌生,通常使用AOP 在方法的前后插入其它的逻辑。如使用 Spring AOP 实现日志记录、鉴权等操作。 那在 dubbo 中是如何实现这样的功能的呢?在 dubbo 中有一种特
2019-06-20
下一篇 
【dubbo系列】 07-Dubbo 协议 【dubbo系列】 07-Dubbo 协议
前言Dubbo 支持的协议有很多,可以通过 dubbo:protocol 来配置不同的协议。包括以下几种: dubbo: 是Dubbo的默认协议。 rmi: 采用 JDK 标准的 java.rmi.* 实现,采用阻塞式短连接和 JDK 标
2019-06-14
  目录