Dubbo源码解析必读篇 — Dubbo SPI扩展机制
Java SPI
在阅读本篇文章前,先介绍下 Java的 SPI机制, 典型的案例如: jdbc
在我们获取数据库连接时,需要写下面两行代码
//1.加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
//2. 获得数据库连接
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
但是如果我们不写 第一行代码,同样可以获取数据库连接,原因就是使用了Java SPI 机制。
在DriverManager
类的 static 方法中, 会通过 ServiceLoader.load(Driver.class)
来加载 跟资源路径 /META-INF/services/ 文件夹下的文件, 通过解析这些文件,来动态加载文件中配置好的 Driver
驱动实现类:Class.forName(aDriver, true, loader)
。
Dubbo SPI 介绍
Dubbo 并没有使用 Java SPI, 而是重新实现了一套更强的SPI机制。
在我看来,Dubbo SPI 借鉴了 Java SPI 动态加载实现类机制 和 Spring 的 IOC 和 AOP 的思想。
Dubbo SPI 机制特点:
- 通过配置文件动态加载接口实现类
- 有管理 实现类 实例化后对象的 容器
- IOC
- AOP
这里 有个 URL总线的概念, 这个URL 是 Dubbo 自己的一个类,该类非常关键,它是贯穿了整个Dubbo 服务提供者与服务消费者 启动流程及 RPC调用 的一个关键点。后面会在源码分析中,慢慢渗透体会该URL,这里只要记住 URL是个总线。
Dubbo SPI 原理
Dubbo SPI案例:
与 Java SPI 类似, Dubbo SPI 也需要在 rescourse/META-INF/services/ 文件夹中先有相关接口的配置文件。
配置文件名 必须是 该接口类的全限定类名, 如:com.zzp.dubbo.spi.api.Car
该文件中 使用 key : value 键值对的方式, 声明 接口实现类的 名字 和 其全限定类名
red = com.zzp.dubbo.spi.impl.RedCar
black = com.zzp.dubbo.spi.impl.BlackCar
下面有两行代码,目的是为了获取接口实现类对象。
// 1. 获取接口类 Car.class 的 拓展加载器实例
ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
// 2. 通过该接口类的拓展加载器实例, 获取名叫 red 的 Car.class接口的实现类。
Car redCar = extensionLoader.getExtension("red");
通过上面的基本案例,使用伪代码简单说明下,Dubbo SPI 获取 目标接口实现类对象的 原理过程:
-
ExtensionLoader.getExtensionLoader(Car.class)
//从缓存中 获取 Car.class 接口的 ExtensionLoader实例. ExtensionLoader<Car> extensionLoader = null; // 缓存中 存在 则直接返回 if(extensionLoader = ExtensionLoaderCache.get(Car.class)){ return extensionLoader; }else{ // 缓存中 不存在, 则创建一个放入缓存中, 并返回 extensionLoader = new ExtensionLoader(Car.class) ExtensionLoaderCache.put(extensionLoader); return extensionLoader; }
-
extensionLoader.getExtension("red")
// 实例 缓存容器 Map<String,Object> cachedInstances = new HashMap(); // 获取实例 getExtension(String name){ // 从缓存中 获取 名叫 red 的实例 Car instance = null; // 缓存中有,则直接返回 if( instance = cachedInstances.get(name)){ return instance; }else{ // 缓存中若没有,则创建一个存入缓存中,并返回。 instance = createInstance(name); CachedInstances.put(instance); return instance; } } // 解析配置文件结果的缓存 Map<String, Class<?>> extensionClasses = new HashMap(); // 创建实例 createInstance(String name){ // 加载解析 rescoures/META-INF 中的文件,获取 该接口 所有实现类的全限定类名 extensionClasses = loadClasses(Car.class); Class clazz= extensionClasses.get(name); // 通过反射创建实例对象 return clazz.newInstance(); }
上面通过伪代码 简单介绍了 Dubbo SPI 的一个基本原理, 可以从中看到,使用了大量的 缓存(Map) 来存储 各种结果。 总的来说 就是 加载 配置文件,通过反射创建实例对象。 但是 其中还有 IOC 与 AOP 等实现,在后面的源码分析中会进行讲解。
Dubbo SPI 源码分析
Dubbo SPI 机制 实际上全程 就围绕着 ExtensionLoader 类 。 首先我们来看一下 ExtensionLoader类的 成员变量。
1.ExtensionLoader成员变量
// =================直到下一个分割线之间 都是 static修饰的全局变量和常量=====================
// 日志
private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
// 下面三个 是 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/";
// 每个接口类的 拓展载器 的缓存
// 例如:{
// Car.class : ExtensionLoader<Car>,
// Driver.class : ExtensionLoader<Driver>
// }
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
// 可扩展实例的缓存 class -> object 相当于一个总的缓存表,缓存所有接口类的 实现类对象
// 例如: {
// RedCar.class : redCar
// BlackCar.class : blackCar
// }
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
// ============================== 分割线 =========================================
// 该ExtesionLoader实例 所管理的 接口类
private final Class<?> type;
// 后面会介绍
private final ExtensionFactory objectFactory;
//
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
// 缓存 每个实现类的 名字与全限定类名的关系 beanName -> Class
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
//
private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
// 缓存 实现类名字 与 实现类对象的关系 beanName -> Object
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
// 自适应拓展实例缓存
private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
private volatile Class<?> cachedAdaptiveClass = null;
上面的变量 能理解最好, 不理解的话 待到后面的 方法源码中 仔细体会。 总之,Dubbo SPI 使用了大量的缓存,来提升性能,避免每次获取都要重新加载或者创建。
2.获取拓展加载器
在Dubbo SPI 原理的 示例中,有两行代码, 首先我们以 第一行代码ExtensionLoader.getExtensionLoader(Car.class);
为入口,来分析全过程。
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
// ... 省略的部分 是做一些检查判断, 不重要
// 从全局 EXTENSION_LOADERS 缓存中获取 该 接口类 的 拓展加载器。
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;
}
上面主要就是,从缓存中获取 目标 接口类 的 拓展加载器。 关键在于 创建 ExtensionLoader 的过程。
下面我们来看 创建 ExtensionLoader 的代码
private ExtensionLoader(Class<?> type) {
// 接口类 赋值
this.type = type;
// ExtensionFatory 表示拓展机制的工厂 (在Dubbo里面有SPI扩展机制,也有Spring扩展机制)
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
上面代码简单明了,就是属性赋值。 重点看一下在 objectFacotry 属性赋值时 ,实际上分为两步:
- ExtensionLoader.getExtensionLoader(ExtensionFactory.class) :又通过上面的方法加载了一次,当然初次是从缓存中获取不到的,所以又会进入该 构造方法内, 而此时 type == ExtensionFactory.class, 则 ExtensionFactory 的 拓展加载器实例 中 objectFactory属性 为 null 。
- getAdaptiveExtension() : 通过 ExtensionFactory 的 拓展加载器 获取 其 自适应拓展实例 ,且最终将结果赋值给 那个初次创建拓展加载器对象的 objectFactory属性。
有点绕很正常,仔细梳理下,其实并不难, 最终我们会把 目标 锁定在 ExtensionLoader
3.获取自适应拓展实例
public T getAdaptiveExtension() {
// 从 自适应拓展实例缓存中 获取
Object instance = cachedAdaptiveInstance.get();
// DCL 检查
if (instance == null) {
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
// 创建 自适应拓展 实例
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
上面代码 很简单,主要就是 DCL缓存 的那一套代码。 最终将目标 锁定在 createAdaptiveExtension() 创建 自适应拓展 实例 上。
// 创建 自适应拓展 实例
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);
}
}
上面代码可以分为以下几个步骤看:
- getAdaptiveExtensionClass() : 获取自适应拓展类的 Class 。
- newInstance() : 通过 反射 创建 实例
- injectExtension() : IOC 依赖注入
newInstance 这个很好理解,就是简单的通过反射创建实例, 下面我们主要看 1,3两个步骤。
首先来看 步骤1 getAdaptiveExtensionClass()
// 获取自适应拓展类的Class 类
private Class<?> getAdaptiveExtensionClass() {
// 加载解析配置文件
// 1.将 可拓展类(正常的类) 缓存到 cachedClasses中
// 2.将 自适应拓展类(@Adaptive标注的类) 缓存到 cachedAdaptiveClass中
// 3.将 包装类(有参数为接口类的构造方法的类) 缓存到 cachedWrapperClasses 中
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 如果没有用户配置文件中没有自定义的 自适应拓展类,
// 那么Dubbo就会帮我们生成一个 自适应拓展类
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
上面代码很短, 但做了详细的注释,介绍了其中两个方法主要的目的。
- 加载解析 /resourse/META-INF/xxx/ 下的配置文件,解析分别获取 可拓展类, 自适应拓展类 和 包装类。
- 若没有用户自定义的拓展类,则Dubbo默认为我们生成一个。
4.加载解析配置文件
接着我们跟进 getExtensionClasses() 看看如何加载解析配置文件的。
// ============== 缓存 DCL 那一套模板代码 ========================
private Map<String, Class<?>> getExtensionClasses() {
// cachedClasses 缓存中 存储了 配置文件中对应的 name 和 Class类
Map<String, Class<?>> classes = cachedClasses.get();
// DCL
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
// 从配置文件中加载可拓展类
classes = loadExtensionClasses();
// 最终会将 解析出来的 可拓展类字典 合并到 cachedClasses 缓存中
cachedClasses.set(classes);
}
}
}
return classes;
}
上面又是先从缓存中获取,若获取不到则去加载解析配置文件,然后将结果存入缓存中。接着我们看 loadExtensionClasses()
private Map<String, Class<?>> loadExtensionClasses() {
// 缓存默认的 拓展类的名字 到 cacehdDefaultName中
// 具体就是 获取该 接口类 上的SPI注解中的 value值。
// 当value中只有一个值时,直接缓存。
// 当value中有多个值时,取第一个
cacheDefaultExtensionName();
/**
* 分别从 以下几个 文件夹中获取 该接口类的 信息
*
* "META-INF/dubbo/internal/"
*
* META-INF/dubbo/
*
* META-INF/services/
*/
Map<String, Class<?>> extensionClasses = new HashMap<>();
// 加载解析 指定路径下的文件
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
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;
}
上面代码中 cacheDefaultExtensionName() 方法比较简单,不做详细分析,实际上就是通过反射获取 接口类上@SPI注解中的 value值,然后存入cacheDefaultName 缓存中。
重点来看 loadDirectory() 方法
/**
* @param extensionClasses map 需要将解析结果存入该字典中
* @param dir 需要加载解析的文件目录
* @param type 需要加载解析的类型,也就是文件名
*/
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
// 组成具体的文件路径地址 (文件夹路径 + 接口全地址名)
// 例如: /META-INF/services/ + "org.apache.dubbo.common.extension.ExtensionFactory"
String fileName = dir + type;
try {
//
Enumeration<java.net.URL> urls;
// 获取类加载器, 当前用于加载文件, 后面会用于加载Class字节码
ClassLoader classLoader = findClassLoader();
// 加载 文件
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
// 遍历加载的文件
if (urls != null) {
while (urls.hasMoreElements()) {
// 获取加载后文件的URL地址,注意这里是 java.net.URL
java.net.URL resourceURL = urls.nextElement();
// 参数1: map
// 参数2: 类加载器
// 参数3: 文件地址等信息
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
上面代码没什么好细聊的,简单概括功能就是 :
- 生成文件名
- 加载文件
- 解析文件 loadResource(extensionClasses, classLoader, resourceURL)
跟进 loadResource(extensionClasses, classLoader, resourceURL)方法
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) {
// 文件其中一行例如: spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory
//参数1: extensionClasses map
//参数2: resourceURL 文件URL
//参数3: Class类 例: SpiExtensionFactory.class
//参数4: name 相当于beanName 例: spi
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
}
}
}
上面代码,主要就是 读取 对应接口类配置文件中 的每一行,按照 key ,value的形式解析出来,并加载 value(全限定类名)获取Class类。
目标接着锁定到 loadClass() 方法中
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) 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); // 缓存到 cachedAdaptiveClass(自适应拓展类缓存)
}
// 判断该实现类 是不是 包装类 ,通过该类的构造方法来判断(有一个可以传入type类型的构造方法就是包装类)
else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz); // 缓存到 cachedWrapperClasses(包装类缓存)
} else {
//TODO 为什么没有赋值,这里单纯的执行getConsructor() 什么意义?
// 我猜测 这里主要是检查 有没有构造器方法。否则会抛出NoSuchMethodException异常
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
// 当没有name时
// 先会取@Extension注解的value值作为name
// 若没有@Extension注解 则从截取其类名作为name
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
//若有多个beanName(car1,car2,car3 = com.xx.xx.xx) 切分beanName
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
// 若该类上有 @Activate注解 则 放到 激活类缓存中
cacheActivateClass(clazz, names[0]);
for (String n : names) {
// 缓存到 cacheNames (若有多个名字对应 一个实现类时,只缓存第一个名字)
//例如:{Red.class: "red"}
cacheName(clazz, n);
// 存入extensionClasses 字典中 例如:{"red":Red.class}
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
}
上述代码看上去长, 其实逻辑非常清晰,注释中都有对应的解释。 其主要工作简单说明下:
- 判断该类是否是 自适应拓展类,若是 加入缓存中
- 判断该类是否是 包装类 ,若是 加入缓存中
- 如果以上都不是,则属于 可拓展类(普通实现类) ,则缓存到 cachedNames ,并添加到 extensionClasses。
至此,加载解析配置文件的相关代码都 分析完毕。
再次总结下 加载解析配置文件 主要的工作有哪些:
- 缓存默认的拓展类名字(@SPI 的value值)到 cacehdDefaultName 中
- 将 可拓展类(正常的类) 缓存到 cachedClasses 和 cacheNames 中
- 将 自适应拓展类(@Adaptive标注的类) 缓存到 cachedAdaptiveClass中
- 将 包装类(有参数为接口类的构造方法的类) 缓存到 cachedWrapperClasses 中
目前我们的主线是 要从 ExtensionFactory 的 ExtensionLoader 拓展加载器中 获取 cachedAdaptiveClass 自适应拓展类。
因此我们先看一下 ExtensionFactory 该接口类
ExtensionFactory
/**
* 扩展工厂
* ExtensionFactory
*/
@SPI
public interface ExtensionFactory {
/**
* Get extension.
*
* @param type object type.
* @param name object name.
* @return object instance.
*/
<T> T getExtension(Class<T> type, String name);
}
另外还有 该资源路径下的 META-INF/ 配置文件, 该文件所属的文件路径为
resources/META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory
其内容为:
adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory
根据上面的信息 ,按照加载解析配置文件的流程 ,能得出以下几个结果:
-
ExtensionFactory 的@SPI 注解没有 value值, 则 cacheDefaultName 缓存为 null
-
配置文件中的AdaptiveExtensionFactory 上有 @Adaptive注解,则cachedAdaptiveClass 中缓存的是 AdaptiveExtensionFactory.class
-
配置文件中没有 符合包装类的 特征,则 cachedWrapperClasses 缓存为 null
-
配置文件中 SpiExtensionFactory 属于可拓展类,则会缓存到 cachedClasses 和 cacheNames
cachedClasses = {"spi": SpiExtensionFactory.class}
cacheNames = {SpiExtensionFactory.class : "spi"}
接下来,我们回到 getAdaptiveExtensionClass() 方法中, 加载解析配置文件后,会查看此次解析配置文件中 有没有缓存 cachedAdaptiveClass , 若没有 说明用户没有自定义自适应类,那么此时 需要 Dubbo 为我们生成一个 自适应拓展类。 而目前我们从 ExtensionFactory 接口类中 解析并得到了 cachedAdaptiveClass 为 AdaptiveExtensionFactory.class, 那么就可以直接返回。
那么就会走到 AdaptiveExtensionFactory.class 的 newInstance 方法 创建实例
然后 执行 injectExtension() 方法 ,进行依赖注入, 该方法 先不分析 ,因为 getAdaptiveExtensionClass() 方法中 还剩下一行代码,先分析它。
现在我们先抛开 ExtensionFactory 该接口, 假设有其它的 接口类,而它并没有 标有 @Adaptive 注解的实现类,那么就需要来到 Dubbo 帮我们生成 自适应拓展实现类的方法。
5.创建自适应拓展类
生成 自适应拓展类 的前提 是在 接口类 的所有方法中, 必须存在 至少一个方法有 @Adaptive 注解。
也就是 createAdaptiveExtensionClass() 该方法。
private Class<?> createAdaptiveExtensionClass() {
// 使用 自适应类代码生成器 根据 当前接口类型以及cachedDefaultName 生成代码。
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
// 获取类加载器
ClassLoader classLoader = findClassLoader();
// 再次通过 Dubbo SPI 加载获取 Compiler接口类 的 自适应拓展类 默认为 AdaptiveCompiler类
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
// 通过获取的 Compiler接口类型 的实现类 根据 生成的代码 来得到字节码
return compiler.compile(code, classLoader);
}
上述代码虽然只有四行代码, 但是其中做了很多事情, 大致可以理解为, 按照 目标接口类中的方法, 生成代码字符串, 该代码字符串实际上就是一个 接口实现类的代码。 然后经过 Compiler 的自适应拓展类 AdaptiveCompiler 编译此代码 得到 对应的字节码。
生成代码
首先来看一下,是如何 生成 代码的。
创建自适应类代码生成器实例
public AdaptiveClassCodeGenerator(Class<?> type, String defaultExtName) {
this.type = type;
this.defaultExtName = defaultExtName;
}
该代码非常简单,就是属性赋值而已。
// 生成代码
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();
// 生成包名
// 例如: package com.zzp.dubbo.spi.api;
code.append(generatePackageInfo());
// 生成import
// 例如: import org.apache.dubbo.common.extension.ExtensionLoader;
code.append(generateImports());
// 生成 类声明
// 例如: public class Driver$Adaptive implements com.zzp.dubbo.spi.api.Driver {
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();
}
最终生成的代码模板案例如下:
package com.zzp.dubbo.spi.api;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Driver$Adaptive implements com.zzp.dubbo.spi.api.Driver {
// 接口方法的实现
public void driveCar(org.apache.dubbo.common.URL arg0) {
// 参数检查
if (arg0 == null) throw new IllegalArgumentException("url == null");
// 根据 URL参数中的 对应值,获取要执行的 实现类对象的 名字
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("driver");
if(extName == null)
throw new IllegalStateException("Failed to get extension (com.zzp.dubbo.spi.api.Driver) name from url (" + url.toString() + ") use keys([driver])");
// 通过Dubbo SPI 中的容器,获取对应 extname的 实例对象
com.zzp.dubbo.spi.api.Driver extension = (com.zzp.dubbo.spi.api.Driver)ExtensionLoader.getExtensionLoader(com.zzp.dubbo.spi.api.Driver.class).getExtension(extName);
// 调用该对象的 当前目标方法
extension.driveCar(arg0);
}
}
以上是生成 XXX&Adaptive 代码的分析。
那么下面我们要看一 下 Dubbo SPI 如何加载并获取 Compiler 接口的 AdaptiveExtensionClass 的。
Dubbo SPI 加载和解析 Compiler 接口类的过程 上面都讲过了, 因此我们只需要看 Compiler接口 和其对应的配置文件 。
/**
* Compiler. (SPI, Singleton, ThreadSafe)
*/
@SPI("javassist") // 默认使用 名叫 javassist 的实现类
public interface Compiler {
/**
* Compile java source code.
*
* @param code Java source code
* @param classLoader classloader
* @return Compiled class
*/
Class<?> compile(String code, ClassLoader classLoader);
}
/META-INF/dubbo/internal/org.apache.dubbo.common.compiler.Compiler
adaptive=org.apache.dubbo.common.compiler.support.AdaptiveCompiler
jdk=org.apache.dubbo.common.compiler.support.JdkCompiler
javassist=org.apache.dubbo.common.compiler.support.JavassistCompiler
从中不难发现, AdaptiveCompiler为自适应拓展类,而JdkCompiler 和 JavassistCompiler 都是可拓展类
因为 Compiler的 ExtensionLoader 调用了 getAdaptiveExtension() ,那么会解析配置文件,然后会在getAdaptiveExtensionClass() 方法中 得到 AdaptiveCompiler.class ,最终会 通过反射生成实例,并调用injectExtension() 方法 进行依赖注入。
6.Dubbo SPI IOC依赖注入
我们先看一下 AdaptiveCompiler 该类
@Adaptive
public class AdaptiveCompiler implements Compiler {
// compiler实现类
private static volatile String DEFAULT_COMPILER;
public static void setDefaultCompiler(String compiler) {
DEFAULT_COMPILER = compiler;
}
@Override
public Class<?> compile(String code, ClassLoader classLoader) {
Compiler compiler;
ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
String name = DEFAULT_COMPILER; // copy reference
if (name != null && name.length() > 0) {
compiler = loader.getExtension(name);
} else {
compiler = loader.getDefaultExtension();
}
return compiler.compile(code, classLoader);
}
}
以 AdaptiveCompiler 为例 看看是如何 进行依赖注入的
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
// 获取并遍历 实例 的所有方法
for (Method method : instance.getClass().getMethods()) {
// 判断有没有set方法, 有则对setXX方法进行赋值
if (isSetter(method)) {
// 若方法上有 @DisableInject 注解 表示 禁止依赖注入
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
// 获取该方法的参数类型
Class<?> pt = method.getParameterTypes()[0];
// 判断该参数类型 是否是私有的或者是基础类型,若是则 不进行依赖注入
// 这里 由于 AdaptiveCompiler 的方法参数 为 java.lang.String,则跳过
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
// 截取 方法名 除了 set 之后的字符串,作为参数名
String property = getSetterProperty(method);
// 从objectFactory中获取 属性值
// 参数一: pt 参数类型
// 参数二: property 参数名
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
}
}
}
return instance;
}
上述代码中,重点关注的 是 objectFactory.getExtension(pt, property);
从前面我们已知 objectFactory 是 AdaptiveExtensionFactory 实例对象。
// 参数一: pt 参数类型
// 参数二: property 参数名
public <T> T getExtension(Class<T> type, String name) {
// 遍历所有的 扩展工厂实例对象, 调用其 getExtension()方法
// 已知 默认仅有一个扩展工厂为 SpiExtensionFactory
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
上面的代码 就是 遍历 所有的 扩展工厂 调用他们的 getExtension 方法,默认仅有一个扩展工厂 为 SpiExtensionFactory。
来看一下它的实现
// 参数一: pt 参数类型
// 参数二: property 参数名
public <T> T getExtension(Class<T> type, String name) {
// 判断 该类型是否是 接口类型 和 该接口类 上是否有 @SPI 注解
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
// 获取该接口类的 ExtensionLoader
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
// 获取其自适应扩展类对象,并返回
return loader.getAdaptiveExtension();
}
}
return null;
}
从上述 Dubbo SPI 依赖注入代码分析,可知 若想要进行依赖注入,首先是 目标注入的属性类型 必须是 接口类型,且该接口类型必须有 @SPI 注解 , 其次要实现该属性的 setXXX 方法.
这里有一个循环依赖的问题,我目前不知道Dubbo SPI 是怎么解决的? 执行时不会报错,并有正确结果。但是Debug的过程中,会无限递归最终导致 StackOverFlowError 错误 ,很奇怪