Dubbo系列-SPI扩展(下)
前言
上一篇文章Dubbo系列-SPI扩展(上),通过对比JDK、Spring、Dubbo对SPI的不同实现,对它们的区别有一个全局的了解,其中Dubbo SPI实现最为复杂,其在整个框架设计与实现上发挥着极大的作用。
本篇会继续深入Dubbo SPI的其它特性,包括Dubbo3引入的作用域模型,及Dubbo SPI扩展点的四大特性(自动包装、自动加载、自适应、自动激活)。
作用域模型
定义
在介绍Dubbo的扩展点时,原来的获取扩展点的静态方法已经标记废弃,如下所示,取而代之的是ApplicationModel的东西进行获取。
@Deprecated
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
return ApplicationModel.defaultModel().getDefaultModule().getExtensionLoader(type);
}
其中ApplicationModel是Dubbo3引入的,其对应着作用域ExtensionScope#APPLICATION,除此之外,还有ExtensionScope#FRAMEWORK、ExtensionScope#MODULE、ExtensionScope#SELF。
SPI扩展点作用域包括:
ExtensionScope#FRAMEWORK:框架级别作用域,对所有的application和module可见;对应着域模型 FrameworkModel
ExtensionScope#APPLICATION:应用级别作用域,对所有module可见;对应着域模型 ApplicationModel
ExtensionScope#MODULE:模块级别作用域;对应着域模型 ModuleModel
ExtensionScope#SELF:自给自足,为每个作用域创建一个实例,用于特殊的SPI扩展,如ExtensionInjector。
每个作用域模型缓存着对应作用域的扩展点的相关信息。可以类比ClassLoader理解。
关于作用域有个可见性,在查找作用域对应的扩展加载器ExtensionLoader时就如ClassLoader的双亲委派,上篇文章有提到,会递归查询上一级的作用域。
例如@SPI注解为ExtensionScope.Module,则获取ExtensionLoader过程ExtensionScope.Module -> Application -> Framework
1)、先ModuleScope缓存中查找,找到返回,找不到则进入2)
2)、再Application缓存中找,找到返回,找不到则匹配注解作用域,匹配则创建ExtensionLoader实例,不匹配进入3)
3)、再Framework缓存中找,找到返回,找不到则匹配注解作用域,匹配则创建ExtensionLoader实例,不匹配进入4)
4)、最后回到了ModuleScope匹配注解作用域,匹配则创建ExtensionLoader实例,不匹配不创建,返回null
对应流程图:
作用
为什么会在Dubbo3的新版本中加入这个作用域模型呢?
1、让Dubbo支持多应用的部署,可能大企业才有诉求,即一个JVM启动两个Dubbo应用
可以参考github issue有关场景
2、从架构设计上,之前都是一个作用域,通过静态类进行属性共享,引入域模型解决了静态属性资源共享、清理的问题
3、分层模型可以将应用的管理和模块的管理进行分开管理
设计与实现
ExtentsionAccessor作为扩展的统一访问接口
用于获取扩展管理器ExtensionDirector
获取扩展加载器ExtensionLoader
获取扩展对象(名字、自适应、默认)
ExtensionDirector扩展管理器
Class到扩展加载器ExtensionLoader的缓存
Class到作用域ExtensionScope的缓存
父扩展管理器ExtensionDirector
所属的模型实例对象scopeModel
扩展点处理器List<ExtensionPostProcessors>
ScopeModel作用域模型抽象父类
表示内部层次结构的内部id
父模型ScopeModel
当前作用域ExtensionScope
当前作用域模型对应的扩展管理器ExtensionDirector
当前作用域模型对应的Bean工厂管理ScopeBeanFactory
当前是否是内部作用域internalScope:boolean
三个不同的作用域模型实现
ModuleModel的构造器需要传入ApplicationModel实例
ApplicationModel构造器需要传入FrameworkModel实例
ApplicationModel和ModuleModel构造器可见性protected;FrameworkModel可见性public
举例
1、Codec2编解码器,一种Codec2编解码器在一个FrameworkModel中只有一个实例;还有如Protocol等。
2、统计服务MetricsService,一个ApplicationModel只有一个实例;FrameworkModel作用域获取不到。
3、Filter过滤器在一个ModuleModel中有一个实例;在ApplicationModel和FrameworkModel作用域获取不到。
SPI四大特性
Dubbo扩展点的四大特性包括:
自动包装
自动加载
自适应
自动激活
不管哪种特性,都是获取SPI接口的扩展点,都需要加载配置文件。
自动包装
在ExtensionLoader加载扩展时,如果发现这个扩展类包含其它的扩展点作为构造函数的参数,则这个扩展类会被当成Wrapper类。
例如ProtocolFilterWrapper,实现了Protocol,同时使用Protocol作为构造器的入参。这是一种装饰器模式,对逻辑进行封装或对子类的增强。
public class ProtocolFilterWrapper implements Protocol {
private final Protocol protocol;
public ProtocolFilterWrapper(Protocol protocol) {
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
}
一个示例
// 接口
@SPI(value = DubboSimpleSpiImpl1.NAME, scope = ExtensionScope.APPLICATION)
public interface DubboSimpleSpi {
void sayHello();
}
// 实现1 略
// 实现2
public class DubboSimpleSpiImpl2 implements DubboSimpleSpi {
public static final String NAME = "dubboSimpleSpiImpl2";
@Override
public void sayHello() {
System.out.println("DubboSimpleSpiImpl2:" + this + " say hello");
}
}
// 包装类
@Wrapper
public class DubboSimpleSpiWrapper implements DubboSimpleSpi {
// 依赖注入
private DubboSimpleSpi dubboSimpleSpi;
public DubboSimpleSpiWrapper(DubboSimpleSpi dubboSimpleSpi) {
this.dubboSimpleSpi = dubboSimpleSpi;
}
@Override
public void sayHello() {
System.out.println("DubboSimpleSpiWrapper sayHello");
dubboSimpleSpi.sayHello();
}
}
// 演示类
public class DubboSpiWrapperDemo {
public static void main(String[] args) {
// 获取DubboSimpleSpiImpl2.NAME对应的实现类
DubboSimpleSpi dubboSimpleSpi = ApplicationModel.defaultModel().getExtensionLoader(DubboSimpleSpi.class)
.getExtension(DubboSimpleSpiImpl2.NAME);
dubboSimpleSpi.sayHello();
}
}
配置文件META-INF/dubbo/top.xudj.spi.dubbo.DubboSimpleSpi:
dubboSimpleSpiImpl1=top.xudj.spi.dubbo.DubboSimpleSpiImpl1
dubboSimpleSpiImpl2=top.xudj.spi.dubbo.DubboSimpleSpiImpl2
dubboSimpleSpiWrapper=top.xudj.spi.dubbo.wrapper.DubboSimpleSpiWrapper
注意,也需要配置包装类,它是一个扩展点,只不过是特殊的扩展点:dubboSimpleSpiWrapper=top.xudj.spi.dubbo.wrapper.DubboSimpleSpiWrapper
运行演示类,输出包装类的日志,再调用原扩展点对象的日志:
DubboSimpleSpiWrapper sayHello
DubboSimpleSpiImpl2:top.xudj.spi.dubbo.DubboSimpleSpiImpl2@7920ba90 say hello
原理分析
分析原理之前,先看@Wrapper注解
@Retention(RetentionPolicy.RUNTIME)
public @interface Wrapper {
String[] matches() default {};
String[] mismatches() default {};
int order() default 0;
}
该注解用于决定被标记的类是否要作为某个扩展点的包装类。
先说结论,下面再分析原因:默认可不加注解或加注解无配置,matches和mismatches属性为扩展点的名称(配置文件内容的key),决定哪些扩展点名称会被当作包装类。
下面看原理分析,获取扩展点实现的方法:getExtension(String),即上篇文章分析的根据名称获取扩展,默认有个参数wrap为true:
// org.apache.dubbo.common.extension.ExtensionLoader#getExtension(java.lang.String, boolean)
public T getExtension(String name, boolean wrap) {
// ...
instance = createExtension(name, wrap);
// ...
}
继续看创建扩展:
// org.apache.dubbo.common.extension.ExtensionLoader#createExtension
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 {
// 从缓存中获取扩展实例,extensionInstances:ConcurrentMap<Class<?>, Object>
T instance = (T) extensionInstances.get(clazz);
if (instance == null) {
// 核心:调用createExtensionInstance方法创建扩展实例(构造器反射),并放入缓存
extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
instance = (T) extensionInstances.get(clazz);
// ... 对instance进行一系列的处理
}
// 是否需要包装,默认为true
if (wrap) {
List<Class<?>> wrapperClassesList = new ArrayList<>();
// 存在缓存的包装类,在getExtensionClasses()文件加载的时候会存入cachedWrapperClasses
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);
// 排序 并 反转
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
}
if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
// AOP思想的体现
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
// match两种情况为true:1)没有Wrapper注解,2)有注解,matches为空或者matches包含name,且mismatches不包含name
boolean match = (wrapper == null)
|| ((ArrayUtils.isEmpty(wrapper.matches())
|| ArrayUtils.contains(wrapper.matches(), name))
&& !ArrayUtils.contains(wrapper.mismatches(), name));
if (match) {
// 包装扩展实例,实例化包装类,并完成"包装类"的DI,类似Spring的Autowired
instance = injectExtension(
(T) wrapperClass.getConstructor(type).newInstance(instance));
// 包装类的后置处理,类似Spring的BeanPostProcessor#postProcessAfterInitialization
// 默认会注入实现了ScopeModelAware接口的方法
instance = postProcessAfterInitialization(instance, name);
}
}
}
}
// ...
// 返回扩展实例
return instance;
} catch (Throwable t) {
// ... throw
}
}
代码省略了部分内容,核心看 if(wrap){},在进入包装扩展处理前,对应的instance已经创建完成了。
首先获取cachedWrapperClasses,这是一个关键成员变量,类型Set<Class<?>>,缓存wrapper的Class,它从何而来?下面再详细介绍。
对示例来说就是top.xudj.spi.dubbo.wrapper.DubboSimpleSpiWrapper对应的Class,如果存在多个,进行排序(正序),根据顺序进行多重包装。这就体现了AOP的思想。
最终会不会进行包装,还要看包装类上的注解@Wrapper,上面提到过,两种情况会进行包装:
类上没有@Wrapper注解;
类上有@Wrapper注解,matches为空或者matches包含name,且mismatches不包含name。这个name是扩展点名称
一旦进行包装,也会对包装类的Setter方法进行DI注入(Inject spi extension 和 scope bean),体现了IoC的思想;以及后置处理。
划重点,cachedWrapperClasses缓存从何而来?
回到createExtension方法的入口有个getExtensionClasses(),该方法会对配置文件进行解析,跟进getExtensionClasses()方法:
private Map<String, Class<?>> getExtensionClasses() {
// cachedClasses扩展点实现的Class缓存,不包括标记@Adaptive注解和Wrapper扩展类
Map<String, Class<?>> classes = cachedClasses.get();
// ... 此处有cachedClasses双检锁
// 获取当前接口的全部的扩展类Class
classes = loadExtensionClasses();
// ... 加入cachedClasses中
return classes;
}
继续看loadExtensionClasses();
private Map<String, Class<?>> loadExtensionClasses() throws InterruptedException {
checkDestroyed();
// @SPI注解的value值,作为默认的扩展名,存入变量cachedDefaultName:String中
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// 加载扩展类的策略,有三种:META-INF/dubbo/internal、META-INF/dubbo、META-INF/services
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy, type.getName());
// compatible with old ExtensionFactory
if (this.type == ExtensionInjector.class) {
loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());
}
}
return extensionClasses;
}
一步步下去,按ClassLoader加载接口命名的配置文件(可能多个),最后调用到:
// org.apache.dubbo.common.extension.ExtensionLoader#loadClass
private void loadClass(
ClassLoader classLoader,
Map<String, Class<?>> extensionClasses,
java.net.URL resourceURL,
Class<?> clazz,
String name,
boolean overridden) {
// 当前class实现了接口type
if (!type.isAssignableFrom(clazz)) {
// ... throw
}
// clazz是否是激活的,不激活则直接返回,由@Activate的onClass属性决定
boolean isActive = loadClassIfActive(classLoader, clazz);
if (!isActive) {
return;
}
if (clazz.isAnnotationPresent(Adaptive.class)) {
// clazz是标有Adaptive注解,将clazz指向缓存 cachedAdaptiveClass:Class<?>
// 几种策略都是overridden=true,可覆盖
cacheAdaptiveClass(clazz, overridden);
} else if (isWrapperClass(clazz)) {
// isWrapperClass:满足构造器的要求(扩展点作为构造器的入参),将class加入缓存 cachedWrapperClasses:Set<Class<?>>
cacheWrapperClass(clazz);
} else {
// 此时,==》clazz不是Adaptive,也不是Wrapper,是普通的扩展类
if (StringUtils.isEmpty(name)) {
// 使用类名作为扩展名
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName()
+ " in the config " + resourceURL);
}
}
// 逗号分割name,正常就一个
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
// 存在@Activate注解时,缓存第一个name(一般也就一个名字)到cachedActivates:Map<String, Object>
cacheActivateClass(clazz, names[0]);
for (String n : names) {
// 缓存name到cachedNames:ConcurrentMap<Class<?>, String>
cacheName(clazz, n);
// 重要,将class放入extensionClasses
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}
该方法是重点,处理的缓存信息关系着SPI的四大特性。
该方法核心分成2大步骤:
首先判断class是否是激活的,激活表示可以获取到该扩展。当使用@Activate且配置了onClass属性且配置的onClass存在className不能ClassLoader加载,则不是激活的,否则就是激活的。
分三种情况依次处理:
如果类是有@Adaptive注解,将Class存入缓存cachedAdaptiveClass:Class<?>中,多个会进行覆盖。
如果有个仅接口类型入参的构造器,被认定为包装类,将Class缓存到cachedWrapperClasses:Set<Class<?>>中
普通的扩展类处理,将扩展点名称到Class的映射放入缓存cachedClasses:Holder<Map<String, Class<?>>>中,名称相同会覆盖;同时标记@Activate的类会额外进行缓存,将扩展点名称到注解的信息缓存到cachedActivates:Map<String, Object>中,
补充说明下,只有在cachedClasses缓存中的Class,才能通过getExtension(String name) 进行获取到扩展。具体可以看上面createExtension方法。
至此,分析了配置文件的加载与解析,得到了包装类信息,存入缓存cachedWrapperClasses中,在getExtension(String name) 获取扩展时,完成对原扩展的包装。
自动加载
上面的自动包装特性是通过构造函数完成的,还有一种是setter方法进行注入,获取类型是参数类型,名称通过方法名截取,Dubbo SPI会自动注入对应的实例,它是一种IoC思想。接下来先看一个示例。
一个示例
// 接口
@SPI
public interface DubboSpiInject {
void echo(String msg);
void setDubboAdaptiveSpi(DubboAdaptiveSpi dubboAdaptiveSpi);
}
// 实现
public class DubboSpiInjectImpl implements DubboSpiInject {
public static final String NAME = "dubboSpiInject";
@Override
public void echo(String msg) {
System.out.println("DubboSpiInjectImpl echo:" + msg);
}
/**
* 自动注入:先查询ScopeBeanFactory,再查询ExtensionLoader,查询ExtensionLoader时,查找自适应扩展实例
* DubboAdaptiveSpi:可以通过SPI获取到的接口类型
*/
@Override
public void setDubboAdaptiveSpi(DubboAdaptiveSpi dubboAdaptiveSpi) {
System.out.println("DubboSpiInjectImpl setDubboAdaptiveSpi:" + dubboAdaptiveSpi);
}
}
// 演示
public class DubboSpiInjectDemo {
public static void main(String[] args) {
ExtensionLoader<DubboSpiInject> extensionLoader = ApplicationModel.defaultModel().getExtensionLoader(DubboSpiInject.class);
DubboSpiInject dubboSpiInject = extensionLoader.getExtension(DubboSpiInjectImpl.NAME);
dubboSpiInject.echo("hello");
}
}
配置文件,META-INF/dubbo/top.xudj.spi.dubbo.inject.DubboSpiInject,内容:
dubboSpiInject=top.xudj.spi.dubbo.inject.DubboSpiInjectImpl
运行演示类,输出:
DubboSpiInjectImpl setDubboAdaptiveSpi:top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi$Adaptive@293a5bf6
DubboSpiInjectImpl echo:hello
通过输出日志可以看出,setDubboAdaptiveSpi方法成功完成了对象注入。
原理分析
演示类中获取扩展点方法仍是extensionLoader.getExtension(String name);得到,在创建对应的扩展点时:
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 {
// 从缓存中获取扩展实例,extensionInstances:ConcurrentMap<Class<?>, Object>
T instance = (T) extensionInstances.get(clazz);
if (instance == null) {
// 核心:调用createExtensionInstance方法创建扩展实例(构造器反射),并放入缓存
extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
// ...
// DI,注入扩展实例的依赖,类似Spring的Autowired,只不过它是"setter注入"(Inject spi extension 和 scope bean)
injectExtension(instance);
// ...
}
// ...
// 返回扩展实例
return instance;
} catch (Throwable t) {
// ... throw
}
}
重点在injectExtension(instance);方法:
private T injectExtension(T instance) {
if (injector == null) {
return instance;
}
try {
for (Method method : instance.getClass().getMethods()) {
// 1、查找方法
if (!isSetter(method)) {
continue;
}
// 2、过滤不需要注入的情况
if (method.isAnnotationPresent(DisableInject.class)) {
continue;
}
// When spiXXX implements ScopeModelAware, ExtensionAccessorAware,
// the setXXX of ScopeModelAware and ExtensionAccessorAware does not need to be injected
if (method.getDeclaringClass() == ScopeModelAware.class) {
continue;
}
if (instance instanceof ScopeModelAware || instance instanceof ExtensionAccessorAware) {
if (ignoredInjectMethodsDesc.contains(ReflectUtils.getDesc(method))) {
continue;
}
}
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
String property = getSetterProperty(method);
// 3、进行注入
Object object = injector.getInstance(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
// ... log
}
}
} catch (Exception e) {
logger.error(COMMON_ERROR_LOAD_EXTENSION, "", "", e.getMessage(), e);
}
return instance;
}
方法主要包括3步:
查找所有setter方法;
对于部分情况不会进行注入,包括方法有@DisableInject注解、类实现了ScopeModelAware, ExtensionAccessorAware继承下来的方法、参数类型属于基础类型或String等都会跳过;
进行注入,依赖于injector:ExtensionInjector对象,在构造ExtensionLoader时创建,最终会依次调用如下2个实现:ScopeBeanExtensionInjector、SpiExtensionInjector的实现方法:org.apache.dubbo.common.extension.ExtensionInjector#getInstance
对于ScopeBeanExtensionInjector:
依赖于beanFactory:ScopeBeanFactory变量,维护着当前作用域下的bean,通过ScopeModel作用域模型获取所得。在ScopeModel的initialize方法中初始化 beanFactory = new ScopeBeanFactory(parent != null ? parent.getBeanFactory() : null, extensionDirector);。类似于Spring的BeanFactory,也存在parent,有registerBean及getBean等api。
对于SpiExtensionInjector
依赖于ExtensionLoader,如下,通过type得到ExtensionLoader,调用getAdaptiveExtension()获取到自适应扩展。
@Override
public <T> T getInstance(final Class<T> type, final String name) {
if (!type.isInterface() || !type.isAnnotationPresent(SPI.class)) {
return null;
}
ExtensionLoader<T> loader = extensionAccessor.getExtensionLoader(type);
if (loader == null) {
return null;
}
// 这里注意,即使getSupportedExtensions有,但可能自适应类扩展没有(一是没有@Adaptive注解的类,二是接口没有@Adaptive注解的方法)
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension();
}
return null;
}
至此,完成了扩展实例的setter方法依赖注入DI。最后提到的自适应扩展是什么呢?往下看自适应特性。
自适应
在SPI扩展中,有个注解@Adaptive,用来标记扩展点类或者接口方法,来实现动态的通过URL确定使用哪个具体的实现类。仍然先通过一个示例看看这个注解怎么用。
一个示例
// 接口
@SPI(DubboAdaptiveSpiImpl1.NAME)
public interface DubboAdaptiveSpi {
@Adaptive(value = {"adaptive", "adaptiveBack"})
String echo(URL url, String s);
default void defaultPlay(String s) {
System.out.println("DubboAdaptiveSpi play");
}
void play(String s);
}
// 实现1
public class DubboAdaptiveSpiImpl1 implements DubboAdaptiveSpi {
public static final String NAME = "impl1";
@Override
public String echo(URL url, String s) {
System.out.println("DubboAdaptiveSpiImpl1 echo url:" + url.toFullString() + ", s:" + s);
return this.getClass().getSimpleName();
}
@Override
public void play(String s) {
System.out.println("DubboAdaptiveSpiImpl1 play s:" + s);
}
}
// 实现2
public class DubboAdaptiveSpiImpl2 implements DubboAdaptiveSpi {
public static final String NAME = "impl2";
@Override
public String echo(URL url, String s) {
System.out.println("DubboAdaptiveSpiImpl2 echo url:" + url.toFullString() + ", s:" + s);
return this.getClass().getSimpleName();
}
@Override
public void play(String s) {
System.out.println("DubboAdaptiveSpiImpl2 play s:" + s);
}
}
// 演示类
public class DubboAdaptiveSpiDemo {
public static void main(String[] args) {
ExtensionLoader<DubboAdaptiveSpi> extensionLoader = ApplicationModel.defaultModel()
.getExtensionLoader(DubboAdaptiveSpi.class);
DubboAdaptiveSpi adaptiveExtension = extensionLoader.getAdaptiveExtension();
// 构造参数
Map<String, String> map = new HashMap<>();
map.put("adaptive", "impl2"); // 使用DubboAdaptiveSpiImpl2,url:dubbo://1.2.3.4:1010/path1?adaptive=impl2
URL url = new ServiceConfigURL("dubbo", "1.2.3.4", 1010, "path1", map);
adaptiveExtension.echo(url, "hello");
}
}
配置文件:META-INF/dubbo/top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi,内容:
impl1=top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpiImpl1
impl2=top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpiImpl2
运行演示类,输出:DubboAdaptiveSpiImpl2 echo url:dubbo://1.2.3.4:1010/path1?adaptive=impl2, s:hello
先说结论:@Adaptive注解方式:
方法上面,会动态生成并编译一个Class类,内部的逻辑往下看;extensionLoader.getAdaptiveExtension()得到的便是生成的类。
类上面,该类作为自适应类,内部逻辑可以自行定义;extensionLoader.getAdaptiveExtension()得到的便是该类。
对于注解在方法上的情况,生成的类的方法内部逻辑如下:通过url获取参数得到扩展的名称,通过扩展名称来决定使用哪个扩展实现类,所以叫自适应。
内部方法实现的逻辑分为2步:
1、获取扩展点name = url.getParameter(${key}, ${default})
${key}:首先是@Adaptive的value值作为key(多个依次为key),如果没配置则使用接口名转化(DubboAdaptiveSpi -> dubbo.adaptive.spi作为key),url通过key得到得value值作为对应的扩展点名称。如果key没有对应的value,则使用${default},${default}为@SPI注解的value值
2、根据扩展点name,调用getExtension(String name)得到扩展点实现,并调用对应的实现方法。
生成的自适应类是什么样子呢?示例生成的类如下:
package top.xudj.spi.dubbo.adaptive;
import org.apache.dubbo.rpc.model.ScopeModel;
import org.apache.dubbo.rpc.model.ScopeModelUtil;
public class DubboAdaptiveSpi$Adaptive implements top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi {
public void defaultPlay(java.lang.String arg0) {
throw new UnsupportedOperationException("The method public default void top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi.defaultPlay(java.lang.String) of interface top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi is not adaptive method!");
}
public void play(java.lang.String arg0) {
throw new UnsupportedOperationException("The method public abstract void top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi.play(java.lang.String) of interface top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi is not adaptive method!");
}
public java.lang.String echo(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
if (arg0 == null)
throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("adaptive", url.getParameter("adaptiveBack", "impl1"));
if(extName == null)
throw new IllegalStateException("Failed to get extension (top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi) name from url (" + url.toString() + ") use keys([adaptive, adaptiveBack])");
ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi.class);
top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi extension = (top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi)scopeModel.getExtensionLoade(top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi.class)
.getExtension(extName);
return extension.echo(arg0, arg1);
}
可以看出:
该类实现了对应的接口,类名为:接口名 + $Adaptive
@Adaptive注解在echo方法,只会处理echo方法,其它方法均会抛出异常
原理分析
先看下@Adaptive注解,比较简单就一个value数组,存放url用于匹配的参数key。
public @interface Adaptive {
String[] value() default {};
}
生成的类的情况上面分析差不多了,这里看下,extensionLoader.getAdaptiveExtension();具体做了什么?
public T getAdaptiveExtension() {
checkDestroyed();
// 先从缓存中取
Object instance = cachedAdaptiveInstance.get();
// ... 双重检查锁
// 重要方法创建自适应扩展点实现
instance = createAdaptiveExtension();
// ... 设置到缓存中
cachedAdaptiveInstance.set(instance);
return (T) instance;
}
这里可以看出,该方法无参,所以每个扩展点接口只有一个对应的自适应实现类。
继续看createAdaptiveExtension()创建:
private T createAdaptiveExtension() {
try {
// 创建自适应扩展类
T instance = (T) getAdaptiveExtensionClass().newInstance();
// 前置处理
instance = postProcessBeforeInitialization(instance, null);
// DI,setter方法注入
injectExtension(instance);
// 后置处理,默认处理ScopeModelAware接口的set方法
instance = postProcessAfterInitialization(instance, null);
// 如果是Lifecycle接口的实现类,调用初始化方法
initExtension(instance);
return instance;
} catch (Exception e) {
throw new IllegalStateException(
"Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
主要看T instance = (T) getAdaptiveExtensionClass().newInstance();,其它都见过。
private Class<?> getAdaptiveExtensionClass() {
// 加载配置文件,几种情况都一样
getExtensionClasses();
// 如果cachedAdaptiveClass不为空,直接返回,cachedAdaptiveClass是@Adaptive注解在了实现类上
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 创建自适应类
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
调用getExtensionClasses加载配置文件,在自动包装特性原理分析时已经讲述过。SPI都离不开这个操作。
这里有个判断cachedAdaptiveClass也很重要,cachedAdaptiveClass(解析文件时处理的)如果不为null,说明存在类上有@Adaptive注解(多个的话默认进行覆盖),直接使用该Class即可。没有的话最后调用createAdaptiveExtensionClass()创建自适应Class:
private Class<?> createAdaptiveExtensionClass() {
// ...
// 生成自适应类的代码,然后通过Compiler编译成Class
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
org.apache.dubbo.common.compiler.Compiler compiler = extensionDirector
.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class)
.getAdaptiveExtension();
return compiler.compile(type, code, classLoader);
}
通过AdaptiveClassCodeGenerator完成代码生成,再通过Compiler的扩展点实现完成compile编译成Class。
这里注意下:AdaptiveClassCodeGenerator的generate()里有个判断,需要当前的扩展点类type中有方法标记了@Adaptive注解,否则抛出异常。
至此,自适应的扩展实现完成。
自动激活
自适应特性是动态寻找实现类的方式,比较灵活,但只能激活一个具体的实现,上面三大特性都是激活一个具体的实现。如果像Filter一样,需要多个类同时激活呢,就需要用到SPI的最后一个特性:自动激活。
对于自动激活的注解@Activate,因为这个注解相对复杂,先看注解:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
String[] group() default {};
// URL parameter keys
String[] value() default {};
@Deprecated
String[] before() default {};
@Deprecated
String[] after() default {};
// 正序排序,值越小越靠前
int order() default 0;
String[] onClass() default {};
}
group:可以通过group进行分组获取,具体看ExtensionLoader的入参
value:配置url参数,支持多个key或配置”key:value“形式
before和after在dubbo2.7.0版本之后废弃,用于排序
order:排序,默认0,值越小越靠前
onClass:决定Class是否是激活的,默认是,具体看上文有提到。
一个示例
// 接口
@SPI
public interface DubboActivateSpi {
void echo(URL url, String msg);
}
// 实现1
@Activate(group = "group1", value = {"key1:impl1", "key"})
public class DubboActivateSpiImpl1 implements DubboActivateSpi {
public static final String NAME = "impl1";
@Override
public void echo(URL url, String msg) {
System.out.println("DubboActivateSpiImpl1 echo url: " + url + ", msg: " + msg);
}
}
// 实现2
@Activate(group = "group1", value = {"key2:impl2", "key"})
public class DubboActivateSpiImpl2 implements DubboActivateSpi {
public static final String NAME = "impl2";
@Override
public void echo(URL url, String msg) {
System.out.println("DubboActivateSpiImpl2 echo url: " + url + ", msg: " + msg);
}
}
// 演示类
public class DubboActivateSpiDemo {
public static void main(String[] args) {
ExtensionLoader<DubboActivateSpi> extensionLoader = ApplicationModel.defaultModel().getExtensionLoader(DubboActivateSpi.class);
// 激活扩展实例
List<DubboActivateSpi> activateExtensions = new ArrayList<>();
// activateExtensions = extensionLoader.getActivateExtensions(); // 获取全部
// 获取特定的激活扩展,根据group和key1
Map<String, String> map = new HashMap<>();
// map.put("key1", "impl1"); // DubboActivateSpiImpl1作为非默认的激活扩展
// map.put("key1", "impl2"); // DubboActivateSpiImpl2作为非默认的激活扩展
// map.put("key1", "impl1,impl2"); // DubboActivateSpiImpl1和DubboActivateSpiImpl2作为非默认的激活扩展
// map.put("key1", "xxx"); // 空,无非默认,无法匹配默认
URL url = new ServiceConfigURL("dubbo", "1.2.3.4", 1010, "path1", map);
activateExtensions = extensionLoader.getActivateExtension(url, "key1", "group1");
// map.put("key", "xxx"); // DubboActivateSpiImpl1和DubboActivateSpiImpl2作为默认的激活扩展
// map.put("key", "xxx,-default"); // 空,无非默认
// map.put("key", ""); // 空,没有激活扩展,无法匹配默认,也无法匹配非默认
// activateExtensions = extensionLoader.getActivateExtension(url, "key", "group1");
activateExtensions.forEach(activateExtension -> activateExtension.echo(url, "hello"));
}
}
配置文件:META-INF/dubbo/top.xudj.spi.dubbo.activate.DubboActivateSpi,内容
impl1=top.xudj.spi.dubbo.activate.DubboActivateSpiImpl1
impl2=top.xudj.spi.dubbo.activate.DubboActivateSpiImpl2
运行演示类,输出:DubboActivateSpiImpl1 echo url: dubbo://1.2.3.4:1010/path1?key1=impl1, msg: hello
原理分析
通过extensionLoader.getActivateExtension(url, "key1", "group1");看:
public List<T> getActivateExtension(URL url, String key, String group) {
String value = url.getParameter(key);
return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
}
调用重载方法,将key转成了url对应的value值,多个用逗号分隔:
public List<T> getActivateExtension(URL url, String[] values, String group) {
checkDestroyed();
// solve the bug of using @SPI's wrapper method to report a null pointer exception.
// 用于排序的map,临时存储
Map<Class<?>, T> activateExtensionsMap = new TreeMap<>(activateComparator);
List<String> names = values == null
? new ArrayList<>(0)
: Arrays.stream(values).map(StringUtils::trim).collect(Collectors.toList());
// value值集合,为啥要name命名,应该是因为这个值是扩展点的名字
Set<String> namesSet = new HashSet<>(names);
// 1、value中不包括 -default 的值情况,这一步拿到所有default的扩展点(不在namesSet中的其它的),后面用
if (!namesSet.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
if (cachedActivateGroups.size() == 0) {
synchronized (cachedActivateGroups) {
// cache all extensions
if (cachedActivateGroups.size() == 0) {
// 加载配置文件
getExtensionClasses();
// 遍历所有的标有@Activate注解的信息(每个类的@Activate注解)
for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
// name为扩展点的名字,activate为扩展点的注解信息
String name = entry.getKey();
Object activate = entry.getValue();
// 注解配置的分组和值
String[] activateGroup, activateValue;
if (activate instanceof Activate) {
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else if (Dubbo2CompactUtils.isEnabled()
&& Dubbo2ActivateUtils.isActivateLoaded()
&& Dubbo2ActivateUtils.getActivateClass().isAssignableFrom(activate.getClass())) {
activateGroup = Dubbo2ActivateUtils.getGroup((Annotation) activate);
activateValue = Dubbo2ActivateUtils.getValue((Annotation) activate);
} else {
continue;
}
// 扩展点的名字对应的group配置
cachedActivateGroups.put(name, new HashSet<>(Arrays.asList(activateGroup)));
// 根据注解配置的value内容,创建不规则的(或称为“锯齿形”)二维数组
String[][] keyPairs = new String[activateValue.length][];
for (int i = 0; i < activateValue.length; i++) {
if (activateValue[i].contains(":")) {
keyPairs[i] = new String[2];
String[] arr = activateValue[i].split(":");
keyPairs[i][0] = arr[0];
keyPairs[i][1] = arr[1];
} else {
keyPairs[i] = new String[1];
keyPairs[i][0] = activateValue[i];
}
}
// 扩展点的名字对应的value配置
cachedActivateValues.put(name, keyPairs);
}
}
}
}
// traverse all cached extensions
cachedActivateGroups.forEach((name, activateGroup) -> {
// 如果入参group为空,或者注解group不为空且包含入参group 且 name 和 -name 不在入参value中 且 注解配置的value是否和url匹配(存在)
// so,作为默认的扩展点的重要条件:1)、group匹配上、2)、扩展点的名称不在入参values中;3)、注解的value值(key或key-value)和url能匹配上
if (isMatchGroup(group, activateGroup)
&& !namesSet.contains(name)
&& !namesSet.contains(REMOVE_VALUE_PREFIX + name)
&& isActive(cachedActivateValues.get(name), url)) {
// 获取对应扩展点,和普通的获取方式一样。
activateExtensionsMap.put(getExtensionClass(name), getExtension(name));
}
});
}
// 2、包括 default 的值,注意顺序
if (namesSet.contains(DEFAULT_KEY)) {
// default的位置影响排序
// ArrayList有序集合
ArrayList<T> extensionsResult = new ArrayList<>(activateExtensionsMap.size() + names.size());
for (String name : names) {
// 扩展name 以-开头。这里的namesSet判断有点冗余了。。。
if (name.startsWith(REMOVE_VALUE_PREFIX) || namesSet.contains(REMOVE_VALUE_PREFIX + name)) {
continue;
}
// name为default,直接添加所有的扩展点
if (DEFAULT_KEY.equals(name)) {
extensionsResult.addAll(activateExtensionsMap.values());
continue;
}
// 存在扩展点,添加
if (containsExtension(name)) {
extensionsResult.add(getExtension(name));
}
}
return extensionsResult;
} else {
// 3、不包括 default 的值,使用activateExtensionsMap对应的顺序
// add extensions, will be sorted by its order
for (String name : names) {
// 扩展name 以-开头,跳过
if (name.startsWith(REMOVE_VALUE_PREFIX) || namesSet.contains(REMOVE_VALUE_PREFIX + name)) {
continue;
}
// 进不来
if (DEFAULT_KEY.equals(name)) {
continue;
}
// 存在扩展点,添加
if (containsExtension(name)) {
activateExtensionsMap.put(getExtensionClass(name), getExtension(name));
}
}
return new ArrayList<>(activateExtensionsMap.values());
}
}
该方法为获取激活扩展实现的核心且复杂方法,可以分成3大步骤看:
1、vlaues中没有“-default”,那么处理defalut的激活类,所谓default是排除values之外的扩展点名称;
2、values中有“default”,根据default的位置来排序所有激活点实例,例如ext1,default,ext2。而default的激活类来自步骤1;
3、values中没有“default”,则在default的基础上,添加values中所有的扩展点,通过getExtension(String name)获取。
关于第1步稍微复杂,哪些会作为defalut的激活扩展类呢?对应逻辑:
// ...
cachedActivateGroups.forEach((name, activateGroup) -> {
if (isMatchGroup(group, activateGroup)
&& !namesSet.contains(name)
&& !namesSet.contains(REMOVE_VALUE_PREFIX + name)
&& isActive(cachedActivateValues.get(name), url)) {
// 获取对应扩展点,和普通的获取方式一样。
activateExtensionsMap.put(getExtensionClass(name), getExtension(name));
}
});
这个if判断,决定作为默认的扩展点的重要逻辑包括:1)、group匹配上、2)、扩展点的名称不在入参values中;3)、注解的value值(key或key-value)和url能匹配上。
所以,@Activate注解的group和value决定着是否作为default自动激活扩展类的关键点。
关于上述代码中keyPairs变量,作为一个二维数组,它可能是一个不规则的(或称为“锯齿形”)二维数组,如对于注解@Activate(group = "group1", value = {"key1:impl1", "key"});keyPairs=[[key1][value2]],[[key]]
至此,获取自动激活的扩展类分析完,它允许通过getActivateExtension方法获取多个满足条件的扩展类。
四大特性小结
四大特性对比:
四大特性 | 对应获取api | 注解 | 是否得到多个 | 是否需要定义额外的扩展类 |
---|---|---|---|---|
自动包装 | getExtension | @SPI @Wrapper | 1 | 需要定义包装类,并加入配置文件中 |
自动加载 | getExtension getAdaptiveExtension getActivateExtension | @SPI | - | - |
自适应 | getAdaptiveExtension | @SPI @Adaptive | 1 | 如果注解类上,这个类就是定义的自适应类,并加入配置文件中 |
自动激活 | getActivateExtension | @SPI @Activate | 多 | 不需要 |
SPI四大特性底层都依赖于getExtension(String name)获取对应的扩展点实例,因为其实都是在获取扩展点实现。
ExtensionLoader的10大缓存:
变量名 | 类型 | 赋值时机 | 使用时机 | 说明 |
---|---|---|---|---|
cachedNames | ConcurrentMap <Class<?>, String> | 解析配置文件时 | getExtensionName(Class) | 可以通过Class得到扩展点名称 |
cachedClasses | Holder<Map<String, Class<?>>> | 解析配置文件时 | getExtensionClasses(),加载接口的扩展类时,所有获取扩展点的方法都用得到 | 扩展点名称到Class的映射,注意:不包括标记@Adaptive注解和被判断为Wrapper扩展类 |
cachedActivates | Map<String, Object> | 解析配置文件时 | getActivateExtension | 标记@Activate注解的扩展缓存。扩展点名称到Activate注解信息的映射 |
cachedActivateGroups | Map<String, Set<String>> | getActivateExtension | getActivateExtension | 扩展点名称到@Activate注解的group的映射 |
cachedActivateValues | Map<String, String[][]> | getActivateExtension | getActivateExtension | 扩展点名称到@Activate注解的value的映射。二维数组的第一维长度对应value的大小 |
cachedInstances | ConcurrentMap <String, Holder<Object>> | getExtension | getExtension | 扩展点名称到扩展点实例的映射 |
cachedAdaptiveInstance | Holder<Object> | getAdaptiveExtension | getAdaptiveExtension | 接口的自适应扩展点实例的映射 |
cachedAdaptiveClass | Class<?> | 解析配置文件时 | getAdaptiveExtension | 如果实现类上为@Adaptive标记,那么会加入该缓存,该实现类会作为自适应类 |
cachedDefaultName | String | 进入解析配置文件之前,loadExtensionClasses 方法 | getDefaultExtension | 默认扩展点名称,@SPI注解的value值 |
cachedWrapperClasses | Set<Class<?>> | 解析配置文件时 | getExtension | 缓存包装类,就是扩展点实现了接口同时构造器入参也是该接口类型,装饰器模式 |
总结
本文介绍了Dubbo3引入的作用域模型;然后介绍了SPI的四大特性;这两块内容在整个Dubbo框架中发挥着至关重要的作用。Dubbo是微内核+扩展的设计,框架内部的一些实现本身就是基于SPI扩展进行实现的。所以了解Dubbo的扩展对于理解和学习Dubbo都是前提。