Dubbo-SPI&自适应的扩展机制

官方文档-SPI 自适应拓展

推荐阅读官方文档

JDK的SPI是一种服务发现机制。SPI将接口的实现类全限定名配置在文件中,由服务加载器读取配置文件,加载实现类,这样可以在运行期间动态的替换实现类。默认的加载路径为META-INF/services,通过ServiceLoader实现加载。
Dubbo实现了增强的SPI机制。Dubbo的SPI相关逻辑被封装在ExtensionLoader中,通过其加载指定的实现类。相比于JDK的路径,Dubbo将配置文件放置在META-INF/dubbo主路径下,同时采用了键值对的形式。

主要分析 com.alibaba.dubbo.common.extension.ExtensionLoader

Case-1

1
2
3
4
5
6
7
8
9
1    @Test
2 public void robotTest() {
3 ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
4 Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
5 optimusPrime.hi();
6
7 Robot bumblebee = extensionLoader.getExtension("bumblebee");
8 bumblebee.hi();
9 }
①初始化type对应ExtensionLoader对象的objectFactory成员变量

1行代码通过 ExtensionLoader.getExtensionLoader(Robot.class); 这行代码就必须走到 new ExtensionLoader(type)。
其实这1行代码就是说初始化 ExtensionFactory 的实现,默认是首先加载 ExtensionFactory.class 然后调用了 getAdaptiveExtension()获取自适应拓展

1
com.alibaba.dubbo.common.extension.ExtensionLoader.createAdaptiveExtension

这个方法就是核心需要理解的啦。

  1. 调用 getAdaptiveExtensionClass 方法获取自适应拓展 Class 对象
  2. 通过反射进行实例化
  3. 调用 injectExtension 方法向拓展实例中注入依赖
    每个步骤里面又细分,尤其是第一步比较精彩!这里面把第1步拿出来说一下。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    /**
    * 1. 调用 getExtensionClasses 获取所有的拓展类
    * 2. 检查缓存,若缓存不为空,则返回缓存
    * 3. 若缓存为空,则调用 createAdaptiveExtensionClass 创建自适应拓展类
    *
    *
    * 首先从第一个逻辑说起,getExtensionClasses 这个方法用于获取某个接口的所有实现类。比如该方法可以获取
    * Protocol 接口的 DubboProtocol、HttpProtocol、InjvmProtocol 等实现类。在获取实现类的过程中,
    * 如果某个实现类被 Adaptive 注解修饰了,那么该类就会被赋值给 cachedAdaptiveClass 变量。此时,上面步
    * 骤中的第二步条件成立(缓存不为空),直接返回 cachedAdaptiveClass 即可。如果所有的实现类均未被
    * Adaptive 注解修饰,那么执行第三步逻辑,创建自适应拓展类。
    *
    * @return
    */
    private Class<?> getAdaptiveExtensionClass() {
    // 通过 SPI 获取所有的拓展类
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
    return cachedAdaptiveClass;
    }
    // 创建自适应拓展类
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
    运行代码的时候,会发现 ExtensionFactory 包含两个实现,其中有一个被 @Adaptive 修饰的 AdaptiveExtensionFactory,其实这个在getExtensionClasses内部会初始化赋值给cachedAdaptiveClass,所以return cachedAdaptiveClass;直接返回啦。
②type对应ExtensionLoader对象获取对应名称所对应的实现类
1
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");

这个就没有啥子说的啦,自己追代码

第1个Case就是通过Class.forName加载。

dubbo通过自实现的SPI来实现扩展,配置切换。 比如Protocol、Cluster、LoadBalance 等。当某些扩展不想在框架启动时候加载,
而希望扩展方法条用的时候,根据运行时参数进行加载。 —— 扩展未加载,难道可以使用扩展的方法?
dubbo通过自适应拓展机制很好的解决了,dubbo 会为拓展接口生成具有代理功能的代码,然后通过 javassist 或 jdk 编译这段
代码得到Class类,最后再通过反射创建代理类。

@Adaptive用在类上面,基本上都是显现类使用,这个实现类就是这个接口的默认实现(比如上面说的 AdaptiveExtensionFactory);但是这个注解多数使用在方法上。

Case-2

下面的测试引用com.alibaba.dubbo.common.extensionloader.ExtensionLoader_Adaptive_Test#test_getAdaptiveExtension_inject,为了方便阅读源码,做了稍微的修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 1    @Test
2 public void test_getAdaptiveExtension_inject() throws Exception {
3 LogUtil.start();
4 ExtensionLoader<Ext6> el = ExtensionLoader.getExtensionLoader(Ext6.class);
5 Ext6 ext = el.getAdaptiveExtension();
6
7 URL url = new URL("p1", "1.2.3.4", 1010, "path1");
8 url = url.addParameters("ext6", "impl1");
9
10 String ans = ext.echo(url, "ha");
11 assertEquals("Ext6Impl1-echo-Ext1Impl1-echo", ans);
12
13 Assert.assertTrue("can not find error.", LogUtil.checkNoError());
14 LogUtil.stop();
15
16 // simple.ext 对应 SimpleExt
17 url = url.addParameters("simple.ext", "impl2");
18 ans = ext.echo(url, "ha")
19 assertEquals("Ext6Impl1-echo-Ext1Impl2-echo", ans);
20
21 }

第4行代码已经在上个case分析过啦,现在分析第5行获取一个自适应的扩展。其实入口也是第4行处理ExtensionFactory加载需要走到的逻辑createAdaptiveExtension,那么为什么上面没有走到 createAdaptiveExtensionClassCode 方法呢?————是因为ExtensionFactory的实现类 AdaptiveExtensionFactory 被 @Adaptive 标注啦。

因为其被@Adaptive标注,在获取所有的拓展类getExtensionClasses逻辑中会赋值cachedAdaptiveClass成员变量。具体的赋值逻辑在loadClass方法中。

我们回头看一下在没有实现类被@Adaptive标注的情况下,Dubbo是怎做自适应扩展的?

下面再重温这段代码吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* 1. 调用 getAdaptiveExtensionClass 方法获取自适应拓展 Class 对象
* 2. 通过反射进行实例化
* 3. 调用 injectExtension 方法向拓展实例中注入依赖
* @return
*/
@SuppressWarnings("unchecked")
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);
}
}

/**
* 1. 调用 getExtensionClasses 获取所有的拓展类
* 2. 检查缓存,若缓存不为空,则返回缓存
* 3. 若缓存为空,则调用 createAdaptiveExtensionClass 创建自适应拓展类
*
*
* 首先从第一个逻辑说起,getExtensionClasses 这个方法用于获取某个接口的所有实现类。比如该方法可以获取
* Protocol 接口的 DubboProtocol、HttpProtocol、InjvmProtocol 等实现类。在获取实现类的过程中,
* 如果某个实现类被 Adaptive 注解修饰了,那么该类就会被赋值给 cachedAdaptiveClass 变量。此时,上面步
* 骤中的第二步条件成立(缓存不为空),直接返回 cachedAdaptiveClass 即可。如果所有的实现类均未被
* Adaptive 注解修饰,那么执行第三步逻辑,创建自适应拓展类。
*
* @return
*/
private Class<?> getAdaptiveExtensionClass() {
// 通过 SPI 获取所有的拓展类
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 创建自适应拓展类
return cachedAdaptiveClass = 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();
// 编译代码,生成 Class
return compiler.compile(code, classLoader);
}

/**
* 其实很简单 找个案例跑一下 比如 com.alibaba.dubbo.remoting.Transporter 或者 com.alibaba.dubbo.rpc.Protocol
*
* 默认生成一个代理类的字符串编码 {name}$Adaptive
*
* @return
*/
private String createAdaptiveExtensionClassCode() {
//...省略
}

通过createAdaptiveExtensionClassCode创建实现类的代码如下,为了方便阅读把全类名修改成简单的类名。可以断点到createAdaptiveExtensionClass中查看code变量值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.alibaba.dubbo.common.extensionloader.ext6_inject;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Ext6$Adaptive implements Ext6 {
public java.lang.String echo(URL arg0, java.lang.String arg1) {
if (arg0 == null) {
throw new IllegalArgumentException("url == null");
}
URL url = arg0;
String extName = url.getParameter("ext6");
if(extName == null) {
throw new IllegalStateException("Fail to get extension(Ext6) name from url("
+ url.toString() + ") use keys([ext6])");
}
Ext6 extension = ExtensionLoader.getExtensionLoader(Ext6.class).getExtension(extName);
return extension.echo(arg0, arg1);
}
}

通过code值就能大体看出来createAdaptiveExtensionClassCode方法做了啥事。而后对这个Ext6$Adaptive类加载实例化赋值给测试代码第5行的ext变量。紧接着走到第10行,执行ext的echo()。自适应加载过程中,配置信息全部放在url中,通过url变量进行控制。其实真实执行的实现类的下面这句代码:

1
Ext6 extension = ExtensionLoader.getExtensionLoader(Ext6.class).getExtension(url.getParameter("ext6"));

因为我们url中”ext6”的参数值”impl1”,所以其实实现类就是com.alibaba.dubbo.common.extensionloader.ext6_inject.impl.Ext6Impl1。在创建的过程中需要调用 injectExtension 方法向拓展实例中注入依赖,看一下 Ext6Impl1 的定义,存在 setExt1 和 setDao 两个方法。注入的主要实现通过objectFactory.getExtension(pt, property) 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// com.alibaba.dubbo.common.extension.ExtensionLoader#injectExtension

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);
}

objectFactory默认实现是AdaptiveExtensionFactory,但是通过其成员变量factories依次进行加载,默认是SpiExtensionFactory加载被@SPI注解修改的类型。所以Ext6Impl1中的成员变量SimpleExt ext1会被加载。

加载的时候因为 SimpleExt 的实现类都没有带有 @Adaptive 注解,所以像 Ext6$Adaptive 形式加载。
但是因为 SimpleExt 接口定义的时候方法使用 @Adaptive,所以生成的代码逻辑比 Ext6$Adaptive 复杂。

1
2
3
4
5
6
7
8
9
10
11
12
@SPI("impl1")
public interface SimpleExt {
// @Adaptive example, do not specify a explicit key.
@Adaptive
String echo(URL url, String s);

@Adaptive({"key1", "key2"})
String yell(URL url, String s);

// no @Adaptive
String bang(URL url, int i);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.alibaba.dubbo.common.extensionloader.ext1;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class SimpleExt$Adaptive implements SimpleExt {
public String echo(URL arg0, String arg1) {
if (arg0 == null)
throw new IllegalArgumentException("url == null");
URL url = arg0;
String extName = url.getParameter("simple.ext", "impl1");
if (extName == null)
throw new IllegalStateException("Fail to get extension(SimpleExt) name from url("
+ url.toString() + ") use keys([simple.ext])");
SimpleExt extension = (SimpleExt) ExtensionLoader.getExtensionLoader(SimpleExt.class).getExtension(extName);
return extension.echo(arg0, arg1);
}

public String yell(URL arg0, String arg1) {
if (arg0 == null)
throw new IllegalArgumentException("url == null");
URL url = arg0;
String extName = url.getParameter("key1", url.getParameter("key2", "impl1"));
if (extName == null)
throw new IllegalStateException("Fail to get extension(SimpleExt) name from url("
+ url.toString() + ") use keys([key1, key2])");
SimpleExt extension = (SimpleExt) ExtensionLoader.getExtensionLoader(SimpleExt.class).getExtension(extName);
return extension.yell(arg0, arg1);
}

public String bang(URL arg0, int arg1) {
throw new UnsupportedOperationException("method public abstract String SimpleExt.bang(URL,int) of interface SimpleExt is not adaptive method!");
}
}

实现带有制定的默认值,参数的设置也是有规则的,所以建议阅读 createAdaptiveExtensionClassCode 方法,阅读的时候参考开头推荐的官网源码就很简单。

最后构造好实例执行 echo 方法。

这里就可以看出来URL的作用,相当于数据总线,通过其绑定配置信息

总结

  • 后期补上 目的 如何设计 如何实现 优缺点

熟悉 ExtensionLoader.getExtensionLoader(type) 这种俄罗斯套娃式的加载。