本文主要介绍了 Dubbo 中的 SPI 机制。 文章部分参考自 Dubbo SPI 官方文档,并在此基础上进行了扩展深入。 涉及到Dubbo的源码分析, Dubbo版本是 2.8.7 。
SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。 在 Dubbo 中,SPI 是一个非常重要的模块 。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。接下来,我们先来了解一下 Java SPI 与 Dubbo SPI 的用法,然后再来分析 Dubbo SPI 的源码。
Java 中的 SPI 机制
前面简单介绍了 SPI 机制的原理,本节通过一个示例演示 Java SPI 的使用方法。首先,我们定义一个接口,名称为 Robot。
public interface Robot {
void sayHello();
}
接下来定义两个实现类,分别为 OptimusPrime 和 Bumblebee。
public class OptimusPrime implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Optimus Prime.");
}
}
public class Bumblebee implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Bumblebee.");
}
}
接下来 META-INF/services 文件夹下创建一个文件,名称为 Robot 的 全限定名 cn.mvbbb.spi.Robot。文件内容为实现类的全限定的类名,如下:
cn.mvbbb.spi.OptimusPrime
cn.mvbbb.spi.Bumblebee
做好所需的准备工作,接下来编写代码进行测试。
public class JavaSPITest {
@Test
public void sayHello() throws Exception {
ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
System.out.println("Java SPI");
// 遍历
serviceLoader.forEach(Robot::sayHello);
}
}
对上面代码的解释:
JDK中,基于SPI的思想,提供了默认具体的实现,ServiceLoader
,位于java.util
包中,实现了 Iterable 接口,可以解析 /META-INF/services/
目录下的配置文件完成对接口具体实现类的实例化。ServiceLoader#load
方法可以加载指定类型(接口)的实现类。
Java SPI机制:ServiceLoader实现原理及应用剖析
执行上述代码:
从测试结果可以看出,我们的两个实现类被成功的加载,并输出了相应的内容。关于 Java SPI 的演示先到这里,接下来演示 Dubbo SPI。
Dubbo中SPI的基本流程与使用
@SPI 注解
Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下。
optimusPrime = cn.mvbbb.spi.OptimusPrime
bumblebee = cn.mvbbb.spi.Bumblebee
与 Java SPI 实现类配置不同,Dubbo SPI 是通过 键值对 的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时, 需要在 Robot 接口上标注 @SPI 注解 。下面来演示 Dubbo SPI 的用法:
public class DubboSPITest {
@Test
public void sayHello() throws Exception {
// 获取扩展点加载器
ExtensionLoader<Robot> extensionLoader =
ExtensionLoader.getExtensionLoader(Robot.class);
// 获取扩展点
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
Robot bumblebee = extensionLoader.getExtension("bumblebee");
bumblebee.sayHello();
}
}
对上述代码的解释:
Dubbo 中的 SPI 机制是 ExtensionLoader(扩展点加载器) 实现的,作用是加载所有打上了 @SPI 注解的接口,并根据配置进行实例化、封装,Dubbo本身的服务调用,暴露等功能也是使用 ExtensionLoader 实现的。简单点说,他就是拿来创建对象的。ExtensionLoader#getExtension
可以根据指定的 key 来获取实现类(扩展点)。
执行上述代码。测试结果如下:
Dubbo SPI 除了支持按需加载接口实现类,还增加了 IOC 和 AOP 等特性,这些特性将会在接下来章节中一一进行介绍。
@Adaptive 注解
dubbo扩展机制有三个重要的注解,分别是:@SPI,@Adaptive,@Activate。此前,我们已经了解了 @SPI 注解,现在来看看 @Adaptive 注解。
@Adaptive标注一般标注在接口的方法上,也可以标注在类,枚举类,方法上,但是比较少见。
@Adaptive标注在接口方法上时,要求接口方法入参中有一个URL,这个URL类是 org.apache.dubbo.common.URL,注解的功能是可以根据方法中URL的入参,来选择对哪一个实现进行调用,使用如下:
-
首先给接口的方法上加 @Adaptive 注解,设置 value 的默认值为 key1
-
之后在调用接口方法时需要传入 URL ,其中需要指定 key1 参数的值,为
/META-INF/dubbo
配置文件下的其中一个键。
修改接口方法:
@SPI
public interface Robot {
@Adaptive(value = "key1")
void sayHello(URL url);
}
测试方法:
URL url = URL.valueOf("dubbo://localhost:8080?key1=bumblebee");
ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
Robot robot = extensionLoader.getAdaptiveExtension();
robot.sayHello(url);
与本文开篇直接在 getExtension 不同,使用 @Adaptive 注解需要在方法参数中添加 URL。目的是为了灵活加载接口的实现类,URL中的参数不同,接口的加载类也就不同。
Dubbo中AOP的源码实现
上一章简单演示了 Dubbo SPI 的使用方法。我们首先通过 ExtensionLoader 的 getExtensionLoader 方法获取一个 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。
ExtensionLoader#getExtensionLoader 方法源码:
@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> 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() + "!");
}
// EXTENSION_LOADERS: ConcurrentMap 类型, 维护 类型->ExtensionLoader 的映射. 做缓存作用
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
可以看到 getExtensionLoader 方法并没有涉及到加载类的过程。getExtensionLoader 方法用于从缓存中获取与拓展类对应的 ExtensionLoader,若缓存未命中,则创建一个新的实例。
接着查看 ExtensionLoader#getExtension 方法
/**
* 根据给定的key获取扩展点, 没有找到就抛出异常
*/
@SuppressWarnings("unchecked")
public T getExtension(String name) {
return getExtension(name, true);
}
public T getExtension(String name, boolean wrap) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
// 获取默认的拓展实现类
return getDefaultExtension();
}
// Holder,顾名思义,用于持有目标对象, 包装了目标对象
// getOrCreateHolder 从 cachedInstances 缓存中取出 Holder
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
// double check 双重检查
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 创建拓展实例
instance = createExtension(name, wrap);
holder.set(instance);
}
}
}
return (T) instance;
}
上面的代码的主要逻辑是,先检查缓存未命中则创建拓展对象,将其包装为 Holder,放入缓存。
有两个地方需要说明
- ExtensionLoader#getDefaultExtension 方法
- ExtensionLoader#createExtension 方法。
关于 ExtensionLoader#getDefaultExtension 方法:
public T getDefaultExtension() {
// 加载拓展类
getExtensionClasses();
// cacheDefaultName 为 @SPI 注解中的内容。也就是说必须在 @SPI 注解中指定一个名称,否则会发生空指针异常
if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
return null;
}
// 根据 @SPI 注解获取扩展类
return getExtension(cachedDefaultName);
}
什么时候会调用 ExtenstionLoader#getDefaultExtension 方法? 当调用 ExtensionLoader#getExtension 方法时只传入了一个 “true” 。其中的 getExtensionClassed 方法在之后讲解。
之后讲 ExtensionLoader#createExtension 方法。ExtensionLoader#createExtension 的源码如下:
@SuppressWarnings("unchecked")
private T createExtension(String name, boolean wrap) {
// getExtensionClasses 从配置文件中加载所有的扩展类, 维护 配置项名称->配置类类型 的映射
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
// EXTENSION_INSTANCES 中维护了从 类型->ExtensionLoader 的映射
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 向扩展对象中注入依赖, Dubbo IOC 的体现
injectExtension(instance);
// Dubbo AOP 的体现
// 如果是 Wapper类就将扩展对象包裹在相应的 Wapper 对象中。
// 比如我基于 Protocol 定义了 DubboProtocol 的扩展,但实际上在 Dubbo 中不是直接使用的 DubboProtocol,
// 而是其包装类ProtocolListenerWrapper
if (wrap) {
List<Class<?>> wrapperClassesList = new ArrayList<>();
// loadExtensionClasses 调用之后会缓存 cachedWrapperClasses。 cachedWrapperClasses 是一个 类型set
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
}
// 循环创建 Wrapper 实例
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 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
// 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
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);
}
}
ExtensionLoader#createExtension 方法的逻辑如下:
- 通过 getExtensionClasses 获取所有的拓展类
- 通过反射创建拓展对象
- 向拓展对象中注入依赖
- 将拓展对象包裹在相应的 Wrapper 对象中
Dubbo 是就是通过第 3 步实现 IOC,第 4 步实现 AOP 。
一句话总结 Dubbo 中的 AOP 是如何实现的:循环将一个实例当作另外一个类的构造参数然后实例化注入。
接下来看 Dubbo 是如何实现 IOC 的。
Dubbo中的依赖注入的源码实现
不同于 Spring 可以使用 @Autowaired 注解注入依赖,Dubbo IOC 通过 setter 方法注入依赖。
Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。整个过程(ExtensionLoader#injectExtension 方法)对应的代码如下:
private T injectExtension(T instance) {
// objectFactory 是什么? 稍后解释
if (objectFactory == null) {
return instance;
}
try {
// 遍历类中的所有方法, 筛选出 setter方法. setter 的特征: set开头, 一个入参, public 修饰.
for (Method method : instance.getClass().getMethods()) {
if (!isSetter(method)) {
continue;
}
/**
* Check {@link DisableInject} to see if we need auto injection for this property
* setter 方法上添加 @DisableInject 注解禁用 Dubbo 注入
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
// 获取第一个参数,判断是否是原始类型及其包装类
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
// 获取属性名,比如 setName 方法对应属性名 name
String property = getSetterProperty(method);
// 从 ObjectFactory 中获取依赖对象
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
// 通过反射调用 setter 方法设置依赖
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
在上面代码中,objectFactory 变量的类型 ExtensionFactory。Dubbo 中 ExtensionFactory 的实现类共有 3 种, 分别是 AdaptiveExtensionFactory、SpiExtensionFactory、SpringExtensionFactory。ExtensionFactory 中的作用是根据类型和名字来获取对象。
我们可以见得 Dubbo IOC 的核心就是 ExtensionFactory。
先来看看 SpiExtensionFactroy 的源代码:
/**
* SpiExtensionFactory
*/
public class SpiExtensionFactory implements ExtensionFactory {
@Override
public <T> T getExtension(Class<T> type, String name) {
// type类型是否为接口类型, 是否被 SPI 注解修饰
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
// 获取 type 的 ExtensionLoader(用于创建对象的)
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
// 这个 type 的扩展点不为空
if (!loader.getSupportedExtensions().isEmpty()) {
// 加载自适应扩展类
return loader.getAdaptiveExtension();
}
}
return null;
}
}
对 Dubbo 中 IOC 实现的源码大概就深入到这里,做一下 总结 :
Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。
我们需要重点关注的是 ObjectFactory 是如何被加载的,ObjectFactory 是 ExtensionFactory 接口类型,该接口有三个实现类,在其中一个实现类 SpiExtensionFactory 中调用了 ExtensionFactory#getAdaptiveExtension 方法。我们接下来就讲一讲这个方法的由来和作用。
Dubbo 中的自适应拓展点
在 Dubbo中SPI的基本流程与使用 中我们介绍了 @Adaptive 注解使用方法,这种通过 URL 参数来指定实现类的方式就是基于 Dubbo 底层的自适应扩展点。在对自适应拓展进行深入分析之前先来看看 @Adaptive 注解:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
String[] value() default {};
}
从上面的代码中可知,Adaptive 可注解在类或方法上。当 Adaptive 注解在类上时,Dubbo 不会为该类生成代理类。注解在方法(接口方法)上时,Dubbo 则会为该方法生成代理逻辑。Adaptive 注解在类上的情况很少,在 Dubbo 中,仅有两个类被 Adaptive 注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory。此种情况,表示拓展的加载逻辑由人工编码完成。更多时候,Adaptive 是注解在接口方法上的,表示拓展的加载逻辑需由框架自动生成。Adaptive 注解的地方不同,相应的处理逻辑也是不同的。注解在类上时,处理逻辑比较简单,本文就不分析了。注解在接口方法上时,处理逻辑较为复杂,本节将会重点分析此块逻辑。
ExtensionLoader#getAdaptiveExtension 方法用于加载自适应扩展类。 自适应扩展类的含义是说,基于参数,在运行时动态选择到具体的目标类,然后执行。 在 Dubbo 中,很多拓展都是通过 SPI 机制进行加载的,比如 Protocol、Cluster、LoadBalance 等。有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。这听起来有些矛盾。拓展未被加载,那么拓展方法就无法被调用(静态方法除外)。拓展方法未被调用,拓展就无法被加载。对于这个矛盾的问题,Dubbo 通过自适应拓展机制很好的解决了。自适应拓展机制的实现逻辑比较复杂,首先 Dubbo 会为拓展接口生成具有代理功能的代码。然后通过 javassist 或 jdk 编译这段代码,得到 Class 类。最后再通过反射创建代理类,整个过程比较复杂。
@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
// 从缓存中获取自适应拓展 cachedAdaptiveInstance: Holder<T>
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
// 如果存在异常,则直接抛出
if (createAdaptiveInstanceError != null) {
throw new IllegalStateException("Failed to create adaptive instance: " +
createAdaptiveInstanceError.toString(),
createAdaptiveInstanceError);
}
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
// double check
if (instance == null) {
try {
// 创建自适应拓展
// 这里分为两种情况:一种是存在 Adaptive 类,另一个是需要生成 Adaptive 类
instance = createAdaptiveExtension();
// 设置自适应扩展到缓存中
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
}
return (T) instance;
}
getAdaptiveExtension 方法首先会检查缓存,缓存未命中,则调用 createAdaptiveExtension 方法创建自适应拓展。下面,我们看一下 createAdaptiveExtension 方法的代码。
@SuppressWarnings("unchecked")
private T createAdaptiveExtension() {
try {
// 获取自适应拓展类,并通过反射实例化
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
createAdaptiveExtension 方法的代码比较少,但却包含了三个逻辑,分别如下:
- 调用 getAdaptiveExtensionClass 方法获取自适应拓展 Class 对象
- 通过反射进行实例化
- 调用 injectExtension 方法向拓展实例中注入依赖
前两个逻辑比较好理解,第三个逻辑用于向自适应拓展对象中注入依赖。这个逻辑看似多余,但有存在的必要,这里简单说明一下。前面说过,Dubbo 中有两种类型的自适应拓展,一种是手工编码的,一种是自动生成的。手工编码的自适应拓展中可能存在着一些依赖,而自动生成的 Adaptive 拓展则不会依赖其他类。这里调用 injectExtension 方法的目的是为手工编码的自适应拓展注入依赖,这一点需要大家注意一下。关于 injectExtension 方法,前文已经分析过了,这里不再赘述。接下来,分析 getAdaptiveExtensionClass 方法的逻辑。
private Class<?> getAdaptiveExtensionClass() {
// 通过 SPI 获取所有的拓展类
getExtensionClasses();
// 检查缓存,若缓存不为空,则直接返回缓存
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 创建自适应拓展类
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
getAdaptiveExtensionClass 方法同样包含了三个逻辑,如下:
- 调用 getExtensionClasses 获取所有的拓展类
- 检查缓存,若缓存不为空,则返回缓存
- 若缓存为空,则调用 createAdaptiveExtensionClass 创建自适应拓展类
这三个逻辑看起来平淡无奇,似乎没有多讲的必要。但是这些平淡无奇的代码中隐藏了着一些细节,需要说明一下。首先从第一个逻辑说起,getExtensionClasses 这个方法用于获取某个接口的所有实现类。比如该方法可以获取 Protocol 接口的 DubboProtocol、HttpProtocol、InjvmProtocol 等实现类。在获取实现类的过程中,如果某个实现类被 Adaptive 注解修饰了,那么该类就会被赋值给 cachedAdaptiveClass 变量。此时,上面步骤中的第二步条件成立(缓存不为空),直接返回 cachedAdaptiveClass 即可。如果所有的实现类均未被 Adaptive 注解修饰,那么执行第三步逻辑,创建自适应拓展类。相关代码如下:
private Class<?> createAdaptiveExtensionClass() {
// 构建自适应拓展代码
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
ClassLoader classLoader = findClassLoader();
// 获取编译器实现类
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
// 编译代码,生成 Class
return compiler.compile(code, classLoader);
}
createAdaptiveExtensionClass 方法用于生成自适应拓展类,该方法首先会生成自适应拓展类的源码,然后通过 Compiler 实例(Dubbo 默认使用 javassist 作为编译器)编译源码,得到代理类 Class 实例。接下来,我们把重点放在 代理类代码生成的逻辑 上,其他逻辑大家自行分析。
AdaptiveClassCodeGenerator#generate 方法生成扩展类代码
public String generate() {
// 如果该接口中没有方法被 @Adaptive 注解修饰,直接抛出异常
if (!hasAdaptiveMethod()) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
}
StringBuilder code = new StringBuilder();
// 生成包名、import、方法等.
code.append(generatePackageInfo());
code.append(generateImports());
code.append(generateClassDeclaration());
Method[] methods = type.getMethods();
for (Method method : methods) {
code.append(generateMethod(method));
}
code.append("}");
if (logger.isDebugEnabled()) {
logger.debug(code.toString());
}
return code.toString();
}
AdaptiveClassCodeGenerator#generate 中涉及到了多个方法:
1、AdaptiveClassCodeGenerator#hasAdaptiveMethod 方法
private boolean hasAdaptiveMethod() {
// 非常优雅的 stream 流
return Arrays.stream(type.getMethods()).anyMatch(m -> m.isAnnotationPresent(Adaptive.class));
}
2、generatePackageInfo、generateImports、generateClassDeclaration 方法用于生成类。以 Dubbo 的 Protocol 接口为例,生成的代码如下:
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
// 省略方法代码
}
3、AdaptiveClassCodeGenerator#generateMethod 用于生成方法,在接下来的重点分析
生成方法
private String generateMethod(Method method) {
String methodReturnType = method.getReturnType().getCanonicalName();
String methodName = method.getName();
// 生成方法内容
String methodContent = generateMethodContent(method);
String methodArgs = generateMethodArguments(method);
String methodThrows = generateMethodThrows(method);
return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
}
generateMethodContent 分析
private String generateMethodContent(Method method) {
// 该方法上必须有 @Adaptive 注解修饰
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512);
if (adaptiveAnnotation == null) {
// 没有 @Adaptive 注解修饰,生成异常信息
return generateUnsupported(method);
} else {
// 获取 URL 在参数列表上的索引
int urlTypeIndex = getUrlTypeIndex(method);
if (urlTypeIndex != -1) {
// 如果参数列表上存在 URL,生成对 URL 进行空检查
code.append(generateUrlNullCheck(urlTypeIndex));
} else {
// 如果参数列表不存在 URL 类型的参数,那么就看参数列表上参数对象中是否包含 getUrl 方法
// 有的话,生成 URL 空检查
code.append(generateUrlAssignmentIndirectly(method));
}
// 解析 Adaptive 注解上的 value 属性
String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
// 如果参数列表上有 Invocation 类型的参数,生成空检查并获取 methodName.
boolean hasInvocation = hasInvocationArgument(method);
code.append(generateInvocationArgumentNullCheck(method));
// 这段逻辑主要就是为了生成 extName(也就是扩展名)
// 分为多种情况:
// 1.defaultExtName 是否存在
// 2.参数中是否存在 invocation 类型参数
// 3.是否是为 protocol 生成代理
// 为什么要对 protocol 单独考虑了?因为 URL 中有获取 protocol 值的方法
code.append(generateExtNameAssignment(value, hasInvocation));
// check extName == null?
code.append(generateExtNameNullCheck(value));
// 生成获取扩展(使用 ExtensionLoader.getExtension 方法)
code.append(generateExtensionAssignment());
// 生成返回语句
code.append(generateReturnAndInvocation(method));
}
return code.toString();
}
上面那段逻辑主要做了如下几件事:
- 检查方法上是否 Adaptive 注解修饰
- 为方法生成代码的时候,参数列表上要有 URL(或参数对象中有 URL)
- 使用 ExtensionLoader.getExtension 获取扩展
- 执行对应的方法
#todo javassist
#todo 打断点完善本部分的内容
扩展 - Dubbo 获取所有扩展类
我们在通过名称获取拓展类之前,首先需要根据配置文件解析出拓展项名称到拓展类的映射关系表(Map<名称, 拓展类>),之后再根据拓展项名称从映射关系表中取出相应的拓展类即可。ExtensionLoader#getExtensionClasses 的作用就是加载名称->扩展类的映射关系到缓存中。相关过程的代码分析如下:
private Map<String, Class<?>> getExtensionClasses() {
// 从缓存中获取已加载的拓展类
Map<String, Class<?>> classes = cachedClasses.get();
// double check
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
// 加载扩展类
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
下面分析 ExtensionLoader#loadExtensionClasses 方法的逻辑。源代码如下:
/**
* synchronized in getExtensionClasses
*/
private Map<String, Class<?>> loadExtensionClasses() {
// 获取SPI注解的内容, 目的是做错误校验
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;
}
逻辑比较简单:
- ExtensionLoader#cacheDefaultExtensionName 方法的作用是检查 @SPI 注解内容,比如说
@SPI(value=“apple,food”)
时就应该抛出异常。 - ExtensionLoader#loadDirectory 的作用是根据 LoadingStrategy 加载目录下的配置文件
ExtensionLoader#loadDirectory 方法中调用了 ExtensionLoader#loadResource 方法,ExtensionLoader#loadResoure 的部分源代码如下:
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;
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 && !isExcluded(line, excludedPackages)) {
// 加载配置文件中的一行
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);
// 。。。。。。。。。。
主要逻辑是:
- 根据文件路径读取文件
- 剔除注释
- 提取键值对
- 判断包是否被排除在外
- 加载类
主要看 ExtensionLoader#loadClass 方法,上源代码
// 重要的两个入参 class类型,name名称
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
boolean overridden) throws NoSuchMethodException {
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 注解
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz, overridden);
}
// 如果该类是一个 Wapper 类: 该类名以 Wapper 结尾,构造函数的参数中包含其他扩展点
else if (isWrapperClass(clazz)) {
// 将该类缓存到 cachedWrapperClasses 中
cacheWrapperClass(clazz);
} else {
// 程序进入此分支,表明 clazz 是一个普通的拓展类
// 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
// 如果 name 为空,则尝试从 Extension 注解中获取 name,或使用小写的类名作为 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 注解,则使用 names 数组的第一个元素作为键,
// 存储 name 到 Activate 注解对象的映射关系
cacheActivateClass(clazz, names[0]);
for (String n : names) {
// 存储 Class 到名称的映射关系
cacheName(clazz, n);
// 存储名称到 Class 的映射关系
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}
逻辑分析如下:
- loadClass 接收到入参 Clazz 类型。
- 对于 @Adaptive 修饰的类,忽略配置文件,将其加入到 @Adaptive 的缓存 cachedAdaptiveClass 中
- 对于 Wapper类,将其缓存到 cachedWrapperClasses 中
- 对于普通的扩展类,读取 name,储存两个映射关系:
- Class 到名称的映射关系
- 名称到 Class 的映射关系
经过如上的流程,下次调用 ExtensionLoader#getExtensionClasses 的方法时,就能直接从 cachedClasses 缓存中获取到所有的名称到类型的映射。
源码总结&TIPS
JDK的标准SPI对比dubbo的SPI
JDK SPI
JDK 标准的 SPI 会一次性加载所有的扩展实现,如果有的扩展加载很耗时,但也没用上,很浪费资源。所以只希望加载某个的实现,就不现实了
DUBBO SPI
1,对 Dubbo 进行扩展,不需要改动 Dubbo 的源码
2,延迟加载,可以一次只加载自己想要加载的扩展实现。
3,增加了对扩展点 IOC 和 AOP 的支持,一个扩展点可以直接 setter 注入其
它扩展点。
3,Dubbo 的扩展机制能很好的支持第三方 IoC 容器,默认支持 Spring Bean。