1. Java-SPI 机制
SPI 全称为(Service Provider Interface),是 Java 提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件,SPI 的作用就是为这些被扩展的 API 寻找服务实现。
常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口是由 Java 核心库来提供,而 SPI 的实现则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)中。
1.1. 基本思想
其实 Java SPI 实际上是 “基于接口的编程+策略模式+配置文件” 组合实现的动态加载机制。
系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。
为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。Java SPI 就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似 IOC 的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以 SPI 的核心思想就是解耦。
1.2. 使用场景
API (Application Programming Interface)在大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅仅依赖接口调用,且无权选择不同实现。从使用人员上来说,API 直接被应用开发人员使用。
SPI (Service Provider Interface)是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。从使用人员上来说,SPI 是被框架扩展人员使用。
1.3. 具体约定
Java SPI 的具体约定为:当服务的提供者,提供了服务接口的一种实现之后,在 jar 包的 META-INF/services/ 目录里同时创建一个以服务接口命名的文件,该文件的内容是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该 jar 包 META-INF/services/ 里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。JDK 提供服务实现查找的一个工具类:java.util.ServiceLoader。
1.4. 代码示例
既然是 SPI,那么就必须先定义好接口。其次,就是定义好接口的实现类。
-
新建一个 Promotion 接口:
public interface Promotion {
BigDecimal getDiscount();
}
-
新建一个 GrouponPromotion 实现类:
public class GrouponPromotion implements Promotion {
@Override
public BigDecimal getDiscount() {
System.out.println("团购优惠一元");
return new BigDecimal(1);
}
}
-
新建一个 SeckillPromotion 实现类:
public class SeckillPromotion implements Promotion {
@Override
public BigDecimal getDiscount() {
System.out.println("秒杀优惠二元");
return new BigDecimal(2);
}
}
-
在 resources 目录下 建立 META-INF/services/ 文件夹:

-
新建一个名为接口全名的文件 com.yly.region.spi.Promotion,内容如下:
com.yly.region.spi.impl.GrouponPromotion
com.yly.region.spi.impl.SeckillPromotion
-
新建一个测试类 Test,使用 java.util.ServiceLoader 运行看下结果:
public class Test {
public static void main(String[] agrs) {
ServiceLoader<Promotion> loaders = ServiceLoader.load(Promotion.class);
for (Promotion promotion : loaders) {
promotion.getDiscount();
}
}
}
-
可以看到运行结果:
团购优惠一元
秒杀优惠二元
2. Dubbo 的 SPI 应用与原理
SPI (Service Provider Interface)本质是 将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。
想要学习 Dubbo 的源码,SPI 机制务必弄懂。我们已经了解了 Java SPI 的用法,接下来我们了解下 Dubbo SPI 的用法,再分析 Dubbo SPI 的源码。
2.1. getExtensionLoader()
在 Dubbo SPI 示例方法中,我们首先通过 ExtensionLoader 的 getExtensionLoader() 方法获取一个接口的 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension() 方法获取拓展类对象,源码如下,首先是 getExtensionLoader() 方法:
/**
* {@link org.apache.dubbo.rpc.model.ApplicationModel}, {@code DubboBootstrap} and this class are
* at present designed to be singleton or static (by itself totally static or uses some static fields).
* So the instances returned from them are of process or classloader scope. If you want to support
* multiple dubbo servers in a single process, you may need to refactor these three classes.
* <p>
* Load dubbo extensions
* <ul>
* <li>auto inject dependency extension </li>
* <li>auto wrap extension in wrapper </li>
* <li>default extension is an adaptive instance</li>
* </ul>
*
* @see <a href="http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Service%20Provider">Service Provider in Java 5</a>
* @see org.apache.dubbo.common.extension.SPI
* @see org.apache.dubbo.common.extension.Adaptive
* @see org.apache.dubbo.common.extension.Activate
*/
public class ExtensionLoader<T> {
// ...
/**
* 扩展类加载器缓存,就是扩展点ExtendsLoader实例缓存;key=扩展接口 value=扩展类加载器
*/
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
@SuppressWarnings("unchecked")
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。
public class ExtensionLoader<T> {
// ...
private ExtensionLoader(Class<?> type) {
this.type = type;
// type通常不为ExtensionFactory类,
// 则objectFactory为ExtensionFactory接口的默认扩展类AdaptiveExtensionFactory
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
}
注意这里创建 ExtensionLoader 对象的构造方法如下:ExtensionLoader.getExtensionLoader() 获取ExtensionFactory 接口的拓展类,再通过 getAdaptiveExtension 从拓展类中获取目标拓展类。它会设置该接口对应的 objectFactory 常量为 AdaptiveExtensionFactory。因为 AdaptiveExtensionFactory 类上加了 @Adaptive 注解,为什么是 AdaptiveExtensionFactory 原因在之后的文章会解释,且 objectFactory 也会在后面用到。
2.2. getExtension()
当通过 ExtensionLoader.getExtensionLoader() 取到接口的加载器 Loader 之后,再通过 getExtension() 方法获取需要拓展类对象。该方法的整个执行流程如下图所示:

参照执行流程图,拓展类对象的获取源码如下:
public class ExtensionLoader<T> {
// ...
/**
* 扩展点实例缓存 key=扩展点名称,value=扩展实例的Holder实例
*/
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
/**
* Find the extension with the given name. If the specified name is not found, then {@link IllegalStateException}
* will be thrown.
*/
@SuppressWarnings("unchecked")
public T getExtension(String name) {
return getExtension(name, true);
}
/**
* 获取接口拓展类实例
* 1.检查缓存中是否存在
* 2.创建并返回拓展类实例
* @param name 需要获取的配置文件中拓展类的key
* @return
*/
public T getExtension(String name, boolean wrap) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
// 获取默认的拓展实现类, 即@SPI注解上的默认实现类, 如@SPI("xxx")
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, wrap);
// 设置实例到 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 的实例。每次取实例的时候会优先从缓存中取,取不到则从配置中加载,实例化并缓存到内存中。
2.3. createExtension()
下面我们来看一下创建拓展对象的过程:
public class ExtensionLoader<T> {
// ...
/**
* 扩展实例存入内存中缓存起来;key=扩展类 ;value=扩展类实例
*/
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
/**
* 创建拓展类实例,包含如下步骤
* 1. 通过 getExtensionClasses 获取所有的拓展类,从配置文件加载获取拓展类的map映射
* 2. 通过反射创建拓展对象
* 3. 向拓展对象中注入依赖(IOC)
* 4. 将拓展对象包裹在相应的 Wrapper 对象中(AOP)
* @param name 需要获取的配置文件中拓展类的key
* @return 拓展类实例
*/
@SuppressWarnings("unchecked")
private T createExtension(String name, boolean wrap) {
// 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的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);
if (wrap) {
// 从缓存中取出所有的包装类,形成包装链
List<Class<?>> wrapperClassesList = new ArrayList<>();
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
}
if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
// 循环创建 Wrapper 实例,形成 Wrapper 包装链
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
}
}
// 初始化实例并返回
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
}
创建拓展类对象步骤分别为:
-
通过 getExtensionClasses 从配置文件中加载所有的拓展类,再通过名称获取目标拓展类; -
通过反射创建拓展对象; -
向拓展对象中注入依赖; -
将拓展对象包裹在相应的 Wrapper 对象中。
第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。我们先重点分析 getExtensionClasses() 方法的逻辑。
2.4. getExtensionClasses()
在通过 name 获取拓展类之前,首先需要根据配置文件解析出拓展项名称与拓展类的映射 map,之后再根据拓展项名称从 map 中取出相应的拓展类即可。getExtensionClasses() 方法源码如下:
public class ExtensionLoader<T> {
// ...
/**
* 扩展点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() 加载拓展类,缓存避免了多次读取配置文件的耗时。
2.5. loadExtensionClasses()
下面分析 loadExtensionClasses() 方法加载配置文件的逻辑:
public class ExtensionLoader<T> {
// ...
private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
/**
* Load all {@link Prioritized prioritized} {@link LoadingStrategy Loading Strategies} via {@link ServiceLoader}
*
* @return non-null
* @since 2.7.7
*/
private static LoadingStrategy[] loadLoadingStrategies() {
return stream(load(LoadingStrategy.class).spliterator(), false)
.sorted()
.toArray(LoadingStrategy[]::new);
}
/**
* synchronized in getExtensionClasses
*/
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// 根据加载策略,加载指定文件夹下的配置文件
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
}
return extensionClasses;
}
}
loadExtensionClasses() 方法总共做了两件事情:
-
首先该方法调用 cacheDefaultExtensionName() 对 SPI 注解进行解析,获取并缓存接口的 @SPI 注解上的默认拓展类在 cachedDefaultName。 -
再调用 loadDirectory() 方法加载指定文件夹配置文件。
SPI 注解解析过程比较简单,源码如下。只允许一个默认拓展类。
public class ExtensionLoader<T> {
// ...
/**
* extract and cache default extension name if exists
*/
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("More than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
// 设置默认拓展类名称
if (names.length == 1) {
cachedDefaultName = names[0];
}
}
}
}
从源码中可以看出 loadExtensionClasses() 方法加载配置文件的路径有三个,分别为 META-INF/dubbo/internal/, META-INF/dubbo/, META-INF/services/ 三个文件夹。方法源码如下:
public class ExtensionLoader<T> {
// ...
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
loadDirectory(extensionClasses, dir, type, false, 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, boolean overridden, String... excludedPackages) {
// 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, overridden, excludedPackages);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
}
首先找到文件夹下的配置文件,文件名需为接口全限定名。利用类加载器获取文件资源链接,再解析配置文件中配置的实现类添加到 extensionClasses 中。我们继续看 loadResource() 是如何加载资源的:
public class ExtensionLoader<T> {
// ...
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
try {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
String line;
String clazz = null;
// 按行读取配置内容
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();
clazz = line.substring(i + 1).trim();
} else {
clazz = line;
}
if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages)) {
// 通过反射加载类,并通过 loadClass 方法对类进行缓存
loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden);
}
} 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);
}
}
}
loadResource() 方法用于读取和解析配置文件,按行读取配置文件,每行以等于号 = 为界,截取键与值,并通过反射加载类,最后通过 loadClass() 方法加载扩展点实现类的 class 到 map 中,并对加载到的 class 进行分类缓存。loadClass() 方法实现如下:
public class ExtensionLoader<T> {
// ...
/**
* 加载扩展点实现类的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,
boolean overridden) throws NoSuchMethodException {
// 判断配置的实现类是否是实现了type接口
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 注解,表示这个类就是一个自适应实现类,缓存到cachedAdaptiveClass
if (clazz.isAnnotationPresent(Adaptive.class)) {
// 检测 clazz 是否是 Wrapper 类型,判断依据是是否有参数为该接口类的构造方法,缓存到cachedWrapperClasses
cacheAdaptiveClass(clazz, overridden);
} 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("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) {
// 缓存扩展点实现类class和扩展点名称的对应关系
cacheName(clazz, n);
// 最后将class存入extensionClasses
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}
}
loadClass() 方法实现了扩展点的分类缓存功能,如包装类,自适应扩展点实现类,普通扩展点实现类等分别进行缓存。
2.6. @Adaptive
需要注意的是自适应扩展点实现类 @Adaptive 注解,该注解源码如下:
/**
* Provide helpful information for {@link ExtensionLoader} to inject dependency extension instance.
*
* @see ExtensionLoader
* @see URL
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
/**
* Decide which target extension to be injected. The name of the target extension is decided by the parameter passed
* in the URL, and the parameter names are given by this method.
* <p>
* If the specified parameters are not found from {@link URL}, then the default extension will be used for
* dependency injection (specified in its interface's {@link SPI}).
* <p>
* 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>
* </ol>
* If the parameter names are empty, then a default parameter name is generated from interface's
* class name with the rule: divide classname from capital char into several parts, and separate the parts with
* dot '.', for example, for {@code org.apache.dubbo.xxx.YyyInvokerWrapper}, the generated name is
* <code>String[] {"yyy.invoker.wrapper"}</code>.
*
* @return parameter names in URL
*/
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 中,到此,关于缓存类加载的过程就分析完了。
2.7. Dubbo AOP
当执行完 injectExtension(T instance) 方法,在 createExtension(String name)就开始执行 wrapper 的包装,类似于 Spring 中的 AOP,Dubbo 运用了装饰器模式。
if (wrap) {
List<Class<?>> wrapperClassesList = new ArrayList<>();
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
}
if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
}
}
这里的 cachedWrapperClasses 通过前面的分析已经知道,就是在解析配置文件时判断是否是 Wrapper 类型的拓展类,++ 判断依据为构造方法中是否有参数为该接口类 ++,则缓存到 cachedWrapperClasses。
执行 wrapperClass.getConstructor(type).newInstance(instance) 将获取包装类的构造方法,方法的参数就是该接口类型,并通过反射生成一个包含该拓展类实例的包装对象,再通过 injectExtension 注入包装对象的依赖。如此循环,得到成 Wrapper 包装链。这里需注意的是, 配置文件中内容靠后的包装类会包装在相对外层。
我们用 Dubbo AOP 类似的方法,改造在 章节1 中的代码,试试效果:
-
先添加 POM 文件依赖:
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.9</version>
</dependency>
-
修改 Promotion 接口,加上 @SPI 标签:
import org.apache.dubbo.common.extension.SPI;
import java.math.BigDecimal;
@SPI
public interface Promotion {
BigDecimal getDiscount();
}
-
新建一个 PromotionWrapper 包装类:
public class PromotionWrapper implements Promotion {
private Promotion promotion;
public PromotionWrapper(Promotion promotion) {
this.promotion = promotion;
}
@Override
public BigDecimal getDiscount() {
System.out.println("操作之前");
BigDecimal result = promotion.getDiscount();
System.out.println("操作之后");
return result;
}
}
-
修改文件 com.yly.region.spi.Promotion,内容如下:
groupon=com.yly.region.spi.impl.GrouponPromotion
seckill=com.yly.region.spi.impl.SeckillPromotion
com.yly.region.spi.PromotionWrapper
-
新建一个测试类 TestAOP,使用 Dubbo 的 ExtensionLoader 运行看下结果:
public class TestAOP {
public static void main(String[] agrs) {
ExtensionLoader<Promotion> extensionLoader = ExtensionLoader.getExtensionLoader(Promotion.class);
Promotion promotion = extensionLoader.getExtension("groupon");
System.out.println(promotion.getDiscount());
}
}
-
可以看到运行结果:
操作之前
团购优惠一元
操作之后
1
运行结果与我们预想的一致,实现了 Wrapper 类的切面功能。
原文始发于微信公众号(白菜说技术):Dubbo 的 SPI 核心原理机制源码级解析
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/172720.html