本系列参考官网文档、芋道源码的源码解读和《深入理解Apache Dubbo与实战》一书。阅读本文前可以参考阅读下面链接里的内容。Dubbo版本为2.6.1。
JavaSPI机制简介
Dubbo官网文档—扩展点加载
文章内容顺序:
1.Dubbo 自己实现了一套 SPI 机制,而不是使用 Java 标准的 SPI 。为什么要自己实现?
2.@SPI简单介绍
3.Dubbo SPI机制是怎么实现加载用到的类,而不是像JDK SPI中加载全部?
4.Dubbo SPI 机制是怎么实现AOP的?
5.如果一个扩展接口有多个包装类,怎么办?比如Protocol这个扩展点,他有两个Wrapper,该怎么生成?
6.那么Dubbo SPI是怎么实现IOC的呢?
7.如果扩展接口有多个实现类,具体注入哪个呢?引出@Adaptive
8.@Adaptive介绍,包括入口方法到最后的链路调用,样例代码的生成。
9.为什么源码里只有AdaptiveExtensionFactory和AdaptiveCompiler注解了?
10.为什么AdaptiveCompiler这个类是固定已知的?包括这个类的源码介绍
11.为什么AdaptiveExtensionFactory这个类是固定已知的?包括这个类的源码介绍
12@Activate注解用途,入口方法介绍
1.Dubbo 自己实现了一套 SPI 机制,而不是使用 Java 标准的 SPI 。为什么要自己实现?
- 1.Dubbo 有很多的拓展点,例如 Protocol、Filter 等等。并且每个拓展点有多种的实现,例如 Protocol 有 DubboProtocol、InjvmProtocol、RestProtocol 等等。那么使用 JDK SPI 机制,会初始化无用的拓展点及其实现,造成不必要的耗时与资源浪费。
- 2.Dubbo解决了JDK SPI机制吞异常的机制。
例如:如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。- 3.增加了对扩展点 IoC(留待下面说明) 和 AOP (通过wapper实现,装饰器设计模式)的支持,一个扩展点可以直接 setter 注入其它扩展点。
这种扩展机制有三个注解,分别是@SPI、@Adaptive 、@Activate。我们将会将SPI、Adaptive放一块讲,Activate单独拎出来讲。
这些机制主要在com.alibaba.dubbo.common.extension.ExtensionLoader类中。
类主要方法和功能如下
image.png
2.@SPI
@SPI 主要用于标记该接口是SPI接口,即扩展点,可以有多个不同内置或用户定义的实现,下面是Dubbo的一个Protocol接口
@SPI("dubbo")
public interface Protocol {
// ... 省略代码
}
他的扩展点如下:
image.png
上面的是Protocol的接口,@SPI整个注解的意义是用来说明这个是扩展类,并且默认的实现是DubboProtocol,可以通过修改XML文件中的来进行配置。
<dubbo:protocol name="dubbo" port="20800" />
3.Dubbo SPI机制是怎么实现加载用到的类,而不是像JDK SPI中加载全部?
image.png
这是 SPI 的配置目录,通过这个文件来加载SPI的加载对象。
而一个拓展( 拓展接口 )对应一个 ExtensionLoader 对象。例如,Protocol 和 Filter 分别对应一个 ExtensionLoader 中的对象。如下图红框所示
image.png
一个拓展接口通过其 ExtensionLoader 对象,加载它的拓展实现们。我们会发现多个属性都是 “cached“ 开头(图里只展示了一点)。ExtensionLoader 考虑到性能和资源的优化,读取拓展配置后,会首先进行缓存。等到 Dubbo 代码真正用到对应的拓展实现时,进行拓展实现的对象的初始化。并且,初始化完成后,也会进行缓存。也就是说:
- (1).缓存加载的拓展配置
- (2).缓存创建的拓展实现对象
通过这种方式,就不用一开始加载就初始化所有对象,而是等需要他们的时候再初始化。
4.Dubbo SPI 机制是怎么实现AOP的?
com.alibaba.dubbo.common.extension
包下的ExtensionLoader
类中的loadFile()
实现(方法过长,就不贴了),此方法用来加载配置文件,其判断加载到的扩展点有拷贝构造函数,则判定为扩展点 Wrapper 类。如下代码所示
Wrapper 类同样实现了扩展点接口(Protocol),但是 Wrapper 不是扩展点的真正实现。
通过 Wrapper 类可以把所有扩展点公共逻辑移至 Wrapper 中。新加的 Wrapper 在所有的扩展点上添加了逻辑,有些类似 AOP,即 Wrapper 代理了扩展点。
这里先卖个关子,也就是他的IOC流程,我们专注于他AOP的实现。
package com.alibaba.xxx;
import org.apache.dubbo.rpc.Protocol;
public class XxxProtocolWrapper implements Protocol {
Protocol impl;
public XxxProtocolWrapper(Protocol protocol) { impl = protocol; }
// 接口方法做一个操作后,再调用extension的方法
public void refer() {
//... 一些操作
impl.refer();
// ... 一些操作
}
// ...
}
5.如果一个扩展接口有多个包装类,怎么办?比如Protocol这个扩展点,他有两个Wrapper,该怎么生成?
比如截取的这个Protocol,就有两个包装类。
image.png
ExtensionLoader#createExtension
方法如下:
private T createExtension(String name) {
// 获得拓展名对应的拓展实现类
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);
// 创建 Wrapper 拓展对象
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);
}
}
注意上述代码中的:
// 创建 Wrapper 拓展对象
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
这里的代码就很好理解了,简单来说就是——套娃。
instance
在一开始是我们的InjvmProtocol
cachedWrapperClasses
存储了他的所有我们wrapperClasses
,即两个包装类ProtocolFilterWrappe
,ProtocolListenerWrapper
。
按照For循环取出来的顺序,第一次循环先把InjvmProtocol
包装进ProtocolFilterWrappe
,第二此循环的时候instance 已经是ProtocolFilterWrappe
了,此时再把ProtocolFilterWrappe
包装进ProtocolListenerWrapper
最后返回,大功告成。
6.那么Dubbo SPI是怎么实现IOC的呢?
createExtension()
(用以创建扩展类的方法)时会调用injectExtension()
来注入属性
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) { // setting && public 方法
// 获得属性的类型
Class<?> pt = method.getParameterTypes()[0];
try {
// 获得属性
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
// 获得属性值
Object object = objectFactory.getExtension(pt, property);
// 设置属性值
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
如代码所示,在injectExtension
方法中,会判断这个类中是否有setter方法,如果有的话会通过ExtensionFactory自动注入对应的扩展点实例。
这个时候就出现问题了:
- 1.如果扩展接口有多个实现类,具体注入哪个呢?
-
2.ExtensionFactory是什么,怎么注入的?
这个时候就需要另一个注解出场了:@Adaptive
@Adaptive
- 注解在类上时,直接使用被注解的类。也因此,一个拓展,只允许最多注解一个类(可以存在多个类,但是只能注解一个),否则会存在多个会是冲突。
这里多提一嘴,整个Dubbo源码中用在类上的@Adaptive只有AdaptiveExtensionFactory
和AdaptiveCompiler
。- 注解在方法上时,代表自动生成和编译一个动态的Adpative类,它主要是用于SPI机制,因为spi的类是不固定、未知的扩展类,所以设计了动态$Adaptive类.
例如 Protocol的spi类有 injvm dubbo registry filter listener等等 很多扩展未知类,
而实现这种机制的入口主要在ExtensionLoader.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: " + t.toString(), t);
}
}
}
// 若之前创建报错,则抛出异常 IllegalStateException
} else {
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
当缓存不存在时,调用 #createAdaptiveExtension()
方法,创建自适应拓展对象,并添加到 cachedAdaptiveInstance 中。
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
调用
#getAdaptiveExtensionClass()
方法,获得自适应拓展类。
调用Class#newInstance() 方法
,创建自适应拓展对象。
调用#injectExtension(instance)
方法,向创建的自适应拓展对象,注入依赖的属性。
那我们再来看看getAdaptiveExtensionClass()
private Class<?> getAdaptiveExtensionClass() {
/**
* 这边深入下去会用到loadFile(),也就是加载在配置文件里的类,
如果有AdaptiveClass,会把AdaptiveClass缓存到cachedAdaptiveClass
*/
getExtensionClasses();
/**
* 如果通过上面的步骤可以获取到cachedAdaptiveClass直接返回,
如果不行的话,就得考虑自己进行利用动态代理创建一个了
*/
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
/**
* 利用动态代理创建一个扩展类
*/
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
先来看看loadFile()做了啥
在loadFile()方法中,会有判断类是否有@Adaptive注解,节选如下
/**
* 判断这个加载的类上,有没有Adaptive的注解,如果有
*/
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
/**
* 将此类作为cachedAdaptiveClass
*/
cachedAdaptiveClass = clazz;
}
/**
* 多个adaptive类的实例,报错
*/
else if (!cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getClass().getName()
+ ", " + clazz.getClass().getName());
}
}
/**
* 如果这个类,没有Adaptive注解
*/
else {
……
}
如果有的类上带有@Adaptive注解,那么将这个类赋值给
cachedAdaptiveClass
,如果没有,那么在else里面就会进入是否是wapper(包装类)的判定。也就是说
如果类上没有@Adaptive注解,此时getAdaptiveExtensionClass()
中的
if (cachedAdaptiveClass != null)
也为空,那么就要进入createAdaptiveExtensionClass()
了。
private Class<?> createAdaptiveExtensionClass() {
// 自动生成自适应拓展的代码实现的字符串
String code = createAdaptiveExtensionClassCode();
// 编译代码,并返回该类
ClassLoader classLoader = findClassLoader();
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
思路很简单,将类以字符串的形式拼接出来,然后利用编译器进行编译,返回编译后的class对象。寻找编译器的过程和具体编译的过程不是我们此次所要关心的,我们关心的是这个createAdaptiveExtensionClassCode方法创建的字符串格式的数据是啥样的,用到了哪些数据。
样例代码如下:
@SPI("dubbo")
public interface AdaptiveExt2 {
@Adaptive({"t"})
String echo(String msg, URL url);
}
@Test
public void test1() {
ExtensionLoader<AdaptiveExt2> loader = ExtensionLoader.getExtensionLoader(AdaptiveExt2.class);
AdaptiveExt2 adaptiveExtension = loader.getAdaptiveExtension();
URL url = URL.valueOf("test://localhost/test?t=cloud");
System.out.println(adaptiveExtension.echo("d", url));
}
cloud
编译后代码如下:
package shuqi.dubbotest.d;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class AdaptiveExt2$Adpative implements shuqi.dubbotest.spi.adaptive.AdaptiveExt2 {
public java.lang.String echo(java.lang.String arg0, com.alibaba.dubbo.common.URL arg1) {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = url.getParameter("t", "dubbo");
if (extName == null)
throw new IllegalStateException("Fail to get extension(shuqi.dubbotest.spi.adaptive.AdaptiveExt2) name from url(" + url.toString() + ") use keys([t])");
shuqi.dubbotest.spi.adaptive.AdaptiveExt2 extension = (shuqi.dubbotest.spi.adaptive.AdaptiveExt2) ExtensionLoader.getExtensionLoader(shuqi.dubbotest.spi.adaptive.AdaptiveExt2.class).getExtension(extName);
return extension.echo(arg0, arg1);
}
}
分析:
@Adaptive中,value 是个字符数组,通过该属性从 URL 中获取扩展名,来决定使用哪个扩展。分为几种情况:
1.设置了 value,且从 URL 中找到了对应的扩展名,则使用该扩展;
2.设置了 value,但从 URL 中找不到扩展名,则使用默认的扩展,即 @SPI 中配置的 value(在此例中为dubbo),还是找不到则抛出异常;
3.未设置 value,则根据接口名生成 value,比如接口 YyyInvokerWrapper 生成 value = “yyy.invoker.wrapper”。
注意下面代码最后传入的extName,这个才是从url中获得的value,通过这个字符串从ExtensionLoader找到对应的扩展类,再通过这个扩展类来执行相应的操作,其他无非是一些判空操作。url.getParameter("t", "dubbo");也只是如果url中有key为t对应的value,则返回,否则返回dubbo。
shuqi.dubbotest.spi.adaptive.AdaptiveExt2 extension = (shuqi.dubbotest.spi.adaptive.AdaptiveExt2) ExtensionLoader.getExtensionLoader(shuqi.dubbotest.spi.adaptive.AdaptiveExt2.class). (extName);
将上面生成的字符串编译成Class对象,作为适配器类,返回,然后实例化后,进行依赖注入需要的属性,随后缓存,备下次使用。
9.为什么源码里只有AdaptiveExtensionFactory和AdaptiveCompiler注解了@Adaptive?
此种情况,表示拓展的加载逻辑已经被确定下来了,一般不再进行除此以外额外的拓展。更多时候,Adaptive 是注解在接口方法上的,表示拓展的加载逻辑需由框架自动生成(生成代码后编译来生成新的类)。
10.为什么AdaptiveCompiler这个类是固定已知的?
因为整个框架仅支持Javassist和JdkCompiler。直接固定了对应的实现而不需要动态生成代码实现,就像策略模式直接确定实现类。在扩展点初始化时,如果发现实现类上有@Adaptive注解,会直接赋值给
cachedAdaptiveClass
,并把实例缓存到cachedAdaptiveInstance
中。他的UML图如下,
image.png
AdaptiveCompiler
中只是作为一个管理作用,用来管理其他两个真正的Compiler,他的代码也非常简单。
@Adaptive
public class AdaptiveCompiler implements 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;
// 获得 Compiler 的 ExtensionLoader 对象。
ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
String name = DEFAULT_COMPILER; // copy reference
// 使用设置的拓展名,获得 Compiler 拓展对象
if (name != null && name.length() > 0) {
compiler = loader.getExtension(name);
// 获得默认的 Compiler 拓展对象
} else {
compiler = loader.getDefaultExtension();
}
// 编译类
return compiler.compile(code, classLoader);
}
}
setDefaultCompiler(compiler) 静态方法,设置默认编辑器的拓展名( DEFAULT_COMPILER )。该方法被 ApplicationConfig#setCompiler(compiler) 方法调用,代码如下:
public void setCompiler(String compiler) {
this.compiler = compiler;
AdaptiveCompiler.setDefaultCompiler(compiler);
}
在 <dubbo:application compiler="" /> 配置下,即可触发该方法。
如果没有配置,则使用接口上的@SPI("javassist")来进行
11.为什么AdaptiveExtensionFactory这个类是固定已知的?
同样的,因为整个框架仅支持2个objFactory,一个是spi,另一个是spring
image.png
AdaptiveExtensionFactory
与上文的AdaptiveCompiler
类似,都是用来管理其他两个扩展类的,在这个AdaptiveExtensionFactory
里直接就加载了所有的ExtensionFactory
实现并缓存起来,SpingExtensionFactory
提供了保存Spring上下文的静态方法,SpiExtensionFactory
可以从Dubbo 容器中获取扩展点实例,这样就打通了Spring容器与SPI容器,注入会从这两个容器里找。
AdaptiveExtensionFactory代码如下:
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
/**
* ExtensionFactory 拓展对象集合
*/
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory() {
// 使用 ExtensionLoader 加载拓展对象实现类。
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);
}
public <T> T getExtension(Class<T> type, String name) {
// 遍历工厂数组,直到获得到属性
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
}
12.@Activate
Activate主要用在有多个扩展点实现,需要根据不同条件激活的场景中。
比如Filter需要多个同时激活,因为每个Filter实现的是不同的功能。
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> exts = new ArrayList<T>();
List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
// 处理自动激活的拓展对象们
// 判断不存在配置 `"-name"` 。例如,<dubbo:service filter="-default" /> ,代表移除所有默认过滤器。
if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
// 获得拓展实现类数组
getExtensionClasses();
// 循环
for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Activate activate = entry.getValue();
if (isMatchGroup(group, activate.group())) { // 匹配分组
// 获得拓展对象
T ext = getExtension(name);
if (!names.contains(name) // 不包含在自定义配置里。如果包含,会在下面的代码处理。
&& !names.contains(Constants.REMOVE_VALUE_PREFIX + name) // 判断是否配置移除。例如 <dubbo:service filter="-monitor" />,则 MonitorFilter 会被移除
&& isActive(activate, url)) { // 判断是否激活
exts.add(ext);
}
}
}
// 排序
Collections.sort(exts, ActivateComparator.COMPARATOR);
}
// 处理自定义配置的拓展对象们。例如在 <dubbo:service filter="demo" /> ,代表需要加入 DemoFilter (这个是笔者自定义的)。
List<T> usrs = new ArrayList<T>();
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX) && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) { // 判断非移除的
// 将配置的自定义在自动激活的拓展对象们前面。例如,<dubbo:service filter="demo,default,demo2" /> ,则 DemoFilter 就会放在默认的过滤器前面。
if (Constants.DEFAULT_KEY.equals(name)) {
if (!usrs.isEmpty()) {
exts.addAll(0, usrs);
usrs.clear();
}
} else {
// 获得拓展对象
T ext = getExtension(name);
usrs.add(ext);
}
}
}
// 添加到结果集
if (!usrs.isEmpty()) {
exts.addAll(usrs);
}
return exts;
}
从源码可以看到,主要流程分为四步
- 1、检查缓存,如果缓存没有,就初始化所有扩展类实现的集合
- 2、遍历整个@Activate集合,根据URL的匹配条件,得到所有符合激活条件的扩展类实现。然后根据@Activate中配置的before、after、order参数进行排序
- 3.遍历所有用户自定义扩展类名称,将自定义扩展类放在自动激活的拓展对象们前面
- 4.返回所有自动激活类集合。
网友评论