接上文dubbo SPI @Adaptive注解使用方法与原理解析
目录
- 概览
-
——入口getAdaptiveExtension
-
-
createAdaptiveExtension
-
- 1
getAdaptiveExtensionClass
-
- 1.1
getExtensionClasses
-
- 1.1.1
loadExtensionClasses
- 1.1.1
- 1.2
createAdaptiveExtensionClass
- 1.1
- 2
injectExtension
- 1
-
概览
自适应类的用法示例
接口代码示例
@SPI("impl1")
public interface SimpleExt {
String echo(URL url,String s);
@Adaptive({"key4"})
void printA(URL url);
@Adaptive
void printB(URL url);
@Adaptive({"key3","key2","key1"})
void printC(URL url);
}
getAdaptiveExtension
方法的大致流程如下图(从左到右)
接下来对每个方法进行解读。
getAdaptiveExtension
——入口
getAdaptiveExtension
此方法是获取自适应类的入口
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) {
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
createAdaptiveExtension
当前面的方法没有获取带自适应类实例的缓存时,会进入此方法。这个方法里一共做了三件事。
-
先获取自适应类getAdaptiveExtensionClass
-
再生成对应的实例newInstance
-
然后为实例注入扩展类属性injectExtension
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
1 getAdaptiveExtensionClass
getAdaptiveExtensionClass
这里将分为两步骤
-
加载扩展点文件。若实现类上有getExtensionClasses
注解,会被缓存起来,只能存在一个自适应类的缓存,默认是不可覆盖的,并且后续的执行方法都是用的此实现类。@Adaptive
-
创建自适应类(仅在自适应类缓存为空时进入此方法)createAdaptiveExtensionClass
private Class<?> getAdaptiveExtensionClass() {
//加载配置文件
getExtensionClasses();
//若自适应类有缓存则返回
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
//自适应类的缓存为空则创建
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
1.1 getExtensionClasses
getExtensionClasses
此方法是dubbo SPI的核心方法,获取扩展类都需要经过这步(包括
getDefalultExtension
,
getAdaptiveExtension
,
getActivateExtension
)。同样还是先希望从缓存取,不存在就去加载文件填充缓存。
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;
}
1.1.1 loadExtensionClasses
loadExtensionClasses
这里也分为两步骤
-
获取扩展点名称并缓存。例如cacheDefaultExtensionName
则是将"impl1"缓存下来@SPI("impl1")
-
解析扩展点文件并缓存实现类。例如配置文件内容为loadDirectory
,则会以key=“impl1” value= SimpleExtImpl1类的键值对 存在impl1=com.example.demo.impl.SimpleExtImpl1
中。除了进行普通扩展类的缓存,此方法还会额外做三种缓存(自适应类,包装扩展类,自动激活类)以加快后续方法的执行效率。Map<String, Class<?>>
private Map<String, Class<?>> loadExtensionClasses() {
//获取扩展点名称并缓存
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
for (LoadingStrategy strategy : strategies) {
//加载指定文件夹下的配置文件
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
//同时加载alibaba和apache的类
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
}
return extensionClasses;
}
1.2 createAdaptiveExtensionClass
createAdaptiveExtensionClass
此方法将生成一个动态的自适应类,它能根据参数决定执行方法的实现类,主要做了三件事。
-
生成类代码字符串。生成代码的逻辑主要分为7步。
1.1 生成与扩展点相同位置的package。import
这个类。为了不写其他的import方法,其他调用都使用全路径名。同时设置类名为“接口名称+$Adaptive”的格式。例如ExtensionLoader
接口会生成SimpleExt
SimpleExt&Adaptive
。
1.2 遍历接口的所有方法,获取方法的返回类型,参数类型,异常类型等。
1.3 生成校验参数是否为空的代码。
1.4 生成默认实现类名称。如果
注解没有默认值,则会先根据此名称去寻找它的扩展实现类。如@Adaptive
SimpleExt
的默认类名称为simple.ext。
1.5 生成获取扩展点名称的代码。例如对于前面的示例接口SimpleExt的printC方法,会生成
url.getParameter("key3", url.getParameter("key2",url.getParameter("key1","impl1")));
1.6 生成获取具体实现类的代码。通过上一步获得的extName执行getExtension(extName) 方法获取实现类。
1.7 生成用此实现类调用方法的代码。
- 获取编译器的自适应类。实现
接口的Compile
类上有AdaptiveCompiler
注解,因此此类会作为编译器的默认实现。@Adaptive
- 使用编译器编译类
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);
}
2 injectExtension
injectExtension
这一部分是为了给新创建的扩展点实现类注入属性。只有当此属性也为扩展点(且该扩展点已被加载并生成实例)时才能注入。
主要逻辑是遍历该类的所有方法,查看setter方法的返回类型对应的类是否为扩展点,是的话将其通过反射调用此setter方法注入。
private T injectExtension(T instance) {
//此工厂中存储了已经实例化的扩展点,若工厂不为空,则查找工厂中是否拥有可注入的类
if (objectFactory == null) {
return instance;
}
try {
//通过反射获取并遍历实现类的所有方法
for (Method method : instance.getClass().getMethods()) {
//只遍历以"set"命名开头的方法
if (!isSetter(method)) {
continue;
}
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
//获得该构造方法第一个参数类型
Class<?> pt = method.getParameterTypes()[0];
//若是基础类型则跳过,如 int,double,String等
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
//获取该属性名,如"setVersion" => "version"
String property = getSetterProperty(method);
//从扩展类工厂中获取该属性的实例
Object object = objectFactory.getExtension(pt, property);
//如果成功获取,则将该扩展类实例注入进去
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
整体的流程就到此结束了,代码多处是反射和缓存的应用,如有错误欢迎指出~
如果有疑问,欢迎评论~
如果成功解决了你的问题,点个赞再走吖~