『Dubbo SPI源码分析』@Adaptive 机制分析
- 基于 2.7.0 版本
- 上一章:『Dubbo SPI源码分析』依赖注入机制分析
- 创建测试 demo
package com.luban.dubbo_spi.api;
@SPI
public interface Car {
public void getColor();
@Adaptive
public void getColorForUrl(URL url);
}
- 根据接口,先创建一个实现类
```java
package com.luban.dubbo_spi.impl;
public class RedCar implements Car {
public void getColor() {
System.out.println("red");
}
@Override
public void getColorForUrl(URL url) {
}
}
package com.luban.dubbo_spi.impl;
public class BlackCar implements Car {
public void getColor() {
System.out.println("black");
}
@Override
public void getColorForUrl(URL url) {
System.out.println("blackUrl");
}
}
- 然后在 resources 创建一个目录 META-INF.services 目录
- 并在 META-INF.services 目录中创建文件 com.luban.dubbo_spi.api.Car 一定要与接口同名
red = com.luban.dubbo_spi.impl.RedCar
black = com.luban.dubbo_spi.impl.BlackCar
public class CarDemo {
public static void main(String[] args) {
ExtensionLoader<Car> carLoader = ExtensionLoader.getExtensionLoader(Car.class);
Car adCar = carLoader.getAdaptiveExtension();
Map<String, String> map = new HashMap<>();
map.put("car", "black");
URL url = new URL("","",1, map);
adCar.getColorForUrl(url);
}
}
- @Adaptive 除了可以标记在自定义的 Adaptive 类(即 com.luban.dubbo_spi.impl.AdaptiveCar 类)上面以外(『Dubbo SPI源码分析』依赖注入机制分析),还可以标记在接口的对应方法中,获取 dubbo 帮你实现的代理类,首先也是按照老套路,先执行 ExtensionLoader.getExtensionLoader() 扫描配置文件中实现了 com.luban.dubbo_spi.api.Car 的类以后,就可以从中获取到 Adaptive 扩展类
public class CarDemo {
public static void main(String[] args) {
ExtensionLoader<Car> carLoader = ExtensionLoader.getExtensionLoader(Car.class);
Car adCar = carLoader.getAdaptiveExtension();
...
}
}
- com.luban.dubbo_spi.api.Car 的 ExtensionLoader 先判断是否有过 Adaptive 实例对象,如果有则直接返回,没有就创建
public class ExtensionLoader<T> {
private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
private volatile Throwable createAdaptiveInstanceError;
...
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);
}
}
}
} else {
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
}
- ExtensionLoader 会调用 getAdaptiveExtensionClass() 获取 Adaptive 扩展类,其中依赖注入就忽略了,因为配置的 case 没有依赖注入
public class ExtensionLoader<T> {
...
@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);
}
}
}
- ExtensionLoader 继续调用 createAdaptiveExtensionClass() 方法,用于生成代理类
public class ExtensionLoader<T> {
...
private Class<?> createAdaptiveExtensionClass() {
String code = createAdaptiveExtensionClassCode();
...
}
}
- 首先遍历每个方法,判断存在一个方法有注释 @Adaptive,没有则抛异常。找到有注释的方法后,先找到存在 URL.class 入参的位置,生成校验 URL 入参代码。然后从 @Adaptive 的 value 属性中取值,没有的话,只能用类名小写代替,即为 “car”(这个参数是 URL 上的占位符 key,用作取数据)。然后判断方法参数列表是否有 org.apache.dubbo.rpc.Invocation 入参,有也生成相应的校验入参代码。最后根据 value 值,生成获取参数的代码,并返回
- 重点
- @Adaptive 注解中,如果填写了 value 值,代表指定占位符名称,没填写,则用类名小写
- 一定要填入 URL.class 参数,除非自定义其他 protocol 协议
- 没标注 @Adaptive 注解的方法,使用会抛异常
- 可以再添加一个 org.apache.dubbo.rpc.Invocation 入参
- 生成的代理代码如下
package com.luban.dubbo_spi.api;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Car$Adaptive implements com.luban.dubbo_spi.api.Car {
public void getColorForUrl(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("car");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.luban.dubbo_spi.api.Car) name from url(" + url.toString() + ") use keys([car])");
com.luban.dubbo_spi.api.Car extension = (com.luban.dubbo_spi.api.Car)ExtensionLoader.getExtensionLoader(com.luban.dubbo_spi.api.Car.class).getExtension(extName);extension.getColorForUrl(arg0);
}
public void getColor() {throw new UnsupportedOperationException("method public abstract void com.luban.dubbo_spi.api.Car.getColor() of interface com.luban.dubbo_spi.api.Car is not adaptive method!");
}
}
public class ExtensionLoader<T> {
private final Class<?> type;
private String cachedDefaultName;
...
private String createAdaptiveExtensionClassCode() {
StringBuilder codeBuilder = new StringBuilder();
Method[] methods = type.getMethods();
boolean hasAdaptiveAnnotation = false;
for (Method m : methods) {
if (m.isAnnotationPresent(Adaptive.class)) {
hasAdaptiveAnnotation = true;
break;
}
}
if (!hasAdaptiveAnnotation) {
throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");
}
codeBuilder.append("package ").append(type.getPackage().getName()).append(";");
codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";");
codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive").append(" implements ").append(type.getCanonicalName()).append(" {");
for (Method method : methods) {
Class<?> rt = method.getReturnType();
Class<?>[] pts = method.getParameterTypes();
Class<?>[] ets = method.getExceptionTypes();
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512);
if (adaptiveAnnotation == null) {
code.append("throw new UnsupportedOperationException(\"method ")
.append(method.toString()).append(" of interface ")
.append(type.getName()).append(" is not adaptive method!\");");
} else {
int urlTypeIndex = -1;
for (int i = 0; i < pts.length; ++i) {
if (pts[i].equals(URL.class)) {
urlTypeIndex = i;
break;
}
}
if (urlTypeIndex != -1) {
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",
urlTypeIndex);
code.append(s);
s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex);
code.append(s);
}
else {
String attribMethod = null;
LBL_PTS:
for (int i = 0; i < pts.length; ++i) {
Method[] ms = pts[i].getMethods();
for (Method m : ms) {
String name = m.getName();
if ((name.startsWith("get") || name.length() > 3)
&& Modifier.isPublic(m.getModifiers())
&& !Modifier.isStatic(m.getModifiers())
&& m.getParameterTypes().length == 0
&& m.getReturnType() == URL.class) {
urlTypeIndex = i;
attribMethod = name;
break LBL_PTS;
}
}
}
if (attribMethod == null) {
throw new IllegalStateException("fail to create adaptive class for interface " + type.getName()
+ ": not found url parameter or url attribute in parameters of method " + method.getName());
}
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
urlTypeIndex, pts[urlTypeIndex].getName());
code.append(s);
s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
code.append(s);
s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod);
code.append(s);
}
String[] value = adaptiveAnnotation.value();
if (value.length == 0) {
String splitName = StringUtils.camelToSplitName(type.getSimpleName(), ".");
value = new String[]{splitName};
}
boolean hasInvocation = false;
for (int i = 0; i < pts.length; ++i) {
if (("org.apache.dubbo.rpc.Invocation").equals(pts[i].getName())) {
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
code.append(s);
s = String.format("\nString methodName = arg%d.getMethodName();", i);
code.append(s);
hasInvocation = true;
break;
}
}
String defaultExtName = cachedDefaultName;
String getNameCode = null;
for (int i = value.length - 1; i >= 0; --i) {
if (i == value.length - 1) {
if (null != defaultExtName) {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
} else {
getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
}
} else {
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 {
getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
}
} else {
getNameCode = "url.getProtocol()";
}
}
} else {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
} else {
getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
}
} else {
getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
}
}
}
code.append("\nString extName = ").append(getNameCode).append(";");
String s = String.format("\nif(extName == null) " +
"throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");",
type.getName(), Arrays.toString(value));
code.append(s);
s = String.format("\n%s extension = (%,
type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
code.append(s);
if (!rt.equals(void.class)) {
code.append("\nreturn ");
}
s = String.format("extension.%s(", method.getName());
code.append(s);
for (int i = 0; i < pts.length; i++) {
if (i != 0) {
code.append(", ");
}
code.append("arg").append(i);
}
code.append(");");
}
codeBuilder.append("\npublic ").append(rt.getCanonicalName()).append(" ").append(method.getName()).append("(");
for (int i = 0; i < pts.length; i++) {
if (i > 0) {
codeBuilder.append(", ");
}
codeBuilder.append(pts[i].getCanonicalName());
codeBuilder.append(" ");
codeBuilder.append("arg").append(i);
}
codeBuilder.append(")");
if (ets.length > 0) {
codeBuilder.append(" throws ");
for (int i = 0; i < ets.length; i++) {
if (i > 0) {
codeBuilder.append(", ");
}
codeBuilder.append(ets[i].getCanonicalName());
}
}
codeBuilder.append(" {");
codeBuilder.append(code.toString());
codeBuilder.append("\n}");
}
codeBuilder.append("\n}");
if (logger.isDebugEnabled()) {
logger.debug(codeBuilder.toString());
}
return codeBuilder.toString();
}
}
- 当生成代理类代码后,通过获取加载器,以及编译器来生成对应代码,并返回
public class ExtensionLoader<T> {
...
private Class<?> createAdaptiveExtensionClass() {
String code = createAdaptiveExtensionClassCode();
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);
}
}
- 代码生成之后,要传入 URL 参数,以获取实际执行的类,其中参数一定是 “car”,而 value 值是在 com.luban.dubbo_spi.api.Car 配置文件的类
public class CarDemo {
public static void main(String[] args) {
ExtensionLoader<Car> carLoader = ExtensionLoader.getExtensionLoader(Car.class);
Car adCar = carLoader.getAdaptiveExtension();
Map<String, String> map = new HashMap<>();
map.put("car", "black");
URL url = new URL("","",1, map);
adCar.getColorForUrl(url);
}
}
- 实际执行的时候,就是执行下面这个代理类了,其中会根据 URL 的参数,通过 getExtensionLoader() 获取指定的类并且实例化后执行 getColorForUrl() 方法
package com.luban.dubbo_spi.api;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Car$Adaptive implements com.luban.dubbo_spi.api.Car {
public void getColorForUrl(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("car");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.luban.dubbo_spi.api.Car) name from url(" + url.toString() + ") use keys([car])");
com.luban.dubbo_spi.api.Car extension = (com.luban.dubbo_spi.api.Car)ExtensionLoader.getExtensionLoader(com.luban.dubbo_spi.api.Car.class).getExtension(extName);extension.getColorForUrl(arg0);
}
public void getColor() {throw new UnsupportedOperationException("method public abstract void com.luban.dubbo_spi.api.Car.getColor() of interface com.luban.dubbo_spi.api.Car is not adaptive method!");
}
}
- 总结