上一篇介绍了dubbo SPI 扩展点的四大特性中的自动包装,接下来再介绍其他的特性。
扩展点自适应
还是先看官方文档http://dubbo.apache.org/zh-cn/docs/dev/SPI.html。可以看到
- ExtensionLoader 注入的依赖扩展点是一个 Adaptive 实例,直到扩展点方法执行时才决定调用是一个扩展点实现
- Dubbo 使用 URL 对象(包含了Key-Value)传递配置信息。扩展点方法调用会有URL参数(或是参数有URL成员)这样依赖的扩展点也可以从URL拿到配置信息,所有的扩展点自己定好配置的Key后,配置信息从URL上从最外层传入。URL在配置传递上即是一条总线。
通俗的讲就是,dubbo SPI可以在方法执行时动态选择扩展点实现类,原理就是通过方法中的url参数拿到配置信息,再根据拿到的配置信息选择扩展点实现类。dubbo在实现过程中通过javassist动态编译java类,实现了这个功能。接下来还是以Protocol接口为例说明。
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
}
其中export和refer两个方法有@Adaptive注解,表明这两个方法执行的时候会动态选择Protocol扩展实现类。
接下来我们看看dubbo源码中是怎样使用的。
我们可以看到dubbo在SeviceConfig中使用了getAdaptiveExtension()方法,接下来我们看看他是怎么实现的
private Class<?> createAdaptiveExtensionClass() {
// 动态拼接类的字符串
String code = createAdaptiveExtensionClassCode();
// 拿到classloader
ClassLoader classLoader = findClassLoader();
// 拿到动态编译类
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
// 根据第一步拿到的字符串动态编译成java类,这里的原理时使用了javassist动态编译
return compiler.compile(code, classLoader);
}
接下来在看看他的动态类代码时如何生成的
// 这方法用字符串拼接除了一个java类的代码
private String createAdaptiveExtensionClassCode() {
StringBuilder codeBuilder = new StringBuilder();
Method[] methods = type.getMethods();
boolean hasAdaptiveAnnotation = false;
// 是否有Adaptive注解
for (Method m : methods) {
if (m.isAnnotationPresent(Adaptive.class)) {
hasAdaptiveAnnotation = true;
break;
}
}
// no need to generate adaptive class since there's no adaptive method found.
if (!hasAdaptiveAnnotation) {
throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");
}
// package很熟悉,这个类所处的报名首先要拼接
codeBuilder.append("package ").append(type.getPackage().getName()).append(";");
// 然后import 其他的类
codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";");
// 动态代理的类名称为接口名+$Adaptive
codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive").append(" implements ").append(type.getCanonicalName()).append(" {");
// 定义logger日志对象
codeBuilder.append("\nprivate static final org.apache.dubbo.common.logger.Logger logger = org.apache.dubbo.common.logger.LoggerFactory.getLogger(ExtensionLoader.class);");
codeBuilder.append("\nprivate java.util.concurrent.atomic.AtomicInteger count = new java.util.concurrent.atomic.AtomicInteger(0);\n");
// 编译每一个接口的方法,动态生成方法的代码
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);
// 如果方法上没有Adaptive注解,表明这个方法不支持动态选择,则生成的动态类,此方法要抛出异常
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;
// 找到方法中