Dubbo源码分析之 SPI(一)

ぐ巨炮叔叔 提交于 2019-12-16 11:53:29

一、概述

    dubbo SPI 在dubbo的作用是基础性的,要想分析研究dubbo的实现原理、dubbo源码,都绕不过 dubbo SPI,掌握dubbo SPI 是征服dubbo的必经之路。

    本篇文章会详细介绍dubbo SPI相关的内容,通过源码分析,目标是让读者能通过本篇文章,彻底征服dubbo SPI。

    文章的组织方式是先介绍SPI 的概念,通过Java SPI 让大家了解SPI 是什么,怎么用,有一个初步的概念,dubbo的SPI是扩展了Java SPI的内容,自己实现了一个SPI。

二、SPI概念介绍

    SPI全称 Service Provider Interface,是一种服务发现机制。我们编程实现一个功能时,经常先抽象一个interface,内部再定一些方法。具体的实现交给 implments了此接口的类,实现了功能和实现类的分离。

    我们设想一下,如果一个描述汽车功能的interface  Car,存在多个实现类BMW、Benz、Volvo,某个场景调用Car的行驶方法,程序就需要确认到底是需要BMW、Benz、Volvo中的那个类对象。如果硬编码到程序中,固然可以,但是出现了接口和实现类的耦合,缺点也显而易见。

    有办法做到在调用代码中不涉及BMW、Benz、Volvo字符,也随意的指定实现类么?当然,SPI就是解决这个问题。

    SPI的实现方式是,在指定的路径下增加一个文本文件,文件的名称是interface的全限定名(包名+接口名),文件内容每行是一个此接口的实现类的全限定名,多个实现类就会有多行。接口进行调用时,根据接口全限定名,先读取文本文件,解析出具体的实现类,通过反射进行实例化,再调用之。如果增加新的实现类,不需要修改调用代码,只需要在文本文件中增加一行实现类的全限定名即可,删除实现类同理。

三、Java SPI 介绍

    我们先看看Java的SPI怎么实现的,通过一个demo,进行了解。

    先定一个小车的接口,有一个方法 goBeijing():

1 package cn.hui.spi
2 //Car 接口
3 public interface Car {
4 
5     // 开车去北京
6     void goBeijing();
7 
8 }

    我们建三个实现类:

    

 1 package cn.hui.spi.impl;
 2 public class Bmw implements Car{
 3     @Override
 4     public void goBeijing() {
 5         // TODO Auto-generated method stub
 6         System.out.println("开着宝马去北京......");
 7     }
 8 }
 9 
10 package cn.hui.spi.impl;
11 public class Benz implements Car{
12     @Override
13     public void goBeijing() {
14         // TODO Auto-generated method stub
15         System.out.println("开着奔驰去北京........");
16     }
17 }
18 
19 package cn.hui.spi.impl;
20 public class Volvo implements Car {
21     @Override
22     public void goBeijing() {
23         // TODO Auto-generated method stub
24         System.out.println("开着沃尔沃去北京......");
25     }
26 }

    我们在 "META-INF/services" 文件夹下新建一个文件,名称为“cn.hui.spi.Car",文件内容:

    

1 cn.hui.spi.impl.Bmw
2 cn.hui.spi.impl.Benz
3 cn.hui.spi.impl.Volvo

     方法调用的代码如下:

    

 1 import java.util.Iterator;
 2 import java.util.ServiceLoader;
 3 public class App {
 4     
 5     public static void main(String[] args) {
 6         ServiceLoader<Car> serviceLoader = ServiceLoader.load(Car.class);
 7         Iterator<Car> iterator = serviceLoader.iterator();
 8         while(iterator.hasNext()) {
 9             Car car = iterator.next();
10             car.goBeijing();
11         }
12     }
13 }

    打印结果:

1 开着宝马去北京......
2 开着奔驰去北京........
3 开着沃尔沃去北京......

    这个就是Java SPI简单实现方式。

三、Dubbo SPI介绍

    dubbo 在Java SPI的基础上进行了功能扩展,我们再看上面的Java SPI示例,可以发现很明显的问题,对文本文件的加载后,实例化对象是一次性全部进行实例化,得到一个实现类的对象集合,调用的时候循环执行。不能唯一指定一个实现类进行唯一调用。dubbo通过在文本文件中指定每个实现类的key,唯一标识出每个实现类,调用的时候可以指定唯一的一个实现类。同样实例化也不需要一次性全部实例化了,只需要实例化需要调用的类即可。

     同时dubbo还实现了IOC和AOP的功能,接口的实现类之间可以进行相互的注入,了解Spring的同学,应该很清楚IOC和AOP的逻辑,下面我们现在熟悉下dubbo SPI的相关概念,之后在通过一个简单的样例,了解dubbo SPI 的使用。

四、Dubbo SPI关键点

    dubbo SPI的功能主要是通过ExtensionLoader类实现,dubbo启动时,默认扫描三个目录:META-INF/services/、META-INF/dubbo/、META-INF/internal/,在这三个目录下的文本文件都会加载解析,文本文件的内容:key=实现类的全限定名。

    dubbo把接口定义为 扩展点,实现类定义为 扩展点实现,所有的扩展点(接口)需要进行@SPI注解,更多的功能和注解我们逐步介绍。

    dubbo在启动的时候扫描文本文件,对文件内容进行解析,但是不会全部进行实例化,只有在调用到具体的扩展点实现时,才会进行特定扩展点的实例化。

    同时dubbo SPI提供自适应扩展、默认扩展、自动激活扩展等功能,我们后面介绍。

五、Dubbo SPI示例

    我们把上面Car接口的例子,改造成基于dubbo SPI的实现。进行配置的文本文件内容。

    在扩展点实现类前都加上key,改为:

1 bmw=cn.hui.spi.impl.Bmw
2 benz=cn.hui.spi.impl.Benz
3 volvo=cn.hui.spi.impl.Volvo

 Car接口改造为:

1 @SPI
2 public interface Car {
3     // 开车去北京
4     void goBeijing();
5 }

扩展点,暂时不做修改,我们看看调用方法:

 1 public class App {
 2     public static void main(String[] args) {
 3         Car car = ExtensionLoader.getExtensionLoader(Car.class).getExtension("bmw");
 4         car.goBeijing();
 5         car = ExtensionLoader.getExtensionLoader(Car.class).getExtension("benz");
 6         car.goBeijing();
 7         car = ExtensionLoader.getExtensionLoader(Car.class).getExtension("volvo");
 8         car.goBeijing();
 9     }
10 }

此时,控制台会出现:

1 开着宝马去北京......
2 开着奔驰去北京........
3 开着沃尔沃去北京......

这个就是简单dubbo使用,复杂的功能我们放到源码分析的时候进行。

六、Dubbo SPI 源码分析

    dubbo SPI的功能主要几种在ExtensionLoader类中实现,分析源码也就主要分析此类,我们通过ExtensionLoader对外提供的方法作为入口进行源码分析。

    需要注意:一个type接口对应一个ExtensionLoader 实例。

    上面的示例中,我们通过 getExtensionLoader(..)方法,获得ExtensionLoader实例,ExtensionLoader类的构造方法是私有的,只能通过此方法获取实例。

    我们先看看此方法:

 1 @SuppressWarnings("unchecked")
 2 public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
 3     if (type == null) {
 4         throw new IllegalArgumentException("Extension type == null");
 5     }
 6     // 必须是接口
 7     if (!type.isInterface()) {
 8         throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
 9     }
10     // 必须被@SPI注解
11     if (!withExtensionAnnotation(type)) {
12         throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
13     }
14     // extension_loaders 为成员变量,是 type---> ExtensionLoader 实例的缓存
15     ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
16     if (loader == null) {
17         // putIfAbsent put不覆盖
18         EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
19         loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
20     }
21     return loader;
22 }

我们看到该方法主要是先对type进行校验,再根据type为key,从缓存EXTENSION_LOADERS中获取ExtensionLoader实例,如果缓存没有,则新建一个ExtensionLoader实例,并放入缓存。
注意,我们说过一个type对应一个ExtensionLoader实例,为什么还需要缓存呢,我们再看看 EXTENSION_LOADERS的定义:

// 扩展点接口和对应ExtensionLoader实例的缓存
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

没错,EXTENSION_LOADERS 是一个static、final修饰的类静态变量。

我们接着看上面,看一下ExtensionLoader的构造方法:

 1 private ExtensionLoader(Class<?> type) {
 2     this.type = type;
 3     // type 为ExtensionFactory时,objectFactory为空
 4     if (type == ExtensionFactory.class) {
 5         objectFactory = null;
 6     } else {
 7         // type为普通接口时,objectFactory为AdaptiveExtensionFactory,负责dubbo spi 的IOC 功能
 8         objectFactory = ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension();
 9     }
10 //        objectFactory = (type == ExtensionFactory.class ? null
11 //                : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
12 }

构造方法私有,不能直接在外部new出实例。

方法内部,参数type赋值给成员变量type,还会进行ExtensionFactory类判断,ExtensionFactory是实现IOC功能的,我们此处暂时绕过,后面进行介绍。
我们总结一下getExtensionLoader(..)方法,绕开ExtensionFactory,就是new 了一个ExtensionLoader对象实例,为成员变量type赋值为扩展点type,对象实例放入EXTENSION_LOADERS 缓存中。
现在我们有了ExtensionLoader实例对象,我们再看看获取type实例的方法:getExtension(..):

 1 @SuppressWarnings("unchecked")
 2 public T getExtension(String name) {
 3     if (name == null || name.length() == 0)
 4         throw new IllegalArgumentException("Extension name == null");
 5     if ("true".equals(name)) {
 6         // 获取默认的扩展实现类
 7         return getDefaultExtension();
 8     }
 9     // Holder仅用于持有目标对象,没有其他逻辑
10     Holder<Object> holder = cachedInstances.get(name);
11     if (holder == null) {
12         cachedInstances.putIfAbsent(name, new Holder<Object>());
13         holder = cachedInstances.get(name);
14     }
15     Object instance = holder.get();
16     if (instance == null) {
17         synchronized (holder) {
18             instance = holder.get();
19             if (instance == null) {
20                 // 创建扩展实例,并设置到holder中
21                 instance = createExtension(name);
22                 holder.set(instance);
23             }
24         }
25     }
26     return (T) instance;
27 }

    方法的入参name为提供配置的文本文件中的key,还记得我们的文本文件中的内容吧,其中一行:bmw=cn.hui.spi.impl.Bmw,此处的name 就是 bmw。 如果name为true,返回getDefaultExtension(),这个方法我们暂时绕过。

 我们看到13行,根据name从cachedInstances中获取Holder对象,很明显 cachedInstances就是一个存放对象的缓存,缓存中没有new一个新的实例,至于Holder,我们看下这个类:

 1 // 持有目标对象
 2 public class Holder<T> {
 3 
 4     private volatile T value;
 5 
 6     public void set(T value) {
 7         this.value = value;
 8     }
 9 
10     public T get() {
11         return value;
12     }
13 
14 }

只是存放对象,没有任何逻辑。

我们接着看到ExtensionLoader类的代码,在拿到holder实例后,我们要从hodler中获取扩展点的实例:

 1 Object instance = holder.get();
 2 if (instance == null) {
 3     synchronized (holder) {
 4         instance = holder.get();
 5         if (instance == null) {
 6             // 创建扩展实例,并设置到holder中
 7             instance = createExtension(name);
 8             holder.set(instance);
 9         }
10     }
11 }

如果holder中没有扩展点的实例,通过双检锁,通过调用 createExtension方法 返回扩展点实例。并放入holder对象中。

到此,我们发现new扩展点实例进到 createExtension方法中。

我们接着分析此方法:

 1 // 创建扩展对象实例
 2 @SuppressWarnings("unchecked")
 3 private T createExtension(String name) {
 4     // 从配置文件中加载所有的扩展类,形成配置项名称到配置类Clazz的映射关系
 5     Class<?> clazz = getExtensionClasses().get(name);
 6     if (clazz == null) {
 7         throw findException(name);
 8     }
 9     try {
10         T instance = (T) EXTENSION_INSTANCES.get(clazz);
11         if (instance == null) {
12             // 通过反射创建实例
13             EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
14             instance = (T) EXTENSION_INSTANCES.get(clazz);
15         }
16         // 向实例中注入依赖,IOC实现
17         injectExtension(instance);
18         // 包装处理
19         // cachedWrapperClasses 加载@SPI配置时赋值,此处进行实例化
20         Set<Class<?>> wrapperClasses = cachedWrapperClasses;
21         if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
22             // 循环创建wrapper实例
23             for (Class<?> wrapperClass : wrapperClasses) {
24                 // 将当前instance作为参数创建wrapper实例,然后向wrapper实例中注入属性值,
25                 // 并将wrapper实例赋值给instance
26                 instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
27             }
28         }
29         return instance;
30     } catch (Throwable t) {
31         throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type + ")  could not be instantiated: " + t.getMessage(), t);
32     }
33 }

    我们看到方法开始就通过 Class<?> clazz = getExtensionClasses().get(name); 获取Class对象,可以直观的看出通过name获得的这个clazz是在配置的文本文件中name对应的扩展点实现类的Class对象,关于getExtensionClasses方法,我们稍后分析,接着往下看:

1 T instance = (T) EXTENSION_INSTANCES.get(clazz);
2 if (instance == null) {
3     // 通过反射创建实例
4     EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
5     instance = (T) EXTENSION_INSTANCES.get(clazz);
6 }

通过clazz对象,从EXTENSION_INSTANCES获取缓存的实例,如果获取不到,通过反射clazz.newInstance() new一个新的实例对象,并放入EXTENSION_INSTANCES中。

我们可以看到,扩展点的实现类 必须要有一个默认无参的构造函数。

接着往下看:

1  // 向实例中注入依赖,IOC实现
2 injectExtension(instance);

此方法是实现IOC功能,我们暂且绕过。

接下来,我们看到:

 1  // 包装处理
 2  // cachedWrapperClasses 加载@SPI配置时赋值,此处进行实例化
 3  Set<Class<?>> wrapperClasses = cachedWrapperClasses;
 4  if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
 5      // 循环创建wrapper实例
 6      for (Class<?> wrapperClass : wrapperClasses) {
 7          // 将当前instance作为参数创建wrapper实例,然后向wrapper实例中注入属性值,
 8          // 并将wrapper实例赋值给instance
 9          instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
10      }
11  }

此处是处理包装类的,我们也暂且绕过。下面就是直接返回扩展点的instance实例了 

1 return instance;

 现在我们还有一个方法没有分析,就是加载扩展点实现类的Class对象的方法getExtensionClasses()。我们现在来看这个方法:

 1 private Map<String, Class<?>> getExtensionClasses() {
 2     Map<String, Class<?>> classes = cachedClasses.get();
 3     if (classes == null) {
 4         synchronized (cachedClasses) {
 5             classes = cachedClasses.get();
 6             if (classes == null) {
 7                 classes = loadExtensionClasses();
 8                 cachedClasses.set(classes);
 9             }
10         }
11     }
12     return classes;
13 }

我们看到,这个方法返回的是一个Map对象,可以确认的是,这个Map存放的是扩展点的所有实现类的Class,Map的key就是配置的文本文件的name。如果缓存cachedClasses 中存在,即返回,如果没有,通过loadExtensionClasses()加载,并设置到cachedClasses中。

我们接着看loadExtensionClasses方法:

 1 private Map<String, Class<?>> loadExtensionClasses() {
 2     // 获取注解 SPI的接口
 3     // type为传入的扩展接口,必须有@SPI注解
 4     final SPI defaultAnnotation = type.getAnnotation(SPI.class);
 5     // 获取默认扩展实现value,如果存在,赋值给cachedDefaultName
 6     if (defaultAnnotation != null) {
 7         String value = defaultAnnotation.value();
 8         if ((value = value.trim()).length() > 0) {
 9             // @SPI value 只能是一个,不能为逗号分割的多个
10             // @SPI value为默认的扩展实现
11             String[] names = NAME_SEPARATOR.split(value);
12             if (names.length > 1) {
13                 throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names));
14             }
15             if (names.length == 1)
16                 cachedDefaultName = names[0];
17         }
18     }
19     // 加载三个目录配置的扩展类
20     Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
21     // META-INF/dubbo/internal
22     loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
23     // META-INF/dubbo
24     loadDirectory(extensionClasses, DUBBO_DIRECTORY);
25     // META-INF/services/
26     loadDirectory(extensionClasses, SERVICES_DIRECTORY);
27     return extensionClasses;
28 }

我们看到方法内部的逻辑,首先判断扩展点接口type是否用@SPI注解,在前面的方法中,已经判断,如果没有@SPI注解,抛出异常,此处type必定存在@SPI注解。

根据注解获取到defaultAnnotation 对象,目的是拿到@SPI中的value,且value值不能用逗号分隔,只能有一个,赋值给cachedDefaultName。

接着定一个了Map对象extensionClasses,作为方法的返回值,我们知道,这个方法的返回值最后设置到了缓存cachedClasses中。我们看看这个extensionClasses是怎么赋值的。这个对象主要是”经历“了三个方法(其实是同一个方法loadDirectory,只是入参不同)。这三个方法的入参是extensionClasses 和一个目录参数,就是前面我们介绍的dubbo默认三个目录:

1 META-INF/services/
2 META-INF/dubbo/
3 META-INF/dubbo/internal/

我们再具体看方法loadDirectory的内容:

 1 private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
 2     // 扩展配置文件完整文件路径+文件名
 3     String fileName = dir + type.getName();
 4     try {
 5         Enumeration<java.net.URL> urls;
 6         // 获取类加载器
 7         ClassLoader classLoader = findClassLoader();
 8         if (classLoader != null) {
 9             urls = classLoader.getResources(fileName);
10         } else {
11             urls = ClassLoader.getSystemResources(fileName);
12         }
13         if (urls != null) {
14             while (urls.hasMoreElements()) {
15                 java.net.URL resourceURL = urls.nextElement();
16                 // 加载
17                 loadResource(extensionClasses, classLoader, resourceURL);
18             }
19         }
20     } catch (Throwable t) {
21         logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", t);
22     }
23 }

首先组合目录参数和type名称,作为文件的真实路径名,通过加载器进行加载,之后调用loadResource方法,同时extensionClasses 传入该方法。

 1 private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
 2     try {
 3         BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
 4         try {
 5             String line;
 6             while ((line = reader.readLine()) != null) {
 7                 // 字符#是注释开始标志,只取#前面的字符
 8                 final int ci = line.indexOf('#');
 9                 if (ci >= 0)
10                     line = line.substring(0, ci);
11                 line = line.trim();
12                 if (line.length() > 0) {
13                     try {
14                         String name = null;
15                         int i = line.indexOf('=');
16                         if (i > 0) {
17                             // 解析出 name 和 实现类
18                             name = line.substring(0, i).trim();
19                             line = line.substring(i + 1).trim();
20                         }
21                         if (line.length() > 0) {
22                             loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
23                         }
24                     } catch (Throwable t) {
25                         IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
26                         exceptions.put(line, e);
27                     }
28                 }
29             }
30         } finally {
31             reader.close();
32         }
33     } catch (Throwable t) {
34         logger.error("Exception when load extension class(interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t);
35     }
36 }

 这个方法就简单多了,解析文件流,拿到配置文本文件中的key、value,同时通过Class.forName(..)加载解析出来的扩展点实现类,传入方法loadClass,注意这个方法传入的参数还有存放key、Class的Map对象extensionClasses,以及配置文本文件中的key,我们再看这个方法:

 1 private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
 2     // type是否为clazz的超类,clazz是否实现了type接口
 3     // 此处clazz 是扩展实现类的Class
 4     if (!type.isAssignableFrom(clazz)) {
 5         throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface.");
 6     }
 7     // clazz是否注解了 Adaptive 自适应扩展
 8     // 不允许多个类注解Adaptive
 9     // 注解adaptive的实现类,赋值给cachedAdaptiveClass
10     if (clazz.isAnnotationPresent(Adaptive.class)) {
11         if (cachedAdaptiveClass == null) {
12             cachedAdaptiveClass = clazz;
13             // 不允许多个实现类都注解@Adaptive
14         } else if (!cachedAdaptiveClass.equals(clazz)) {
15             throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName());
16         }
17         // 是否为包装类,判断扩展类是否提供了参数是扩展点的构造函数
18     } else if (isWrapperClass(clazz)) {
19         Set<Class<?>> wrappers = cachedWrapperClasses;
20         if (wrappers == null) {
21             cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
22             wrappers = cachedWrapperClasses;
23         }
24         wrappers.add(clazz);
25         // 普通扩展类
26     } else {
27         // 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
28         clazz.getConstructor();
29         // 此处name为 SPI配置中的key
30         // @SPI配置中key可以为空,此时key为扩展类的类名(getSimpleName())小写
31         if (name == null || name.length() == 0) {
32             // 兼容旧版本
33             name = findAnnotationName(clazz);
34             if (name.length() == 0) {
35                 throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
36             }
37         }
38         // 逗号分割
39         String[] names = NAME_SEPARATOR.split(name);
40         if (names != null && names.length > 0) {
41             // 获取Activate注解
42             Activate activate = clazz.getAnnotation(Activate.class);
43             if (activate != null) {
44                 cachedActivates.put(names[0], activate);
45             }
46             for (String n : names) {
47                 if (!cachedNames.containsKey(clazz)) {
48                     cachedNames.put(clazz, n);
49                 }
50                 // name不能重复
51                 Class<?> c = extensionClasses.get(n);
52                 if (c == null) {
53                     extensionClasses.put(n, clazz);
54                 } else if (c != clazz) {
55                     throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
56                 }
57             }
58         }
59     }
60 }

 方法参数clazz就是传过来的扩展点实现类的Class对象,首先判断是否实现了扩展点type接口。接着判断是否注解了@Adaptive以及是否为包装类isWrapperClass(clazz),这两个分支逻辑 我们暂且绕过,接下来会进行构造器检查,判断是否存在无参构造器,如果name为空,为了兼容老版本 会进行一次name赋值。

此处会再进行一次name的分隔,前门已经知道,name中不会存在逗号的,但经过上面兼容老版本的重新赋值,会再进行一次判断。@Activate注解的判断,我们也暂且绕过。

循环解析过的name字符串,把加载的扩展点实现Class对象和name存放到入参extensionClasses中。

至此,解析、加载配置文本文件的逻辑已经结束。最后的结果主要是有:把加载到的扩展点Class和key存入到缓存对象extensionClasses中,同时设置cachedDefaultName为扩展点注解@SPI中的value。

我们重新回到方法createExtension中,现在我们已经拿到了特定name对应的扩展点实现类的Class对象,如果对象为空,抛出异常。

接着,我们从缓存对象EXTENSION_INSTANCES中,通过Class对象获取实例,如果实例为空,通过clazz.newInstance()创建,并放入EXTENSION_INSTANCES中。

createExtension方法的后面的逻辑:

 1 // 向实例中注入依赖,IOC实现
 2 injectExtension(instance);
 3 // 包装处理
 4 // cachedWrapperClasses 加载@SPI配置时赋值,此处进行实例化
 5 Set<Class<?>> wrapperClasses = cachedWrapperClasses;
 6 if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
 7     // 循环创建wrapper实例
 8     for (Class<?> wrapperClass : wrapperClasses) {
 9         // 将当前instance作为参数创建wrapper实例,然后向wrapper实例中注入属性值,
10         // 并将wrapper实例赋值给instance
11         instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
12     }
13 }

 是拿到扩展点的实例之后,后期的处理,包括对IOC的实现,包装类的处理等功能逻辑,这些知识点,我们稍后进行分析。

七、总结

    总结一下,本篇文章,我们分析了dubbo SPI的主流程,从入门介绍、示例描述到源码分析,主流程基本介绍完了,中间涉及到的@Adaptive、@Activate注解,以及包装类、扩展点实现类的IOC功能等知识点,我们都暂且绕过了,后面我们会在下一篇文章中逐一介绍。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!