Dubbo SPI自适应扩展机制是是Dubbo SPI扩展到补充,强化了Dubbo SPI扩展,实现了在扩展方法被调用时,根据运行时参数进行动态加载。该机制的核心是为扩展接口动态的生成具有代理功能的代码,然后通过javassist或jdk编译这段代码,得到class类,最后通过反射得到具体的代理类。
示例
package org.apache.dubbo.demo.provider.javaspi;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
@SPI("ali")
public interface Pay {
@Adaptive({"payType"})
void pay(URL url);
}
package org.apache.dubbo.demo.provider.javaspi;
import org.apache.dubbo.common.URL;
public class AliPay implements Pay{
@Override
public void pay(URL url) {
System.out.println("支付宝支付");
}
}
package org.apache.dubbo.demo.provider.javaspi;
import org.apache.dubbo.common.URL;
public class WechatPay implements Pay{
@Override
public void pay(URL url) {
System.out.println("微信支付");
}
}
# 此处为Dubbo SPI的配置文件,文件路径为:META-INF/dubbo/org.apache.dubbo.demo.provider.javaspi.Pay
ali=org.apache.dubbo.demo.provider.javaspi.AliPay
wechat=org.apache.dubbo.demo.provider.javaspi.WechatPay
wrapper=org.apache.dubbo.demo.provider.javaspi.PayWrapper1
package org.apache.dubbo.demo.provider.javaspi;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.junit.jupiter.api.Test;
import java.util.ServiceLoader;
public class JavaSpiTest {
@Test
public void test() {
ExtensionLoader<Pay> extensionLoader = ExtensionLoader.getExtensionLoader(Pay.class);
// 获取自适应扩展
Pay adaptiveExtension = extensionLoader.getAdaptiveExtension();
// 使用支付宝支付
adaptiveExtension.pay(URL.valueOf("dubbo://192.168.0.101:20880/xxxService"));
// 使用微信支付
adaptiveExtension.pay(URL.valueOf("dubbo://192.168.0.101:20880/xxxService?payType=wechat"));
}
}
以上为Dubbo SPI的自适应扩展机制的Demo,通过Pay adaptiveExtension = extensionLoader.getAdaptiveExtension();可以发现,adaptiveExtension实例是个代理类,在调用pay方法时,根据URL参数来决定调用具体的扩展类。
源码分析
在对自适应拓展生成过程进行深入分析之前,我们先来看一下与自适应拓展息息相关的一个注解,即 Adaptive 注解。该注解的定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
String[] value() default {};
}
从上面的代码中可知,Adaptive 可注解在类或方法上。当 Adaptive 注解在类上时,Dubbo 不会为该类生成代理类。注解在方法(接口方法)上时,Dubbo 则会为该方法生成代理逻辑。Adaptive 注解在类上的情况很少,在 Dubbo 中,仅有两个类被 Adaptive 注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory。此种情况,表示拓展的加载逻辑由人工编码完成。更多时候,Adaptive 是注解在接口方法上的,表示拓展的加载逻辑需由框架自动生成。Adaptive 注解的地方不同,相应的处理逻辑也是不同的。注解在类上时,处理逻辑比较简单,本文就不分析了。注解在接口方法上时,处理逻辑较为复杂,本章将会重点分析此块逻辑。
获取自适应扩展
/**
* 加载自适应扩展,这里有双重缓存获取及双重检查,避免重复执行创建ExtensionFactory
*/
@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
// 从缓存中获取自适应扩展实例
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError != null) {
throw new IllegalStateException("Failed to create adaptive instance: " +
createAdaptiveInstanceError.toString(),
createAdaptiveInstanceError);
}
// 加锁,避免重复创建自适应扩展
synchronized (cachedAdaptiveInstance) {
// 从缓存中获取ExtensionFactory
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);
}
}
}
}
return (T) instance;
}
这里的方法很简单,就是先从缓存中获取自适应扩展实例,不存在的则通过createAdaptiveExtension()方法创建自适应扩展实例,并添加到缓存。
/**
* 创建自适应扩展
*/
@SuppressWarnings("unchecked")
private T createAdaptiveExtension() {
try {
// 获取自适应扩展且实例化后进行IOC注入
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
createAdaptiveExtension方法的代码非常少,但是包含了三个逻辑在里面:
1、获取自适应扩展类
2、实例化自适应扩展类
3、向自适应扩展类实例中注入依赖
第二步的逻辑最简单,就是将第一步获取到的扩展类实例化,第三步我们在Dubbo SPI中已做过分析,此处不再探讨。接下来我们重点分析下第一步
/**
* 获取自适应扩展类
*/
private Class<?> getAdaptiveExtensionClass() {
// 获取扩展类
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 创建自适应扩展类
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
getAdaptiveExtensionClass方法的代码也很少,先是获取扩展类,再从缓存中获取自适应扩展类,如果缓存不存在,则创建自适应扩展类。其中getExtensionClasses()在Dubbo SPI中已经做过详细的分析,此处不再重复,接下来继续看createAdaptiveExtensionClass()方法。
/**
* 动态生成具有代理功能的自适应扩展类并编译
*/
private Class<?> createAdaptiveExtensionClass() {
// 动态生成自有代理功能的自适应扩展类
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
ClassLoader classLoader = findClassLoader();
// 获取编译器
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
// 编译
return compiler.compile(code, classLoader);
}
从上面的代码来看只是执行了四行代码,首先是通过new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate()完成自适应扩展类代码的动态生成,而后获取类加载器,然后获取Compile实例的编译器(Dubbo模式使用javassist作为编译器),最后通过编译器编译动态生成的自适应扩展类的代码并返回,接下来把重点放在动态代码生成的逻辑上。
自适应扩展类代码生成
我们顺着String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate()这行代码来分析,第一步是通过AdaptiveClassCodeGenerator的构造方法来指定扩展类以及扩展类的默认实现的名称,其中扩展类的默认实现的名称是通过作用在扩展类上的@SPI注解来实现的,在Dubbo SPI中已经分析过;第二步是代码生成的逻辑。
/**
* 构造函数
* @param type 扩展类
* @param defaultExtName 默认实现的扩展类,在@SPI中声明
*/
public AdaptiveClassCodeGenerator(Class<?> type, String defaultExtName) {
this.type = type;
this.defaultExtName = defaultExtName;
}
/**
* 生成代码
*/
public String generate() {
// 不需要生成自适应类,因为没有找到自适应方法。
if (!hasAdaptiveMethod()) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
}
StringBuilder code = new StringBuilder();
// 生成包信息
code.append(generatePackageInfo());
// 生成导入的包信息
code.append(generateImports());
// 生成类声明
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();
}
由于generate()方法的代码太多,为了方便阅读,我们将对该方法进行拆分
生成包信息
private String generatePackageInfo() {
return String.format(CODE_PACKAGE, type.getPackage().getName());
}
包信息生成的逻辑非常简单,其中CODE_PACKAGE为指定的目标,通过String的format方法进行响应的替换。
生成包导入信息
private String generateImports() {
return String.format(CODE_IMPORTS, ExtensionLoader.class.getName());
}
包导入信息生成的逻辑同样非常简单,与包信息生成一样。
生成类声明
private String generateClassDeclaration() {
return String.format(CODE_CLASS_DECLARATION, type.getSimpleName(), type.getCanonicalName());
}
类声明生成的逻辑同样非常简单,与包信息生成一样。
生成方法
private String generateMethod(Method method) {
String methodReturnType = method.getReturnType().getCanonicalName();
String methodName = method.getName();
// 生成方法内容
String methodContent = generateMethodContent(method);
// 生成方法参数
String methodArgs = generateMethodArguments(method);
// 生成方法需要抛出的异常
String methodThrows = generateMethodThrows(method);
return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
}
此处显示分别获取到了该方法的返回值类型及方法名,然后依次生成方法内容、方法参数、异常抛出,最后通过String的format格式化生成的代码。其中方法参数、异常抛出的代码生成逻辑比较简单,这里不做分析,接下来主要分析方法内容的生成逻辑
private String generateMethodContent(Method method) {
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512);
// Adaptive注解检测,无此注解的方法在调用时将会抛出异常
if (adaptiveAnnotation == null) {
// 无Adaptive注解的方法,在方法内部直接抛出异常
return generateUnsupported(method);
} else {
// 获取类型为URL的参数索引,如void pay(URL url)返回的为0,void pay(String name, URL url)返回的为1
int urlTypeIndex = getUrlTypeIndex(method);
// urlTypeIndex != -1,表示参数列表中存在 URL 参数
if (urlTypeIndex != -1) {
// 为 URL 类型参数生成判空代码,格式如下:
// if (arg + urlTypeIndex == null) {throw new IllegalArgumentException("url == null");}
code.append(generateUrlNullCheck(urlTypeIndex));
} else {
// 参数列表中不存在 URL 类型参数的校验
code.append(generateUrlAssignmentIndirectly(method));
}
// 解析方法上@Adaptive注解的值
String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
// 查询该方法上是否有参数为org.apache.dubbo.rpc.Invocation
boolean hasInvocation = hasInvocationArgument(method);
code.append(generateInvocationArgumentNullCheck(method));
// 解析URL参数,生成ExtensionName代码相关的逻辑
code.append(generateExtNameAssignment(value, hasInvocation));
// 校验ExtensionName是否为null
code.append(generateExtNameNullCheck(value));
// 生成获取Extension语句
code.append(generateExtensionAssignment());
// 生成返回语句
code.append(generateReturnAndInvocation(method));
}
return code.toString();
}
generateMethodContent方法分类了两部分,当该方法无@Adaptive注解时,逻辑非常简单,抛出异常;有该注解时则需要判断该方法是否包含URL参数,也分为了两种情况,当有URL参数时,逻辑也很简单,需要判断该URL参数是否为null,为null则抛出异常,第二种情况相对复杂,在该方法没有URL参数时,继续深入generateUrlAssignmentIndirectly方法
private String generateUrlAssignmentIndirectly(Method method) {
Class<?>[] pts = method.getParameterTypes();
Map<String, Integer> getterReturnUrl = new HashMap<>();
// 遍历方法的参数类型列表
for (int i = 0; i < pts.length; ++i) {
// 遍历方法列表,寻找可返回 URL 的 getter 方法
for (Method m : pts[i].getMethods()) {
String name = m.getName();
// 1. 方法名以 get 开头,或方法名大于3个字符
// 2. 方法的访问权限为 public
// 3. 非静态方法
// 4. 方法参数数量为0
// 5. 方法返回值类型为 URL
if ((name.startsWith("get") || name.length() > 3)
&& Modifier.isPublic(m.getModifiers())
&& !Modifier.isStatic(m.getModifiers())
&& m.getParameterTypes().length == 0
&& m.getReturnType() == URL.class) {
getterReturnUrl.put(name, i);
}
}
}
if (getterReturnUrl.size() <= 0) {
// 如果所有参数中均不包含可返回 URL 的 getter 方法,则抛出异常
throw new IllegalStateException("Failed to create adaptive class for interface " + type.getName()
+ ": not found url parameter or url attribute in parameters of method " + method.getName());
}
// 默认获取方法名为getUrl
Integer index = getterReturnUrl.get("getUrl");
// 如果方法名为getUrl的方法存在,则通过索引获取
if (index != null) {
return generateGetUrlNullCheck(index, pts[index], "getUrl");
} else {
// 如果不存在方法名为getUrl的方法,则获取满足上述五个条件的首个getter方法
Map.Entry<String, Integer> entry = getterReturnUrl.entrySet().iterator().next();
// 生成对getter方法进行判断处理的代码
return generateGetUrlNullCheck(entry.getValue(), pts[entry.getValue()], entry.getKey());
}
}
private String generateGetUrlNullCheck(int index, Class<?> type, String method) {
// Null point check
StringBuilder code = new StringBuilder();
// 为 getter 方法返回的 URL 生成判空代码,格式如下:
// if (argN.getter方法名() == null)
// throw new IllegalArgumentException(参数全限定名 + argument getUrl() == null);
code.append(String.format("if (arg%d == null) throw new IllegalArgumentException("%s argument == null");\n",
index, type.getName()));
code.append(String.format("if (arg%d.%s() == null) throw new IllegalArgumentException("%s argument %s() == null");\n",
index, method, type.getName(), method));
// 生成赋值语句,格式如下:
// URL全限定名 url = argN.getter方法名(),比如
// com.alibaba.dubbo.common.URL url = invoker.getUrl();
code.append(String.format("%s url = arg%d.%s();\n", URL.class.getName(), index, method));
return code.toString();
}
在generateUrlAssignmentIndirectly方法中有双重循环,先是遍历扩展方法的参数类型,再遍历各个参数类型的方法,找出满足方法访问权限为public、为非静态方法、方法参数为0,返回值类型为URL的getter方法,如果该方法不存在,则抛异常,否则根据不同情况生成上述存在的getter方法的判断代码。到这里,我们回到generateMethodContent方法,接着下面的流程来分析generateInvocationArgumentNullCheck方法
private String generateInvocationArgumentNullCheck(Method method) {
Class<?>[] pts = method.getParameterTypes();
return IntStream.range(0, pts.length).filter(i -> CLASSNAME_INVOCATION.equals(pts[i].getName()))
.mapToObj(i -> String.format(CODE_INVOCATION_ARGUMENT_NULL_CHECK, i, i))
.findFirst().orElse("");
}
generateInvocationArgumentNullCheck方法逻辑相对简单,如果存在Invocation,则为其生成判空方法,否则生成空字符串。到这里,我们回到generateMethodContent方法,接着下面的流程来分析generateExtNameAssignment方法
private String generateExtNameAssignment(String[] value, boolean hasInvocation) {
// TODO: refactor it
String getNameCode = null;
// 遍历 value,这里的 value 是 Adaptive 的注解值
// 此处循环目的是生成从URL中获取拓展名的代码,生成的代码会赋值给 getNameCode 变量。注意这
// 个循环的遍历顺序是由后向前遍历的。
for (int i = value.length - 1; i >= 0; --i) {
// 当 i 为最后一个元素的坐标时
if (i == value.length - 1) {
// 默认拓展名非空
if (null != defaultExtName) {
// protocol 是 url 的一部分,可通过 getProtocol 方法获取,其他的则是从
// URL 参数中获取。因为获取方式不同,所以这里要判断 value[i] 是否为 protocol
if (!"protocol".equals(value[i])) {
// hasInvocation 用于标识方法参数列表中是否有 Invocation 类型参数
if (hasInvocation) {
// 生成的代码功能等价于下面的代码:
// url.getMethodParameter(methodName, value[i], defaultExtName)
// 以 LoadBalance 接口的 select 方法为例,最终生成的代码如下:
// url.getMethodParameter(methodName, "loadbalance", "random")
getNameCode = String.format("url.getMethodParameter(methodName, "%s", "%s")", value[i], defaultExtName);
} else {
// 生成的代码功能等价于下面的代码:
// url.getParameter(value[i], defaultExtName)
getNameCode = String.format("url.getParameter("%s", "%s")", value[i], defaultExtName);
}
} else {
// 生成的代码功能等价于下面的代码:
// ( url.getProtocol() == null ? defaultExtName : url.getProtocol() )
getNameCode = String.format("( url.getProtocol() == null ? "%s" : url.getProtocol() )", defaultExtName);
}
// 默认拓展名为空
} else {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
// 生成代码格式同上
getNameCode = String.format("url.getMethodParameter(methodName, "%s", "%s")", value[i], defaultExtName);
} else {
// 生成的代码功能等价于下面的代码:
// url.getParameter(value[i])
getNameCode = String.format("url.getParameter("%s")", value[i]);
}
} else {
// 生成从 url 中获取协议的代码,比如 "dubbo"
getNameCode = "url.getProtocol()";
}
}
} else {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
// 生成代码格式同上
getNameCode = String.format("url.getMethodParameter(methodName, "%s", "%s")", value[i], defaultExtName);
} else {
// 生成的代码功能等价于下面的代码:
// url.getParameter(value[i], getNameCode)
// 以 Transporter 接口的 connect 方法为例,最终生成的代码如下:
// url.getParameter("client", url.getParameter("transporter", "netty"))
getNameCode = String.format("url.getParameter("%s", %s)", value[i], getNameCode);
}
} else {
// 生成的代码功能等价于下面的代码:
// url.getProtocol() == null ? getNameCode : url.getProtocol()
// 以 Protocol 接口的 connect 方法为例,最终生成的代码如下:
// url.getProtocol() == null ? "dubbo" : url.getProtocol()
getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
}
}
}
// 格式化
return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode);
}
generateExtNameAssignment方法用于根据 SPI 和 Adaptive 注解值生成“获取拓展名逻辑”,同时生成逻辑也受 Invocation 类型参数影响,综合因素导致本段逻辑相对复杂。本段逻辑可能会生成但不限于下面的代码:
// 第一种
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
// 第二种
String extName = url.getMethodParameter(methodName, "loadbalance", "random");
// 第三种
String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
该方法的逻辑判断条件太多,阅读起来比较难受,可以通过多次调试去看看。到这里,我们回到generateMethodContent方法,接着下面的流程来分析generateReturnAndInvocation方法
private String generateReturnAndInvocation(Method method) {
String returnStatement = method.getReturnType().equals(void.class) ? "" : "return ";
String args = IntStream.range(0, method.getParameters().length)
.mapToObj(i -> String.format(CODE_EXTENSION_METHOD_INVOKE_ARGUMENT, i))
.collect(Collectors.joining(", "));
return returnStatement + String.format("extension.%s(%s);\n", method.getName(), args);
}
generateReturnAndInvocation方法为生成返回语句,逻辑不复杂,不做太多分析。
生成代码简单样式
public class Demo implements org.apache.dubbo.demo.provider.javaspi.Pay {
public void say() {
throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.demo.provider.javaspi.Pay.say() of interface org.apache.dubbo.demo.provider.javaspi.Pay is not adaptive method!");
}
public void pay(org.apache.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("payType", "ali");
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.demo.provider.javaspi.Pay) name from url (" + url.toString() + ") use keys([payType])");
org.apache.dubbo.demo.provider.javaspi.Pay extension = (org.apache.dubbo.demo.provider.javaspi.Pay)ExtensionLoader.getExtensionLoader(org.apache.dubbo.demo.provider.javaspi.Pay.class).getExtension(extName);
extension.pay(arg0);
}
}
总结
至此,Dubbo SPI自适应扩展的源码分析结束,在这里对其执行流程做个总结: