在学习过程中,对Dubbo SPI 和 Adaptive的源码分析,自己的理解做一个总结记录。
内容建立在已经了解DubboSPI和Adaptive扩展点情况下对源码分析的关键点做总结。
DubboSPI是什么呢? 等同于Java的SPI和Spring的SPI,为了满足从外部环境或者三方Jar包中加载和实例化扩展定制的类,通过一些规则去读取文本文件里的类信息,然后加载到JVM中再实例化。
SPI的区别:
所以Java的SPI必须是文件名等同于接口名,Dubbo也是,为了方便通过文件名就知道它是哪个接口的配置方便解析,Spring是使用Spring.factories文件通过KV的方式定义映射关系,所以配置里一定要是类的全路径名。
DubboSPI的基本操作:
// 获取一个Protocol的扩展实现dubboProtocol Protocol protocol = ApplicationModel.defaultModel().getDefaultModule().getExtensionLoader(Protocol.class).getExtension("dubbo");
1. getExtensionLoader(): 从缓存中去查找指定类型的加载器,找不到就创建再放到缓存中
extensionLoadersMap.putIfAbsent(type, new ExtensionLoader<T>(type, this, scopeModel))
2. getExtension("dubbo"):从当前加载器的缓存中去查找指定name的实例,如果找不到就去创建。
public T getExtension(String name, boolean wrap) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
String cacheKey = name;
if (!wrap) {
cacheKey += "_origin";
}
final Holder<Object> holder = getOrCreateHolder(cacheKey);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 第一次缓存中不存在,直接到这里创建实例并且放入缓存中
instance = createExtension(name, wrap);
holder.set(instance);
}
}
}
return (T) instance;
}
2.1 创建的入口:
instance = createExtension(name, wrap);
private T createExtension(String name, boolean wrap) {
// 先加载外部配置的Class并且放到缓存中,再从缓存中获取name对应的Class
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null || unacceptableExceptions.contains(name)) {
throw findException(name);
}
try {
T instance = (T) extensionInstances.get(clazz);
if (instance == null) {
extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
instance = (T) extensionInstances.get(clazz);
instance = postProcessBeforeInitialization(instance, name);
injectExtension(instance);
instance = postProcessAfterInitialization(instance, name);
}
省略 .....................
}
2.2 加载外部配置缓存并获取指定name
Class<?> clazz = getExtensionClasses().get(name);
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
// 第一次缓存是空的所以初始化加载
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
2.3 创建的时候先去缓存中获取所有的外部配置,当然第一次先初始化缓存:
loadExtensionClasses()
初始化就是读取所有的外部配置信息并解析,根据不同的策略类对应不同的文件路径
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy, type.getName());
// compatible with old ExtensionFactory
if (this.type == ExtensionInjector.class) {
loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());
}
}
以上就完成了根据一个接口动态的加载外部配置对应的扩展类,主要步骤
1)获取或创建一个加载器 getExtensionLoader()
2)根据name从缓存中获取或创建一个扩展类实例 getExtensionClasses().get(name)
2-1) 缓存不存在则需要读取一次外部配置加载解析。
比如Java中的Driver接口,不同的数据库厂商提供各自的驱动都在三方Jar包中,通过SPI获取Driver接口的外部类。
3.Dubbo SPI的自适应扩展点
// 通过自适应扩展点生成一个Protocol的适配器类 Protocol protocol = ApplicationModel.defaultModel().getDefaultModule().getExtensionLoader(Protocol.class).getAdaptiveExtension();
@Adaptive可以注解在实现类上和接口的方法上,这两种用法有很大的区别。
3.1 @Adaptive注解在实现类上会直接从缓存中查找,优先级是最高的。找不到则尝试给当前接口生成一个适配器的类。
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
// 如果实现类注解了@Adaptive直接返回
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 否则生成适配器类
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
3.2 生成适配器类是通过字节码的方式,先拼接一个Java类的字符串代码
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
拼接核心是针对加了@Adaptive注解的方法,对该方法生成一个模板内容,该代码内容主要是根据extName调用DubboSPI的方法,那么就完成了根据name动态调用扩展子类的效果,就是一种适配器方法。
// 模板方法内容,每个注解@Adaptive的方法生成这个模板
public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
org.apache.dubbo.common.URL url = arg0.getUrl();
// 通过不同的判断获取extName需要与SPI配置文件中KEY一致,处理规则在这里generateExtNameAssignment
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), org.apache.dubbo.rpc.Protocol.class);
// 通过Dubbo的SPI根据extName获取扩展实现类再调用一样的方法,完成了适配器的效果
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)scopeModel.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
自适应扩展点按@Adaptive注解在实现类上,优先级最高,如果存在就直接返回。
@Adaptive注解在接口的方法上,会通过字节码的方式动态生成一个适配器类,给注解的方法加上模板内容,以上就是模板内容。