前言
前文讲到了Dubbo中有十个分层,各分层之间单向依赖。每一层对其他模块的依赖都是通过Dubbo的SPI机制建立的。因此我们首先要了解Dubbo的SPI机制。由于dubbo各层次之间的这种SPI依赖关系,我们可以很方便的实现在各层的扩展,只需要针对性的定制相应SPI接口的实现即可。
要知道的是,dubbo没有直接使用Java SPI,而是重新实现了一套功能更为强大的SPI机制。相关逻辑都在ExtensionLoader 类中。先看一个demo示例。
-
先定义一个SPI接口,该接口被@SPI注解标注
@SPI public interface MySpi { void test(); }
-
然后定义一个配置文件,文件名为上一步接口的全名,放在META-INF/dubbo 路径下,文件内容如下:
spi1=org.apache.dubbo.demo.spi.MySpiSupport
spi2=org.apache.dubbo.demo.spi.MySpiSupport2
其中每一行’=‘前是定义的name别名,’='后是对应该接口的实现类全名
-
看一下实现的代码:
public class MySpiSupport implements MySpi{ @Override public void test() { System.out.println("MySpiSupport.test()"); } }
很简单的,就是打印一下
-
测试代码:
public class MySpiTest { public static void main(String[] args) { ExtensionLoader<MySpi> loader = ExtensionLoader.getExtensionLoader(MySpi.class); MySpi spi1 = loader.getExtension("spi1"); spi1.test(); } }
一共两步:
- 先得到对应SPI接口的extensionLoader
- 根据name得到对应的实现类对象
从上面的测试代码中可以看到,我们可以通过指定name来得到相应的SPI实现。也就是说我们可以通过注入不同的name可以得到相应模块的不同实现。
实际上Dubbo的SPI还提供了更强大的功能,即类似Spring的IOC和AOP。 这部分我们可以在后面分析代码的过程中看到。
源码分析
ExtensionLoader.getExtensionLoader
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
// 缓存中获取 每个类型的扩展加载器通过缓存维护,loader本身不提供公开的实例化方法。
// 可以看成是按type类型的单例模式
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;
}
代码比较简单。首先对type类型进行判断,不能为空,必须是接口,必须有@API注解。然后从缓存中取,没有取到就new一个新的。
看一下构造方法:
private ExtensionLoader(Class<?> type) {
this.type = type;
// 如果取的是ExtensionFactory本身的扩展点,则设置objectFactory为空,否则初始化设置objectFactory为ExtensionFactory的扩展点实现
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
getAdaptiveExtension这个是自适应扩展,后面再分析。
从这里我们可以看到。ExtensionLoader只有一个私有的构造方法。它的实例只能通过getExtensionLoader静态方法得到,而且又有缓存。那么得到一个结论,ExtensionLoader类似单例模式的实现方式,只不过会出现多个实例,每个实例对应一个SPI接口,也就是说有多少个SPI接口,也就最多有多少个ExtensionLoader实例(没有调用到的SPI接口,还没有创建出来,缓存中也没有。)
loader.getExtension({name})
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,顾名思义,用于持有目标对象
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;
}
-
wrap代表是否包装。默认不传这个参数的时候是true。 这个包装概念后面代码中会看到。
-
name为“true”时返回默认的实现。这个默认是怎么来的后面分析
-
Holder持有需要获取的扩展对象。主要是使用了volatile关键字保证可见性。
public class Holder<T> { private volatile T value; public void set(T value) { this.value = value; } public T get() { return value; } }
-
然后就是双重检查,保证线程安全
-
双重检查未得到的情况下创建对象实例createExtension
createExtension(name, wrap)
private T createExtension(String name, boolean wrap) {
// 从配置文件中加载拓展类,可得到“配置项名称”到“配置类”的映射关系表
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);
}
// 向实例中注入依赖
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 实例
for (Class<?> wrapperClass : wrapperClassesList) {
// 获取对应的wapper注解
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
/**
* 如果没有注解,则默认按上述条件认为是包装类
* 如果有注解,则需要满足注解的matches条件和mismatches条件
*/
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
// 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
// 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
/**
* 原来的instance被替换成包装类,同时把原instance作为构造方法参数注入,
* 其他扩展属性通过setter注入
*/
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
}
}
/**
* 这里执行初始化方法,即实现了Lifecycle接口的类调用initialize方法
* Lifecycle接口,具有生命周期特性的(init ->start -> destory)
*/
initExtension(instance);
/**
* 到此,SPI的扩展类初始化完成
*/
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
这里做了几件事情:
- 获得当前name所对应的Class类型
- 反射创建对象
- 通过setter方法进行依赖注入
- 如果是需要包装的进行包装,并将原对象注入到包装对象中
- 如果当前要拿的扩展对象是Lifecycle接口的实现类,调用它的initialize方法
getExtensionClasses
这部分就是SPI扩展类加载过程。
private Map<String, Class<?>> getExtensionClasses() {
// 从缓存中获取已加载的拓展类
Map<String, Class<?>> classes = cachedClasses.get();
// 双重检查
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
// 加载拓展类
classes = loadExtensionClasses();
// 写入缓存
cachedClasses.set(classes);
}
}
}
return classes;
}
双重检查
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
/**
* strategies LoadingStrategy的实现类数组,看一下初始化方法,
* 使用的是JDK的SPI机制
*/
// 加载指定文件夹下的配置文件
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
// 兼容老版本。将原com.alibaba的扩展转换成org.apache的扩展接口
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
}
return extensionClasses;
}
这里的strategies加载器是使用的java的SPI机制得到的。可以看一下它的初始化方法:
private static LoadingStrategy[] loadLoadingStrategies() {
return stream(load(LoadingStrategy.class).spliterator(), false)
.sorted()
.toArray(LoadingStrategy[]::new);
}
其中load(LoadingStrategy.class)这一句是ServiceLoader的。加载了哪些LoadingStrategy的实现可以在META-INF/services 目录下找到,每个实现类指定了dubboSPI的几个加载目录。一般我们只使用META-INF/dubbo目录定义自己的SPI扩展,其他目录是dubbo内部使用的。
好了回来继续看loadDirectory方法,上面调用了两次,实际上就是兼容以前dubbo版本
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
// fileName = 文件夹路径 + type 全限定名
// /MATE-INF/dubbo/org.apache.xxx.Xxx
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls = null;
// 获取顺序thread context 的 ClassLoader => 当前class的 ClassLoader => bootstrap的ClassLoader
ClassLoader classLoader = findClassLoader();
// try to load from ExtensionLoader's ClassLoader first
// 先尝试通过ExtensionLoader的ClassLoader获取资源
// 这里的配置相当于把ExtensionLoader的classLoader放到前面
if (extensionLoaderClassLoaderFirst) {
ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
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);
}
}
// 取得urls时
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
// 加载资源
loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
可以看到,开始找文件了,看一下loadResource方法
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();
}
// 过滤掉excludedPackages属性定义的
if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
// 加载类,并通过 loadClass 方法对类进行缓存
loadClass(extensionClasses, resourceURL, Class.forName(line, 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);
}
}
先去掉#定义的注释行
根据‘=’分隔,左边是name,右边是class。然后是loadClass方法:
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 注解
// AdaptiveCompiler 和 AdaptiveExtensionFactory 目前只有这两个类加了Adaptive注解
if (clazz.isAnnotationPresent(Adaptive.class)) {
// 设置 cachedAdaptiveClass缓存。 cachedAdaptiveClass是一个实例属性,class是上面两个类时才会设置值
cacheAdaptiveClass(clazz, overridden);
}
// 检测 clazz 是否是 Wrapper 类型
// 即判断是否存在构造方法,参数是当前类型本身的
else if (isWrapperClass(clazz)) {
// 存储 clazz 到 cachedWrapperClasses 缓存中
// 这里将wapperClass缓存起来了,后面在获取具体的扩展对象时,默认情况下会使用这个wapper类包装起来
cacheWrapperClass(clazz);
} else {
// 程序进入此分支,表明 clazz 是一个普通的拓展类
// 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
clazz.getConstructor();
// 扩展文件中没有“=”时,即只定义了扩展类的,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 按 ‘,’分隔,即一个扩展有多个name定义
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
// 双向存储缓存数据,方便获取
// 存储 Class 到名称的映射关系
cacheName(clazz, n);
// 存储名称到 Class 的映射关系
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}
这里有几个缓存,@Adaptive注解标注的、wrapper类型的、普通的,分别有对应的缓存容器存储。
看一下isWrapperClass
private boolean isWrapperClass(Class<?> clazz) {
try {
clazz.getConstructor(type);
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
就是判断clazz是否存在构造方法,方法参数为type类型。
这里wrapper是干什么用的呢?看一下如下测试代码:
public class MyExtensionWapper implements MyExtension{
private MyExtension extension;
public MyExtensionWapper(MyExtension extension){
this.extension = extension;
}
@Override
public void test() {
System.out.println(extension);
extension.test();
}
}
很明显,典型的代理模式。也就是说可以定义一个wrapper类对原实现进行代理,进行功能扩展。像不像Spring的AOP思想?
injectExtension通过setter方法进行依赖注入
好了,现在回来看依赖注入部分
private T injectExtension(T instance) {
/**
* objectFactory 是ExtensionFactory接口的实现
* ExtensionFactory 本身也是一个扩展接口,两个实现SpiExtensionFactory、SpringExtensionFactory
* 即通过SPI获取和通过Spring管理的Bean获取,默认是SpiExtensionFactory。
* getLoader时初始化,如果扩展类型type是ExtensionFactory,这个是null,即ExtensionFactory是没有注入
*/
if (objectFactory == null) {
return instance;
}
try {
// 遍历目标类的所有方法
for (Method method : instance.getClass().getMethods()) {
// 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
// 只保留setter方法执行下面逻辑
if (!isSetter(method)) {
continue;
}
/**
* Check {@link DisableInject} to see if we need auto injection for this property
* @DisableInject 注解,指定该setter方法不进行依赖注入。 即忽略掉有@DisableInject注解的方法
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
// 获取 setter 方法参数类型
Class<?> pt = method.getParameterTypes()[0];
// 参数是否是基础类型,如果是基础类型,则不注入
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
// 获取属性名,比如 setName 方法对应属性名 name
String property = getSetterProperty(method);
// 从 ObjectFactory 中获取依赖对象。
// 如果是通过spring的,那就是取容器中对象了,需要提前设置好spring容器context。
// name 为属性名。 而属性名是通过setter方法名截取出来的。 也就是说在这种情况下这个setter方法名不能随便定义。
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;
}
实际上就是取setter方法,取出属性名和类型,通过objectFactory得到对应的扩展类实现,然后通过method.invoke方法进行注入。从这里我们得到几点:
- dubbo支持自身SPI的注入和Spring的bean注入,使用Spring注入时需提前给SpringExtensionFactory设置spring的context–这个spring启动的时候设置进去就可以了。
- 使用的是setter方法的注入,也就是说需要在自己的扩展中定义setter方法。注意方法名不能随便写,name是通过方法名截取出来的。需要和你要注入的对象name值一致(spring的方式要beanName,SPI的方式就是对应扩展的name)
包装过程
前面已经分析过了包装是干什么的。简单来说就是类似spring的AOP机制,对原扩展进行代理,使功能更强大。
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 实例
for (Class<?> wrapperClass : wrapperClassesList) {
// 获取对应的wapper注解
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
/**
* 如果没有注解,则默认按上述条件认为是包装类
* 如果有注解,则需要满足注解的matches条件和mismatches条件
*/
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
// 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
// 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
/**
* 原来的instance被替换成包装类,同时把原instance作为构造方法参数注入,
* 其他扩展属性通过setter注入
*/
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
}
}
首先检查包装类。在前面加载class的时候已经写到缓存中了。
取@Wrapper注解,如果没有注解直接包装,如果有注解需要根据注解定义的规则和name进行匹配,满足条件的才进行包装。
包装的过程就是把原类型作为参数反射调用包装类的构造方法。然后再进行setter注入。
总结
至此Dubbo的SPI基本分析完了。简单总结一下:
- 如何加载SPI配置的?
- 如何进行依赖注入的?
- 如何进行包装的?包装的目的是什么?
篇幅有点长了,下篇分析自适应扩展