Dubbo源码分析之 SPI(一)
yxfree 人气:3一、概述
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-INFhttps://img.qb5200.com/download-x/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-INFhttps://img.qb5200.com/download-x/dubbo/internal 22 loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); 23 // META-INFhttps://img.qb5200.com/download-x/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-INFhttps://img.qb5200.com/download-x/dubbo/ 3 META-INFhttps://img.qb5200.com/download-x/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功能等知识点,我们都暂且绕过了,后面我们会在下一篇文章中逐一介绍。
加载全部内容