Dubbo的独门绝技,SPI实现原理分析
文章目录
- 前言
- 普通SPI实现原理
- 实例化扩展点源码分析
- 扩展点加载流程分析
- LoadingStrategy分析
- 接口定义
- 接口实现
- 加载原理
- loadClass方法分析
- 自适应SPI实现原理
- 自适应扩展代码生成分析
- 自激活SPI
- 简单使用
- 原理分析
- Activate注解
- 源码分析
- IOC实现原理
- objectFactory介绍
- 总结
- AOP实现原理
- 总结
本专栏对应Dubbo版本:
2.7.8。官方文档地址:https://dubbo.apache.org/zh/docsv2.7/dev/
官方GitHub地址:https://github.com/apache/dubbo/releases/tag/dubbo-2.7.8
前言
在上篇文章我们已经对Dubbo中的SPI有了简单的了解,接下来我们通过源码详细了解其实现细节。
在本文中,我将SPI分为普通SPI,与之相对应的是自适应SPI,这个概念是笔者“捏造”的,为了更好的划分文章结构,读者不必纠结字眼。
普通SPI实现原理
核心的API为
// 第一步:获取到对应接口的ExtensionLoader
ExtensionLoader<SpiService> extensionLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
// 第二步:通过ExtensionLoader实例化具体扩展点
SpiService internal = extensionLoader.getExtension("internal");
获取ExtensionLoader的逻辑非常简单,大家自行阅读源码即可,如何创建扩展点才是我们的重点
实例化扩展点源码分析
在没有进行源码分析之前大家应该能想到,要得到一个扩展点实现类对象起码要做这么几件事
- 根据接口名找到并解析配置文件,加载对应的扩展点实现类
- 类加载成功后就可以反射创建对象了
- 之后再对这个对象完成IOC及AOP
实际上Dubbo也确实是这么做的,接下来我们分析其源码
扩展点实例化核心方法如下,源码比较简单,这里我只保留了其骨架,我们先对整体流程有一个认知
// 直接定位到调用的核心方法
// name:为传入的扩展点名称
// wrap:代表是否要进行AOP,默认为true
private T createExtension(String name, boolean wrap) {// 1️⃣.加载扩展点实现类,得到class对象Class<?> clazz = getExtensionClasses().get(name);// 2️⃣.这里通过反射调用空参构造函数,完成实例化T instance = (T) EXTENSION_INSTANCES.get(clazz);if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());instance = (T) EXTENSION_INSTANCES.get(clazz);}// 3️⃣.IOCinjectExtension(instance);// 4️⃣.AOPif (wrap) {// .....}// 5️⃣.扩展点生命周期,进一步完成初始化initExtension(instance);return instance;
}
在上面的代码中我们可以清晰的看到整个扩展点实例化分为5步,如下图所示

扩展点加载流程分析
在这一小节中,我们主要分析Dubbo是如何加载扩展点的,核心代码位于org.apache.dubbo.common.extension.ExtensionLoader#loadExtensionClasses,如下:
private Map<String, Class<?>> loadExtensionClasses() {// 我们可以在@SPI的注解中指定默认要使用的SPI名称cacheDefaultExtensionName();Map<String, Class<?>> extensionClasses = new HashMap<>();// dubbo中内置了一些加载策略for (LoadingStrategy strategy : strategies) {loadDirectory(extensionClasses, strategy.directory(), type.getName(),strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());// 这里主要是为了向下兼容alibaba DubboloadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());}return extensionClasses;
}
要理解上述代码,我们首先要搞懂LoadingStrategy的作用,其次我们知道在strategies这个集合中放入了哪些LoadingStrategy。
LoadingStrategy分析
接口定义

可以看到这个接口继承了Prioritized,Prioritized的主要作用是定义加载的优先级。LoadingStrategy的作用在于定义加载SPI配置文件时的策略,例如:从哪个目录下加载、哪些不需要加载等
public interface LoadingStrategy extends Prioritized {// 定义了SPI配置文件的加载地址String directory();// 目前没有看到哪个实现类复写了这个方法,可忽略default boolean preferExtensionClassLoader() {return false;}// 排除指定包下的SPI实现类default String[] excludedPackages() {return null;}// 如果一个SPI存在多个同名的实现的时候,是否要进行覆盖default boolean overridden() {return false;}
}
接口实现
在Dubbo中内置了三种LoadingStrategy,分别为:
-
DubboInternalLoadingStrategy:加载优先级最高(
Integer.MIN_VALUE),加载目录为:META-INF/dubbo/internal/,不支持覆盖。这个策略主要是Dubbo内部使用的[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SKPgpOCS-1679484853206)(/Users/mingzhidai/Library/Application Support/typora-user-images/image-20211219161506784.png)]
注意:这个策略的优先级是最高的,因此
META-INF/dubbo/internal/这个目录下的类会被最先加载,同时它是不支持覆盖的,因此如果同名的实现存在多个,第一个会生效 -
DubboLoadingStrategy:正常加载优先级(
0),加载目录为:META-INF/dubbo/,支持覆盖。这个策略是提供给外部开发者使用的,我们平常进行扩展时,更多是使用这个目录。由于这个
注意:这个策略的优先级低于
DubboInternalLoadingStrategy,因此META-INF/dubbo/这个目录下的类会晚于META-INF/dubbo/internal/中的类被加载,同时它又能支持覆盖已存在的同名实现类,这就意味在META-INF/dubbo/目录中的类的优先级是高于META-INF/dubbo/internal/的,通过这种方式我们就能替换Dubbo中默认的SPI实现,这也体现了内核+插件的思想 -
ServicesLoadingStrategy:加载目录为:
META-INF/services/,加载优先级最低(0),同时也支持覆盖。这个策略兼容了JDK原生SPI的加载目录。
注意:这个策略的优先级是最低的,同时它支持覆盖,因为这个目录下的类的优先级是最高的,会覆盖之前加载的类。
整个优先级及是否覆盖的设计也体现了一个思想:越靠近应用,优先级越高,方便扩展
加载原理
LoadingStrategy的加载本身使用的是JDK原生的SPI,加载逻辑见org.apache.dubbo.common.extension.ExtensionLoader#loadLoadingStrategies,如下图所示:
我们查看Dubbo Jar包内的META-INF/services/目录也能看到以下内容

loadClass方法分析
在了解了LoadingStrategy之后,我们回头继续分析扩展点实现类的加载流程
-
org.apache.dubbo.common.extension.ExtensionLoader#loadExtensionClasses☞ 入口⇣⇣⇣⇣
-
org.apache.dubbo.common.extension.ExtensionLoader#loadDirectory☞ 加载目录⇣⇣⇣⇣
-
org.apache.dubbo.common.extension.ExtensionLoader#loadResource☞ 一个目录下会有很多配置文件,逐个解析配置文件⇣⇣⇣⇣
-
org.apache.dubbo.common.extension.ExtensionLoader#loadClass☞ 通过配置文件可以拿到全类名,加载并缓存
整个流程还是非常清晰的,先加载目录,通过对前面LoadingStrategy学习大家应该知道,不同加载策略的加载目录是不同的。目录加载完成后会得到一个配置文件列表,之后通过loadResource方法逐个加载配置文件,在这个过程中可以拿到所有扩展点实现的全类名,最后加载所有的扩展点实现类,并进行缓存。关于前面配置文件的加载较为简单,本文不做详细介绍,读者可自行阅读源码。我们着重看loadClass方法的处理逻辑,代码如下:
下面的代码忽略了一些简单及异常处理,只保留了核心逻辑
// extensionClasses:一个缓存map,key为SPI名称,value为对应的实现类Class对象
// resourceURL:这个参数传入进来只是为了更好的描述异常信息
// clazz:本次要被加载的类,通过Class.forName(”全类名“, true, classLoader)加载而来
// overridden:对应的加载策略是否允许覆盖已存在的类
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,boolean overridden) throws NoSuchMethodException {// 1️⃣.根据是否存在Adaptive注解判断实现类是否是一个Adaptive类,并进行缓存if (clazz.isAnnotationPresent(Adaptive.class)) {cacheAdaptiveClass(clazz, overridden);// 2️⃣.根据是否存在一个参数类型为当前SPI接口的构造函数判断是否是一个wrapper类,用于AOP} else if (isWrapperClass(clazz)) {cacheWrapperClass(clazz);} else {// 3️⃣.SPI实现类必须要有一个空参构造函数,// 这里相当于一个检查,保证缓存的扩展点实现是可用的// 如果不存在这个构造函数,这一步就会抛出异常clazz.getConstructor();// 4️⃣.如果使用在配置文件中没有对这个SPI扩展点命名,// 那么尝试直接从实现类中解析出该扩展点的名称if (StringUtils.isEmpty(name)) {name = findAnnotationName(clazz);}// 使用逗号对该name进行切割String[] names = NAME_SEPARATOR.split(name);if (ArrayUtils.isNotEmpty(names)) {// 5️⃣.缓存自激活扩展实现类,关于自激活我们在后文中分析cacheActivateClass(clazz, names[0]);for (String n : names) {// 缓存扩展点名称,只会缓存第一个名称cacheName(clazz, n);// 6️⃣.缓存扩展点实现,一个扩展点实现可以有多个名称// 但一个名称只能对应一个扩展点实现saveInExtensionClass(extensionClasses, clazz, n, overridden);}}}
}
上面代码逻辑非常简单,配合注释大家基本能看懂,注释中提到的自激活扩展实现类我会在后文进行分析。经过上面的分析,我们可以得到下面一张关系图:

- 一个SPI接口,对应一个
ExtensionLoader - 在
ExtensionLoader中会按照不同的分类将加载的类缓存到不同字段中
到目前为止,我们已经知道了扩展点实现类是如何加载的, 对于整个扩展点实例化的流程我们已经完成了一大部分,再回过头来看一下整个扩展点实例化的流程图,如下:

按照图中流程,我现在应该跟大家介绍Dubbo中的IOC及AOP,不过由于IOC依赖了自适应SPI,所以这里我们先来详细了解一下自适应SPI,关于其使用我已经在之前的文章中介绍过了,本文更多的是分析原理
自适应SPI实现原理
使用自适应扩展的API如下:
ExtensionLoader<OrderService> orderServiceExtensionLoader = ExtensionLoader.getExtensionLoader(SPI接口).getAdaptiveExtension();
自适应扩展的核心代码如下:
private Class<?> getAdaptiveExtensionClass() {// 实际上就是在加载配置文件中的类,并进行缓存 getExtensionClasses();// 是否存在被Adaptive注解修饰的自适应扩展类if (cachedAdaptiveClass != null) {return cachedAdaptiveClass;}// 如果不存在的话,通过代码生成创建一个自适应扩展类return cachedAdaptiveClass = createAdaptiveExtensionClass();
}private Class<?> createAdaptiveExtensionClass() {// 生成代码,其实就是字符串的拼接String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();ClassLoader classLoader = findClassLoader();// 编译,并返回具体的Class对象org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();return compiler.compile(code, classLoader);
}
通过上面的代码要知道的是,自适应SPI有两种形式
- 在扩展点实现类上添加Adaptive注解,指定由这个类来实现自适应逻辑
- 如果我们没有添加Adaptive注解, 那么Dubbo会帮我们生成一个自适应类来完成适配逻辑
自适应扩展代码生成分析
关于代码生成的细节本文不做过多分析,主要是一些字符串的拼接,生成code字符串后再进行编译。我们重点关注最终生成的代码,首先我们编写一个测试接口,如下:
@SPI
public interface AdaptiveSpi {/*** 注意这个方法有两个特征* <p>* 1.方法上有adaptive注解* <p>* 2.方法的参数中有一个URL,需要注意的是这是一个org.apache.dubbo.common.URL,不是java.net.URL*/@Adaptive("adaptive1")void adaptiveMethod1(URL url);@Adaptive("adaptive2")void adaptiveMethod2(URLHolder url);@Adaptivevoid adaptiveMethod3(URLHolder url, Invocation invocation);/*** 普通方法,用于观察最终生成的代码*/void normalMethod();class URLHolder {private final URL url;public URLHolder(URL url) {this.url = url;}public URL getUrl() {return url;}}
}
对应的生成的自适应扩展实现类代码如下:
限于篇幅问题,在下面的代码中我省去了一些检查相关代码。例如:url不能为空,扩展点名称不能为空等
public class AdaptiveSpi$Adaptive implements com.easy4coding.dubbo.spi.service.adaptive.AdaptiveSpi {public void adaptiveMethod1(com.easy4coding.dubbo.spi.service.adaptive.AdaptiveSpi.URLHolder arg0) {org.apache.dubbo.common.URL url = arg0.getUrl();String extName = url.getParameter("adaptive1");com.easy4coding.dubbo.spi.service.adaptive.AdaptiveSpi extension = (com.easy4coding.dubbo.spi.service.adaptive.AdaptiveSpi) ExtensionLoader.getExtensionLoader(com.easy4coding.dubbo.spi.service.adaptive.AdaptiveSpi.class).getExtension(extName);extension.adaptiveMethod2(arg0);}public void adaptiveMethod2(org.apache.dubbo.common.URL arg0) {org.apache.dubbo.common.URL url = arg0;String extName = url.getParameter("adaptive2");com.easy4coding.dubbo.spi.service.adaptive.AdaptiveSpi extension = (com.easy4coding.dubbo.spi.service.adaptive.AdaptiveSpi) ExtensionLoader.getExtensionLoader(com.easy4coding.dubbo.spi.service.adaptive.AdaptiveSpi.class).getExtension(extName);extension.adaptiveMethod1(arg0);}public void adaptiveMethod3(com.easy4coding.dubbo.spi.service.adaptive.AdaptiveSpi.URLHolder arg0, org.apache.dubbo.rpc.Invocation arg1) {org.apache.dubbo.common.URL url = arg0.getUrl();String methodName = arg1.getMethodName();String extName = url.getMethodParameter(methodName, "adaptive.spi", "null");com.easy4coding.dubbo.spi.service.adaptive.AdaptiveSpi extension = (com.easy4coding.dubbo.spi.service.adaptive.AdaptiveSpi) ExtensionLoader.getExtensionLoader(com.easy4coding.dubbo.spi.service.adaptive.AdaptiveSpi.class).getExtension(extName);extension.adaptiveMethod3(arg0, arg1);}public void normalMethod() {throw new UnsupportedOperationException("The method public abstract void com.easy4coding.dubbo.spi.service.adaptive.AdaptiveSpi.normalMethod() of interface com.easy4coding.dubbo.spi.service.adaptive.AdaptiveSpi is not adaptive method!");}
}
观察上面3个方法,我们可以得出以下结论:
-
自适应SPI接口上中的方法如果要用到自适应的能力,必须要有
@Adaptive注解,例如的normalMethod直接抛出了UnsupportedOperationException。所谓自适应能力,我们在上篇文章中已经解释过了,就是根据方法调用的参数去适配真实的SPI实现类 -
自适应SPI实际调用的是其它实际的扩展点实现类,调用的API就是我们之前分析过的
ExtensionLoader.getExtensionLoader(扩展点名称).getExtension()。 -
真正要使用的扩展点的名称是从URL的参数中解析出来的,同时会将
@Adaptive注解中的值作为key,从而去从URL中解析出扩展点名称,如果@Adaptive注解中没有配置value属性的话,那么会将类名转换为key,如果类名是驼峰命名的方式的话,那么会将驼峰转换为"."分隔的形式,例如AdaptiveSpi会被转换为adaptive.spi(见adaptiveMethod3中的逻辑) -
另外,根据自适应方法(被
@Adaptive注解修饰的方法)的参数中是否存在org.apache.dubbo.rpc.Invocation,Dubbo从URL中解析扩展点名称的方式也存在一些差异,如果存在Invocation类型的参数,那么调用的是url.getMethodParameter(见adaptiveMethod3中的逻辑),如果不存在Invocation类型的参数调用的是url.getParameterorg.apache.dubbo.rpc.Invocation代表一次具体的RPC调用,它持有调用过程中的变量,比如方法名,参数等
自激活SPI
接下来我们学习自激活SPI,什么是自激活SPI呢?顾名思义,这一类的SPI扩展点是不需要显式的传入扩展点名称来获取的,而是当满足一定条件时,会自动返回。看到这里,你可能还不明白,没有关系,我们来看一个例子。
简单使用
@SPI
public interface ActivateSpi {void activateMethod();
}public class FirstActivateSpiImpl implements ActivateSpi {@Overridepublic void activateMethod() {System.out.println("activate first");}
}// ❤️ 注意这个注解
@Activate("second")
public class SecondActivateSpiImpl implements ActivateSpi {@Overridepublic void activateMethod() {System.out.println("activate second");}
}public static void main(String[] args) { URL url = new URL("","",0);url = url.addParameter("second","1");// 明确表明要加载的扩展点是firstString[] extensionNames = new String[]{"first"};final List<ActivateSpi> activateExtension = ExtensionLoader.getExtensionLoader(ActivateSpi.class).getActivateExtension(url, extensionNames);activateExtension.forEach(ActivateSpi::activateMethod);
}// 程序输出:
// activate second
// activate first
不忘忘记要添加配置文件哈~
first=com.easy4coding.dubbo.spi.service.activate.FirstActivateSpiImpl second=com.easy4coding.dubbo.spi.service.activate.SecondActivateSpiImpl
在上面的例子中我们可以看到,当我们调用getActivateExtension时,显示传入的扩展点名称是first,但此方法返回的扩展点不仅仅有first,second也被加载了,这说明second这个扩展点自己激活了自己,这就是自激活SPI,接下来我们来分析一下其实现原理
原理分析
核心的API如下:
// key:用于从url中获取到具体的扩展点名称
public List<T> getActivateExtension(URL url, String key, String group) {// 通过key,从url中获取对应的value,并将其作为扩展点名称String value = url.getParameter(key);return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
}
// values:代表要显式获取到的扩展点名称,具体源码我们在后文中分析
public List<T> getActivateExtension(URL url, String[] values, String group) {...}
我们基于以上API进行分析其原理
Activate注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {// 匹配通过API调用时方法参数中传入的groupString[] group() default {};// 用于跟Url中的参数进行匹配,只有匹配成功扩展点才会激活String[] value() default {};// 从2.7版本已经过时,用于排序,指定顺序在某一扩展点前@DeprecatedString[] before() default {};// 从2.7版本已经过时,用于排序,指定顺序在某一扩展点后@DeprecatedString[] after() default {};// 用于排序int order() default 0;
}
Activate注解中的group及value属性主要用于跟之前提到的API中传入的参数进行匹配,另外几个属性主要用于对加载的扩展点进行排序。
源码分析
核心代码位于:org.apache.dubbo.common.extension.ExtensionLoader#getActivateExtension(URL,String[],String)

整个代码逻辑并不难,核心逻辑可以分为两部分
-
判断传入的扩展点名称中是否包含
-default,如果包含,代表不需要自激活的扩展点,只加载名称在values数组中的扩展点实现。-代表指定要排除某一个扩展点,例如传入的valuse集合为:[“name1”,“name2”,“-name3”],则代表要使用"name1","name2"及自激活的SPI扩展点实现,同时排除名称为"name3"的扩展点实现代码如下:
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {// 前文已经分析过,调用这个方法会去读取SPI配置文件并加载所有的类缓存到不同的集合中getExtensionClasses();// cachedActivates中缓存的是被Activate注解修饰的扩展点实现for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {// name是扩展点名称String name = entry.getKey();// value是Activate注解Object activate = entry.getValue();String[] activateGroup, activateValue;// 获取注解中的值,用于后续匹配if (activate instanceof Activate) {activateGroup = ((Activate) activate).group();activateValue = ((Activate) activate).value();// 兼容alibaba Dubbo} else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();} else {continue;}// 👀,这里是重点哦~~~~!if (isMatchGroup(group, activateGroup)&& !names.contains(name)&& !names.contains(REMOVE_VALUE_PREFIX + name)&& isActive(activateValue, url)) {activateExtensions.add(getExtension(name));}}// 我们前文已经分析过Activate注解,知道他有排序能力// 这里就是根据注解中的before、after及order进行排序activateExtensions.sort(ActivateComparator.COMPARATOR);}上面代码的核心在于,如何判断一个SPI实现是不是自激活的,对应判断条件如下:
-
isMatchGroup(group, activateGroup),分组是否匹配。在group参数不为空的情况下,要求Activate注解中的group的值必须跟方法参数group的值匹配。 -
!names.contains(name),不能是显示获取的扩展点,主要是为了排序,显示加载的扩展点会在后面进行统一加载 -
!names.contains(REMOVE_VALUE_PREFIX + name),不能是显示排除的扩展点 -
isActive(activateValue, url),必须跟URL中的key是匹配的,代码如下,注释中已经做了详细介绍,笔者不再过多介绍
-
-
加载指定名称的扩展点实现
List<T> loadedExtensions = new ArrayList<>();for (int i = 0; i < names.size(); i++) {String name = names.get(i);if (!name.startsWith(REMOVE_VALUE_PREFIX)&& !names.contains(REMOVE_VALUE_PREFIX + name)) {if (DEFAULT_KEY.equals(name)) {if (!loadedExtensions.isEmpty()) {// default代表了自激活扩展点,这里主要是为了排序activateExtensions.addAll(0, loadedExtensions);loadedExtensions.clear();}} else {loadedExtensions.add(getExtension(name));}}}if (!loadedExtensions.isEmpty()) {// 最终都放入activateExtensions集合中统一返回activateExtensions.addAll(loadedExtensions);}
不知道大家有没有注意到,在分析上面两步时,文中都提到了一个词:排序。主要是因为这个方法最终返回的扩展点集合包含了两部分内容
- 自激活的扩展点
- 指定名称,显示申明要使用的扩展点(通过values参数申明)
对于自激活的扩展点,由于它们都被Activate注解修饰,因此可以直接依赖Activate注解对其进行排序,但是另外一部分显示要申明的扩展点要怎么办呢?Dubbo的做法是
- 默认自激活扩展点的顺序高于显式申明要使用的扩展点,所以最后一行代码调用了
activateExtensions.addAll(loadedExtensions) - 可以在
values参数中通过传入default值的方式来指定自激活扩展点的顺序。举个例子,如果传入的values值为:[“name1”,“name2”,“default”,“name3”],那么"name1",“name2"对应的扩展点实现的顺序高于自激活扩展点的顺序,自激活扩展点的顺序高于"name3"对应的扩展点实现。如果传入的如果传入的values值为[“name1”,“name2”,“name3”],不包含"default"的情况下,自激活扩展点的顺序高于"name1”,“name2”,"name3"对应的扩展点实现
到现在为止我们已经基本已经掌握了Dubbo中的SPI的核心内容,接下来我们要学习更高阶的知识了☞Dubbo中的IOC及AOP
通过前文我们应该知道,IOC及AOP发生在扩展点实例化的过程中,整个流程图如下:

核心代码如下:
private T createExtension(String name, boolean wrap) {// 1️⃣.加载扩展点实现类,得到class对象Class<?> clazz = getExtensionClasses().get(name);// 2️⃣.这里通过反射调用空参构造函数,完成实例化T instance = (T) EXTENSION_INSTANCES.get(clazz);if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());instance = (T) EXTENSION_INSTANCES.get(clazz);}// 3️⃣.IOCinjectExtension(instance);// 4️⃣.AOPif (wrap) {// .....}// 5️⃣.扩展点生命周期,进一步完成初始化initExtension(instance);return instance;
}
接下来我们来分析整个IOC的过程,代码并不难哈~😎
IOC实现原理
核心代码如下:

可以看到代码真的非常简单,大概逻辑分为这么几步
- 判断setter方法上是否有
DisableInject注解,这个注解代表不需要进行注入 - 从
objectFactory中获取到一个指定名称的对象并反射调用setter方法进行注入
所以,我们要理解IOC首先要搞懂objectFactory是什么
objectFactory介绍

我们可以看到,objectFactory是在创建ExtensionLoader的过程中被初始化的,同时它是一个自适应SPI,对应的接口类型为:ExtensionFactory。这种情况,我们首先去查找它的SPI配置文件,如下:
在其配置文件中存在三个SPI实现类
spring=org.apache.dubbo.config.spring.extension.SpringExtensionFactory
adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory
我们分别查看对应的三个SPI实现会发在,在AdaptiveExtensionFactory这个实现类有一个@Adaptive注解,代表调用API获取自适应SPI时,真正返回的是这个类,这个类做的更多的是适配的工作,真正干活的还是SpringExtensionFactory及SpiExtensionFactory。SpringExtensionFactory这个类的作用是从Spring容器中获取到指定的Bean,而SpiExtensionFactory这个类的作用是依赖Dubbo的SPI机制获取到指定的对象,它们都是服务于Dubbo的IOC机制。
因为代码比较简单,这里我们就看一下AdaptiveExtensionFactory的代码:
AdaptiveExtensionFactory

注意哦:dubbo中SPI实现类的优先级会高于spring容器中bean的优先级
现在我们已经知道了objectFactory的作用,那么IOC就已经没有任何秘密。简单总结一下:
总结
- IOC发生的时机:在实例化SPI实现时进行IOC
- IOC的条件
- 存在setter方法
- setter方法上没有
DisableInject注解 - 在进行IOC时,被注入的对象是通过
objectFactory获取,objectFactory是通过自适应SPI进行初始化的,实际上它会优先尝试使用Dubbo的SPI获取一个对象,如果获取到了,只会返回。如果没有获取到,那么会再次从Spring容器中获取一个对应的bean。
AOP实现原理
AOP的实现就更加简单了,实现AOP的关键是依赖通过loadExtensionClasses方法加载的wrapper类,还记得loadExtensionClasses干了啥吗?回顾一下这张图

AOP的核心代码如下:
Dubbo的AOP说白了就是依靠wrapper类来对真实的扩展点进行一次包装,wrapper类持有一个真实的扩展点实现引用!
总结
这篇文章我们分析了Dubbo中的各种SPI实现,以及Dubbo基于SPI扩展出来功能:IOC、AOP。我相信只要大家认真看完这篇文章,积极动手实践,搞懂Dubbo的SPI是没有任何问题。要学好Dubbo,掌握SPI是第一步,整个框架中用到SPI的地方数不胜数。
谢谢你看完这篇文章,也请你谢谢认真学习的自己!
相关文章:
Dubbo的独门绝技,SPI实现原理分析
文章目录前言普通SPI实现原理实例化扩展点源码分析扩展点加载流程分析LoadingStrategy分析接口定义接口实现加载原理loadClass方法分析自适应SPI实现原理自适应扩展代码生成分析自激活SPI简单使用原理分析Activate注解源码分析IOC实现原理objectFactory介绍总结AOP实现原理总结…...
单例模式,饿汉与懒汉
文章目录什么是单例模式单例模式的两种形式饿汉模式懒汉模式懒汉模式与饿汉模式是否线程安全懒汉模式的优化什么是单例模式 单例模式其实就是一种设计模式,跟象棋的棋谱一样,给出一些固定的套路帮助你更好的完成代码。设计模式有很多种,单例…...
Prometheus监控实战之Blackbox_exporter黑盒监测
1 Blackbox_exporter应用场景 blackbox_exporter是Prometheus官方提供的exporter之一,可以提供HTTP、HTTPS、DNS、TCP以及ICMP的方式对网络进行探测。 1.1 HTTP 测试 定义 Request Header信息 判断 Http status / Http Respones Header / Http Body内容 1.2 TC…...
【蓝桥杯集训·每日一题】AcWing 1051. 最大的和
文章目录一、题目1、原题链接2、题目描述二、解题报告1、思路分析2、时间复杂度3、代码详解三、知识风暴线性DP一、题目 1、原题链接 1051. 最大的和 2、题目描述 对于给定的整数序列 A{a1,a2,…,an},找出两个不重合连续子段,使得两子段中所有数字的和最…...
【Unity工具,简单应用】Photon + PUN 2,做一个简单多人在线聊天室
【Unity工具,简单应用】Photon PUN 2,做一个简单多人聊天室前置知识,安装,及简单UI大厅聊天室简单同步较复杂同步自定义同步最终效果前置知识,安装,及简单UI 【Unity工具,简单学习】PUN 2&…...
程序员增加收入实战 让小伙伴们都加个鸡腿
文章目录前言1️⃣一、发外包平台💁🏻♂️二、朋友介绍✍️三、打造自己的个人IP👋🏿四、混群拉单🤳🏿五、面试拉单💻六、技术顾问🦴七、开发个人项目总结:前言 程序员…...
GPIO四种输入和四种输出模式
GPIO的结构图如下所示: 最右端为I/O引脚,左端的器件位于芯片内部。I/O引脚并联了两个用于保护的二极管。 输入模式 从I/O引脚进来就遇到了两个开关和电阻,与VDD相连的为上拉电阻,与VSS相连的为下拉电阻。再连接到TTL施密特触发…...
ChatGPT能够改变时代吗?一点点思考
都知道ChatGPT的出现对整个世界产生了剧烈的影响,前不久出的ChatGPT4更是在ChatGPT3.5的基础上展现了更强的功能。比如说同一个问题,ChatGPT3.5还是乱答的,ChatGPT4已经能给出正确解了。当然这只能说明技术是进步的。 虽然如此,很…...
Markdown如何使用详细教程
目录 一、Markdown 标题 二、Markdown 段落 三、Markdown 字体 四、Markdown 分隔线 五、Markdown 列表 六、Markdown 引用 七、Markdown 代码 八、Markdown 链接 九、Markdown 图片 十、Markdown 表格 前言 当前许多网站都广泛使用 Markdown 来撰写博客,…...
HTML5庆祝生日蛋糕烟花特效
HTML5庆祝生日蛋糕烟花特效 <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>HTML5 Birthday Cake Fireworks</title><style>canvas {position: absolute;top: 0;left: 0;z-index: -1;}</style> </h…...
算法套路四——反转链表
算法套路四——反转链表 算法示例一:LeetCode206. 反转链表 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 初始化pre为空,cur为头指针 pre指针:记录当前结点的前一个结点 cur指针:记录当…...
多线程 (六) wait和notify
🎉🎉🎉点进来你就是我的人了 博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!人生格言:当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔🦾&am…...
React--》状态管理工具—Mobx的讲解与使用
目录 Mobx的讲解与使用 Mobx环境配置 Mobx的基本使用 Mobx计算属性的使用 Mobx监听属性的使用 Mobx处理异步的使用 Mobx的模块化 Mobx的讲解与使用 Mobx是一个可以和React良好配合的集中状态管理工具,mobx和react的关系相当于vuex和vue之间的关系࿰…...
有效的括号长按键入验证外星语词典字符的最短距离用栈实现队列
有效的括号来源:杭哥20. 有效的括号 - 力扣(LeetCode)bool isValid(char * s) {int szstrlen(s);char stack[sz];int k0;for (int i0;i<sz;i){if (s[i]( || s[i][ || s[i]{){stack[k]s[i];}else{if (k0){return false;}else if (s[i]} &am…...
《前端开发者的进阶之路》
前端作为Web开发的重要领域之一,不断地发展和演变着。除了基本的HTML、CSS、JavaScript技能,前端开发者需要掌握更多的进阶知识才能应对不断变化的需求。本文将介绍一些前端的进阶知识,帮助前端开发者进一步提高自己的技能水平。1.框架和库在…...
为什么说网络安全是风口行业?是IT行业最后的红利?
前言 “没有网络安全就没有国家安全”。当前,网络安全已被提升到国家战略的高度,成为影响国家安全、社会稳定至关重要的因素之一。 网络安全行业特点 1、就业薪资非常高,涨薪快 2021年猎聘网发布网络安全行业就业薪资行业最高人均33.77万&…...
使用shell 脚本,批量解压一批zip文件,解压后的文件放在以原zip文件名前10个字符的文件夹中的例子
#!/bin/bash for file in *.zip dofolder$(echo $file | cut -c 1-10)mkdir $folderunzip -q $file -d $folder doneecho "All zip files have been extracted." # 说明: # 1. for循环遍历当前目录下的所有zip文件 # 2. 使用cut命令提取zip文件名前10个字…...
01 | Msyql系统架构
目录MySQL系统架构连接器查询缓存分析器优化器执行器MySQL系统架构 大体来说,MySQL分为Server层和引擎层两部分。 Server层包含链接器、查询缓存、分析器、优化器和执行器,而引擎层负责的是数据的存储和读取,支持InnoDB、Myisam、Memory等多…...
Linux命令---设备管理
Linux setleds命令Linux setleds命令用来设定键盘上方三个 LED 的状态。在 Linux 中,每一个虚拟主控台都有独立的设定。语法setleds [-v] [-L] [-D] [-F] [{|-}num] [{|-}caps] [{|-}scroll]参数:-F:预设的选项,设定虚拟主控台的状…...
前端入门:HTML5+CSS3+JAAVASCRIPT
1、 初识HTML HTML:Hyper Text Markup Language(超文本标记语言) 。 超文本包括:文字、图片、音频、视频、动画等。 1.1、W3C标准 1.2、HTML基本结构 示例: <!-- DOCTYPE:告诉浏览器,我们要使用什么规划,这里是HTML --> …...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
