Alibaba-技术专区-Dubbo的SPI应用与原理
SPI(Service Provider Interface)
- 本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。
- 在Java中SPI是被用来设计给服务提供商做插件使用的。基于策略模式 来实现动态加载的机制 。我们在程序只定义一个接口,具体的实现交个不同的服务提供者;在程序启动的时候,读取配置文件,由配置确定要调用哪一个实现;
- 通过 SPI 机制为我们的程序提供拓展功能,在dubbo中,基于 SPI,我们可以很容易的对 Dubbo 进行拓展。例如dubbo当中的protocol,LoadBalance等都是通过SPI机制扩展。
JAVA 原生SPI 示例
先简单介绍JAVA SPI的应用。首先,我们定义一个Car接口
1 2 3 | public interface Car { String getBrand(); } |
定义该接口的两个实现类。
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class BM implements Car { public String getBrand() { System.out.println( "BM car" ); return "BM" ; } } public class Benz implements Car { public String getBrand() { System.out.println( "benz car" ); return "Benz" ; } } |
再在resources下创建META-INF/services 文件夹,并创建一个文件,文件名称为Car接口的全限定名com.dubbo.dp.spi.Car。内容为接口实现类的全限定类名。
1 2 | com.dubbo.dp.spi.Benz com.dubbo.dp.spi.BM |
使用如下,调用Car接口在配置文件中的所有实现类。
1 2 3 4 5 6 7 | public class JavaSPITest { @Test public void sayHello() throws Exception { ServiceLoader<Car> serviceLoader = ServiceLoader.load(Car. class ); serviceLoader.forEach(car -> System.out.println(car.getBrand())); } } |
JAVA SPI实现了接口的定义与具体业务实现解耦,应用进程可以根据实际业务情况启用或替换具体组件。
举例:JAVA的java.sql包中就定义一个接口Driver,各个服务提供商实现该接口。当我们需要使用某个数据库时就导入相应的jar包。
缺点
- 不能按需加载。Java SPI在加载扩展点的时候,会一次性加载所有可用的扩展点,很多是不需要的,会浪费系统资源;
- 获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。
- 不支持AOP与依赖注入。
- JAVA SPI可能会丢失加载扩展点异常信息,导致追踪问题很困难;
dubbo SPI示例
- dubbo重新实现了一套功能更强的 SPI 机制,支持了AOP与依赖注入,并且利用缓存提高加载实现类的性能,同时支持实现类的灵活获取,文中接下来将讲述SPI的应用与原理。
Dubbo的SPI接口都会使用@SPI注解标识,该注解的主要作用就是标记这个接口是一个SPI接口。源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 | @Documented @Retention (RetentionPolicy.RUNTIME) @Target ({ElementType.TYPE}) public @interface SPI { /** * default extension name * 设置默认拓展类 */ String value() default "" ; } |
该注解只作用在接口上,value用来设置默认拓展类
首先讲解下dubbo SPI的使用。在上述Car接口加上@SPI注解,它的实现类暂时不变,配置文件的路径与文件名也暂时不变,文件内容调整如下:
1 2 3 4 5 6 7 8 9 | @SPI public interface Car { String getBrand(); } benz=com.dubbo.dp.spi.Benz bm=com.dubbo.dp.spi.BM |
配置文件通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。使用如下:
public class JavaSPITest {
@Test
public void sayHello() throws Exception {
ExtensionLoader<Car> carExtensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
//按需获取实现类对象
Car car = carExtensionLoader.getExtension("benz");
System.out.println(car.getBrand());
}
}
输出结果为
benz car
Benz
Dubbo SPI 源码分析
在dubbo SPI示例方法中,我们首先通过ExtensionLoader
的 getExtensionLoader
方法获取一个接口的 ExtensionLoader
实例,然后再通过 ExtensionLoader
的 getExtension
方法获取拓展类对象,源码如下,首先是getExtensionLoader
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | /** <strong> * 扩展类加载器缓存,就是扩展点ExtendsLoader实例缓存; key=扩展接口 value=扩展类加载器 </strong> */ <strong> private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(); </strong> 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
也会在后面用到。
1 2 3 4 5 | private ExtensionLoader(Class<?> type) { this .type = type; <strong> //type通常不为ExtensionFactory类,则objectFactory为ExtensionFactory接口的默认扩展类AdaptiveExtensionFactory </strong> objectFactory = (type == ExtensionFactory. class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory. class ).getAdaptiveExtension()); } |
当通过ExtensionLoader.getExtensionLoader
取到接口的加载器Loader之后,在通过getExtension
方法获取需要拓展类对象。该方法的整个执行流程如下图所示

参照执行流程图,拓展类对象的获取源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | /** * 扩展点实例缓存 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性能强劲的原因之一,包括
- 扩展点Class缓存 ,Dubbo SPI在获取扩展点时,会优先从缓存中读取,如果缓存中不存在则加载配置文件,根据配置将Class缓存到内存中,并不会直接初始化。
- 扩展点实例缓存 ,Dubbo不仅会缓存Class,还会缓存Class的实例。每次取实例的时候会优先从缓存中取,取不到则从配置中加载,实例化并缓存到内存中。
下面我们来看一下创建拓展对象的过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | /** * 扩展实例存入内存中缓存起来; 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( "....." ); } } |
创建拓展类对象步骤分别为:
- 通过 getExtensionClasses 从配置文件中加载所有的拓展类,再通过名称获取目标拓展类
- 通过反射创建拓展对象
- 向拓展对象中注入依赖
- 将拓展对象包裹在相应的 Wrapper 对象中
第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。我们先重点分析getExtensionClasses 方法的逻辑getExtensionClasses
方法的逻辑。
从配置文件中加载所有的拓展类
-
在通过name获取拓展类之前,首先需要根据配置文件解析出拓展项名称与拓展类的映射map,之后再根据拓展项名称从map中取出相应的拓展类即可。
getExtensionClasses
方法源码如下12345678910111213141516171819202122232425/**
* 扩展点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
方法加载配置文件的逻辑12345678910111213141516171819202122232425/**
* 三个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 注解解析过程比较简单,源码如下。只允许一个默认拓展类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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/
三个文件夹。方法源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 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是如何加载资源的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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
方法实现如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | /** * 加载扩展点实现类的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注解,该注解源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | / *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运用了装饰器模式。
1 2 3 4 5 6 7 | 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的例子,我们继续使用上面的Car接口与实现类,同时新增一个实现类,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class CarWrapper implements Car{ private Car car; /** * 有一个包含该接口类参数的构造方法 */ public CarWrapper(Car car) { this .car = car; } @Override public String getBrand() { System.out.println( "校验" ); String result = car.getBrand(); System.out.println( "记录日志" ); return result; } } |
该接口实现了Car,并且持有一个Car对象,同时拥有一个构造方法且该构造方法的参数为Car接口类型,那么该类会被识别为接口的Wrapper类。则可以在方法中做一些切面功能的扩展,再调用car对象执行其方法实现AOP功能。
将配置文件内容中添加wrapper实现类,如下
1 2 3 | benz=com.xiaoju.automarket.energy.scm.rpc.Benz bm=com.xiaoju.automarket.energy.scm.rpc.BM com.xiaoju.automarket.energy.scm.rpc.CarWrapper #包装类 |
执行如下代码获取benz的拓展类实例后,调用其方法,将会被Wrapper包装类
1 2 3 4 5 6 7 | public class TestAOP { public static void main(String[] args) { ExtensionLoader<Car> carExtensionLoader = ExtensionLoader.getExtensionLoader(Car. class ); Car car = carExtensionLoader.getExtension( "benz" ); System.out.println(car.getBrand( null )); } } |
结果如下
校验 benz car 记录日志 Benz
与我们预想的一致,实现了Wrapper类的切面功能。
总结
本篇简单分别介绍了 Java SPI 与 Dubbo SPI 用法,并对 Dubbo SPI 的加载拓展类的过程进行了分析。同时分析了Dubbo AOP的实现原理。如果文章中有错误不妥之处,希望大家指正。
下一篇文章将讲述 Dubbo SPI 的扩展点自适应机制,dubbo自动注入中也涉及到了该部分,即SpringExtensionFactory执行factory.getExtension涉及到加载自适应拓展点。
本文来自博客园,作者:洛神灬殇,转载请注明原文链接:https://www.cnblogs.com/liboware/p/12973118.html,任何足够先进的科技,都与魔法无异。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix