Dubbo 扩展机制 SPI
在 Dubbo 中,SPI 贯穿在整个 Dubbo 的核心。所以有必要先对 Dubbo 中 SPI 做一个详细的介绍。
JDK SPI
之前写过一篇介绍 JDK SPI 的博客,可以点击查看。
Dubbo SPI
Dubbo 实现了自己的 SPI 机制
- 除了可以配置在
META-INF/services
目录下,还可以配置在META-INF/dubbo
和META-INF/dubbo/internal
- 配置文件内容采用
key=value
的形式。这样可以按需实例化加载类,而不用像JDK
一样在启动时一次性加载实例化扩展点的所有实现,浪费性能。 - 出现异常时可以更准确定位
- 增加了对 Duboo 自己实现的 IOC 和 AOP 的支持
源码解析
这里使用的版本是 dubbo-2.6.4
这里的分析流程是根据例子来分析内部源码的实现
注意,在阅读下面的源码解析时,有些跟当前流程无关的代码我会标注 忽略点 - X,表示这段代码先跳过,不影响当前流程,且在后面用到的地方我会再重新解释该忽略点的意思。
1、Dubbo SPI 的基本示例
接口,注意加 @SPI
注解
@SPI
public interface Robot {
void sayHello();
}
实现类
public class MIRobot implements Robot {
@Override
public void sayHello() {
System.out.println("大家好,我是小米机器人...");
}
}
在文件夹 resources/META-INF/services
下添加配置文件,文件名 com.lin.spi.Robot
(接口的路径)
文件内容
miRobot = com.lin.spi.MIRobot
在测试类中调用
public class DubboSPITest {
@Test
public void test() {
ExtensionLoader<Robot> extensionLoader =
ExtensionLoader.getExtensionLoader(Robot.class);
Robot miRobot = extensionLoader.getExtension("miRobot");
miRobot.sayHello();
}
}
输出
大家好,我是小米机器人...
Process finished with exit code 0
上面就是 Dubbo SPI 的基本使用,接下来开始源码分析。
(1)入口方法 ExtensionLoader#getExtensionLoader(Class<T> type)
@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> 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;
}
getExtensionLoader(Class<T> type)
的作用只是简单的根据 type
包装一个 ExtensionLoader
实例,缓存在 EXTENSION_LOADERS
中,然后返回 ExtensionLoader
实例。
其中 ExtensionLoader
实例化时 objectFactory
的创建我们这里先忽略
(2)调用方法 ExtensionLoader#getExtension(String name)
获取具体的实例
@SuppressWarnings("unchecked")
public T getExtension(String name) {
// ...
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
getExtension(String name)
的作用就是创建我们在配置文件配置的扩展点实现类(例子中的 MIRobot
实例),包装在 Holder
中然后缓存进 cachedInstances
,返回实例。
创建扩展类
@SuppressWarnings("unchecked")
private T createExtension(String name) {
// 根据 type 去配置文件中加载配置的所有扩展类存进 Map<String, Class<?>> 缓存,再根据 name 得到特定的 clazz
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);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}
createExtension(String name)
方法的逻辑包含如下步骤
- 获取所有的扩展类(实例中是返回 miRobot -> "class com.lin.spi.MIRobot" )
- 通过反射创建拓展对象
- 向拓展对象中注入依赖(这里暂时忽略)
- 将拓展对象包裹在相应的 Wrapper 对象中(这里暂时忽略)
(2.1)获取所有的扩展类
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() {
// 获取 @SPI 注解
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if (names.length > 1) {
// 检测 @SPI 注解内容是否合法
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
if (names.length == 1) cachedDefaultName = names[0];
}
}
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
// 加载指定文件夹下的配置文件
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); // META-INF/dubbo/internal
loadDirectory(extensionClasses, DUBBO_DIRECTORY); // META-INF/dubbo
loadDirectory(extensionClasses, SERVICES_DIRECTORY); // META-INF/services
return extensionClasses;
}
loadDirectory
方法先通过 classLoader
获取所有资源链接(配置文件链接),然后再通过 loadResource
方法读取和解析配置文件,把解析出的每一个 clazz
调用 loadClass()
在 cachedNames
、extensionClasses
、cachedActivates
等缓存起来
2、Dubbo AOP 实现
例子
在上面的例子基础上,新建类 RobotWrapper
public class RobotWrapper implements Robot {
private Robot robot;
public RobotWrapper(Robot robot) {
this.robot = robot;
}
@Override
public void sayHello() {
System.out.println("before...");
this.robot.sayHello();
System.out.println("after...");
}
}
在配置文件中写上
miRobot = com.lin.spi.MIRobot
wrapper = com.lin.spi.RobotWrapper
关于 RobotWrapper
,一定要有构造函数才会生效,同时在配置文件中可以不用写 wrapper=
,也可以同时存在多个 xxxWrapper
实现
接下来不用改动原来的代码,直接运行,
输出:
before...
大家好,我是小米机器人...
after...
源码分析
getExtensionClass(key) -> getExtensionClasses() -> loadExtensionClasses() -> loadDirectory() -> loadResource() -> loadClass()
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
// ...
if () {
} else if (isWrapperClass(clazz)) { // 判断 clazz 是否有构造函数 忽略点-3的解释
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz); // 把 clazz 存进缓存中
} else {
}
}
private boolean isWrapperClass(Class<?> clazz) {
try {
clazz.getConstructor(type); // clazz 是否有 type 作为参数的构造函数
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
接下来在 clazz
实例化时,
@SuppressWarnings("unchecked")
private T createExtension(String name) {
// ...
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
// 取出所有的 wrapperClass,遍历,取出每一个,把 instance 传进 wrapperClass 的构造函数并实例化,赋值给 instance
// 最后返回的 instance 就是被 wrapper 层层包裹的结果
// 代入例子中,instance = RobotWrapper.class.getConstructor(Robot.class).newInstance(miRobot)
// 类似 instance = new RobotWrapper(miRobot)
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
// ...
}
3、Dubbo IOC 实现
Dubbo IOC 是通过 setter 方法注入依赖。Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。
Dubbo IOC 这部分可以结合 Dubbo 的自适应扩展来讲
4、什么是自适应扩展?
前面我们讲解 Dubbo 可以通过 ExtensionLoader.getExtensionLoader(XXXClass).getExtension(key)
的形式来获取某个接口的实现类,但这种形式属于硬编码来指定引用哪个实现。而现在某些扩展希望可以在被调用时,根据运行时参数进行加载,这需要自适应扩展来实现。
首先先来看和自适应扩展相关的两个参数,一个是注解 @Adaptive
,一个是 com.alibaba.dubbo.common.URL
@Adaptive
可修饰在类和方法上,
- 当修饰在类上时,表示该类为所实现的接口的代理类实现
- 当修饰在方法上时,Dubbo 会自动生成适配器类,会从
URL
中取值作为扩展点名去加载实现类并实例化,最后再使用这个实例调用对应的方法。
例子:
@SPI
public interface Robot {
@Adaptive("robot")
void sayHello(URL url);
}
public class AdaptiveRobot implements Robot {
private Robot robot;
public void setRobot(Robot robot) {
this.robot = robot;
}
@Override
public void sayHello(URL url) {
System.out.println("在代理类内部通过 URL 和 SPI 机制获取和加载具体的 Robot 实现类,并调用目标方法");
this.robot.sayHello(url);
}
}
public class MIRobot implements Robot {
@Override
public void sayHello(URL url) {
System.out.println("hello");
}
}
import com.alibaba.dubbo.common.URL;
public class DubboSPITest {
@Test
public void test() {
ExtensionLoader<Robot> extensionLoader =
ExtensionLoader.getExtensionLoader(Robot.class);
Map<String, String> map = new HashMap<>();
map.put("robot", "miRobot"); // key=robot 由注解 @Adaptive("robot") 决定
URL url = new URL("", "", 1, map);
Robot adaptiveRobot = extensionLoader.getExtension("adaptiveRobot");
adaptiveRobot.sayHello(url);
}
}
输出:
在代理类内部通过 URL 和 SPI 机制获取和加载具体的 Robot 实现类,并调用目标方法
hello
源码分析
在执行 Class<?> clazz = getExtensionClasses().get("adaptiveRobot");
并实例化 instance
后,调用 injectExtension(instance)
方法向扩展实例中注入依赖
@SuppressWarnings("unchecked")
private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name);
// ...
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
// ...
injectExtension(instance);
// ...
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}
接下来看 injectExtension(instance)
的具体实现
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
// 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
// 获取 setter 方法参数类型
Class<?> pt = method.getParameterTypes()[0];
try {
// 获取属性名,比如 setRobot 方法对应属性名 robot
String property = method.getName().length() > 3 ?
method.getName().substring(3, 4).toLowerCase() +
method.getName().substring(4) : "";
// 从 ObjectFactory 中获取依赖对象 eg. XXX$Adaptive
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
// 通过反射调用 setter 方法设置依赖
method.invoke(instance, object); // 往示例中的 AdaptiveRobot 实例的 setRobot() 方法中注入 Robot$Adaptive 自适应类
}
} catch (Exception e) {
logger.error("fail to inject via method...");
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
这部分源码解释看注释就可以,流程很清晰,主要说下里面的 Object object = objectFactory.getExtension(pt, property);
的 objectFactory
。
objectFactory
的实例化
每一个 objectFactory
的创建都是在 ExtensionLoader
实例化的时候,
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
接下来看 getAdaptiveExtension()
public T getAdaptiveExtension() {
// 从缓存中获取自适应拓展
Object instance = cachedAdaptiveInstance.get();
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("fail to create adaptive instance: ...");
}
}
}
} else {
throw new IllegalStateException("fail to create adaptive instance: ...");
}
}
return (T) instance;
}
private T createAdaptiveExtension() {
try {
// 获取自适应拓展类,并通过反射实例化,调用 injectExtension 方法向拓展实例中注入依赖
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extension ...");
}
}
private Class<?> getAdaptiveExtensionClass() {
// 通过 SPI 获取所有的拓展类,例子中指所有 Robot 接口实现类
getExtensionClasses();
// 如果某个实现类被 Adaptive 注解修饰了,那么该类就会被赋值给 cachedAdaptiveClass 变量。
// 那么这里直接返回,不创建自适应扩展类
// 这也就是为什么说被 @Adaptive 注解修饰在类上和方法上不一样
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 创建自适应拓展类
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
现在,世界线收束,我们再从 getExtensionClasses(ExtensionFactory.class);
方法看起,一层层进到最里面的 loadClass()
方法时,这个接口 ExtensionFactory.class
的实现类有 AdaptiveExtensionFactory
、SpiExtensionFactory
和 SpringExtensionFactory
。其中 AdaptiveExtensionFactory
类上被注解 @Adaptive
修饰,表示该类是该接口的适配器,不提供具体业务支持,会根据在运行时的一些状态来选择具体调用 ExtensionFactory
的哪个实现。
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
// ...
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
}
}
// ...
}
返回 cachedAdaptiveClass = adaptiveExtensionFactory
因此,当一开始 ExtensionLoader.getExtensionLoader(Robot.class)
初始化后,得到(忽略点 - 1 的解释)
private ExtensionLoader(Robot.class) {
this.type = Robot.class;
objectFactory = adaptiveExtensionFactory;
}
搞清楚 objectFactory 等于什么之后,我们继续看 injectExtension(instance)
的内容,
private T injectExtension(T instance) {
// ...
// 从 ObjectFactory 中获取依赖对象
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
// 通过反射调用 setter 方法设置依赖
method.invoke(instance, object);
}
// ...
return instance;
}
objectFactory.getExtension(pt, property)
也就是 adaptiveExtensionFactory.getExtension(pt, property)
,而 AdaptiveExtensionFactory
在实例化的时候会根据当前系统环境获取 ExtensionFactory
,也就是 SpiExtensionFactory
和 SpringExtensionFactory
存到 factories
中。
public AdaptiveExtensionFactory() {
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
我们看其中一个 SpiExtensionFactory
的实现,
public class SpiExtensionFactory implements ExtensionFactory {
@Override
public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension();
}
}
return null;
}
}
从 loader.getAdaptiveExtension()
一步步跟进,来到 createAdaptiveExtensionClass()
, 创建默认代理类
private Class<?> createAdaptiveExtensionClass() {
// 通过反射检测接口方法是否包含 Adaptive 注解
// 构建自适应拓展代码
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
// 获取编译器实现类
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
// 编译代码,生成 Class
return compiler.compile(code, classLoader);
}
如果你对 createAdaptiveExtensionClass()
生成的代理类感觉有点模糊的话,觉得看代码不清晰,我们可以用阿里开源的工具 arthas 反编译查看一下生成的代理类是什么样子的
由上图可知,生成的代理类从 com.alibaba.dubbo.common.URL
中获取参数实例化并调用接口方法,返回生成的自适应类。
在我们举的例子中,injectExtension(T instance)
的作用就是,生成自适应类 object = Robot$Adaptive
,并通过 AdaptiveRobot.setRobot(Robot robot)
注入进去。
5、@Activate 自动激活扩展点
表示实现类是否可以被激活。通常被用在一个接口有很多现实类,但是这些实现类在特定条件才需要使用,比如有些实现只想在生产者生效,有些实现类想在消费者生效。它有两个设置过滤条件的字段,group,value 都是字符数组,用来指定这个扩展类在什么条件下激活。
在 loadClass()
加载扩展类时,把所有实现类中有Activate注解的,放到 cachedActivates
中(忽略点 - 4的解释)
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
// ...
if (names != null && names.length > 0) {
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(names[0], activate);
}
// ...
}
}
后记
这篇文章讲解了 Dubbo SPI 的实现机制,关键是要弄懂 @SPI
@Adaptive
@Activate
注解的含义,知道 Dubbo 中 AOP、IOC 的各自实现,还有就是 SPI 的自适应拓展机制以及 Dubbo 中的 URL 作为配置总线,各个服务的参数配置都是通过 URL 进行传递的。