三、ExtensionLoader的工作原理

ExtensionLoader是整个扩展机制的主要逻辑类,在这个类里面实现了配置的加载、扩展类缓存、自适应对象生成等所有工作。

1、工作流程

逻辑入库可以分为getExtension、getAdaptiveExtension、getActivateExtension三个。分别对应获取普通扩展类、获取自适应扩展类。获取自动激动扩展类。总体逻辑都是从调用这三个方法开始的,每个方法可能会有不同的重载的方法。三个入口中,getActivateExtension对getExtension的依赖比较重,getAdaptiveExtension则相对独立。

方法只是根据不同的条件同时激活多个普通扩展类。因此,该方法中只会做一些通用的判断逻辑,如接口是否包含@Activate注解、匹配条件是否符合等。最终还是通过调用getExtension方法获得具体的扩展点实现类。

getExtension是整个扩展加载器中最核心的地方,实现了一个完整的普通扩展类加载过程。

加载过程中的每一步,都会先检查缓存中是否存在,如果存在则从缓存中读取,没有则重新加载。这个方法每次只会根据名称返回一个扩展点实现类。初始化的过程可以分为4步:

  • 1、框架读取SPI对应路径下的配置文件,并根据配置加载所有扩展类并缓存(不初始化)。
  • 2、根据传入的名称初始化对应的扩展类。
  • 3、尝试查找符合条件的包装类:包括扩展点的setter方法,例如setProtocol(Protocol procotol)方法会自动注入protocol扩展点实现;包含与扩展点类型相同的构造函数,为其注入扩展类实例,例如:初始化一个Class A,初始化完成后,会寻找构造参数中需要Class A的包装类(Wrapper),然后注入Calss A实例,并初始化这个包装类。
  • 4、返回对应的扩展类实例。getAdaptiveExtension也相对独立,只有加载配置信息部分与getExtension共同调用一个方法。和获取普通扩展类一样,框架会先检查缓存是否已经初始化好Adaptive实例,没有则调用createAdaptiveExtension重新初始化。初始化过程分为4步:
  • 1、和getExtension一样先加载配置文件。
  • 2、生成自适应类的代码字符串。
  • 3、获取类加载器和编译器,并且编译器变异刚才生成的代码字符串。Dubbo一共有三种类型的编译器实现。
  • 4、返回对应的自适应类实例。

getExtension:获取普通扩展类

三、Apache Dubbo学习整理---扩展点加载机制(2)_加载

  • 1、当调用getExtension(String name),先检查缓存,没有则调用createExtension开始创建。如果传入的是true,则加载并返回默认扩展类。
  • 2、调用createExtension,会先检查缓存,如果不存在。则从META-INF/services、META-INF/dubbo、META-INF/dubbo/internal这几个路径中读取所有的配置文件,通过I/O读取字节流,然后通过解析字符串,得到配置文件中对应的扩展点实现类的全程(如org.apache.dubbo.config.spring.extension.SpringExtensionFactory)
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;
}

private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
//final SPI defaultAnnotation = type.getAnnotation(SPI.class);
//检查SPI注解,有,则获取注解中填写的名称,并缓存为默认实现,

Map<String, Class<?>> extensionClasses = new HashMap<>();

for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
}

return extensionClasses;
}
//加载配置
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
if (urls == null || !urls.hasMoreElements()) {
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
}
}

加载完扩展点配置后,在通过反射获得所有扩展实现类并缓存。此处仅仅是把Class加载到JVM中,但没有做Class初始化。在加载Class文件时,会根据Class上的注解来判断扩展点类型,再根据类型分类做缓存。

if (clazz.isAnnotationPresent(Adaptive.class)) {
//如果是自适应类则缓存,缓存的是自适应类只能有一个
cacheAdaptiveClass(clazz, overridden);
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
如果是包装扩展类(Wrapper),则直接加入包装扩展类set集合
} else {
clazz.getConstructor();
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
//如果有自动激活注解(Activate),则缓存到自动激活的缓存中
cacheActivateClass(clazz, names[0]);
//不是自适应、包装类,剩下的就是普通扩展类,也会缓存起来。
//自动激活类也是普通扩展类的一种,只是会根据不同条件同时激活
for (String n : names) {
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}

最后,根据传入的name找到对应的类并通过Class.forname方法进行初始化,并为其注入一来的其他扩展类(自动加载特性)。当扩展类初始化后,会检查一次包装扩展类Set<Class<?>> wrapperClasses,查找包含与扩展点类型相同的构造函数,为其注入则初始化的扩展类。

//向扩展类注入其依赖的属性,如扩展类A又依赖了扩展类B
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
//遍历扩展点包装类,用于初始化包装类实例
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type)
.newInstance(instance));//找到构造方法参数类型为type的包装类,为其注入扩展类实例
}
}
initExtension(instance);

在injectExtension方法中可以为类注入依赖属性,使用ExtensionFactory# T getExtension(Class type, String name)来获取对应的bean实例。injectExtension方法总体实现了类似Spring的IOC机制,原理比较简单:

  • 1、通过反射获取类的所有方法
  • 2、遍历以set开头的方法
  • 3、得到set方法的参数类型
  • 4、在通过ExtensionFactory寻找参数类型相同的扩展类实例,如果找到,就设值进去。
for (Method method : instance.getClass().getMethods()) {
/**
method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers());
*/
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];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}

try {
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);
}

}

包装类的构造参数注入也是通过injectExtension方法实现的。

如果方法上有@Adaptive 和类上有@SPI,优先通过@Adaptive注解的key去查找扩展实现类。

如果没找到,在通过SPI注解的key去查找,如果SPI注解中没有默认值,则把类转化为key再去查找。

getActivateExtension:在getAdaptiveExtension方法中,会为扩展点接口自动生成实现类字符串,实现类主要包含以下逻辑:为接口中每个有@Adaptive注解的方法生成默认实现(没有注解的方法则生成空实现),每个默认实现都会从URL中提取Adaptive参数值,并以此为依据动态加载扩展点。然后框架会使用不同的编译器,把实现类字符串编译为自适应类并返回。

  • 1、生成package、import、类名称等头部信息。此处只会引入一个类ExtensionLoader。为了不写其他类的import方法,其他方法调用时会使用全路径。类名称会变为“接口名称+$Adaptive”的格式。
  • 2、遍历接口所有方法,获取方法的返回类型、参数类型、异常类型等。
  • 3、生成参数为空校验代码,如参数是否为空的校验。如果有远程调用,还会添加Invocation参数为空的校验。
  • 4、生成默认实现类名称。如果@Adaptive注解中没有设定默认值,则根据类名称生成,如XyzInvokerWrapper会被转移为xyz.invoker.wrapper。规则是不同找到大写字母,并把它用“.”链接起来。得到默认实现类名称后,还需要知道这个实现是哪个扩展点的。
  • 5、生成获取扩展点名称的代码。根据@Adaptive注解中配置的key值生成不同的获取代码。如果是@Adaptive("protocol"),则会生成url.getProtocol()。
  • 6、生成获取具体扩展实现类代码。最终还是通过getExtension(extName)方法获取自适应扩展类的真正实现。如果根据URL中配置的key没有找到对应的实现类,则使用4中生成的默认实现类名称去找。
  • 7、生成调用结果代码

三、Apache Dubbo学习整理---扩展点加载机制(2)_Dubbo_02

getActivateExtension(URL url,,String key,String group)方法可以获取所有自动激活扩展点。参数分别是URL,URL中指定的key(多个用逗号隔开)和URL中指定的组信息(Group).

当调用方法时,主线流程分4步:

  • 1、检查缓存,如果没有,则初始化所有扩展类实现的集合。
  • 2、遍历整个@Activate注解集合,根据传入URL匹配条件(匹配group、name等),得到所有复合激活条件的扩展类实现。然后根据@Activate中匹配到的before、after、order等参数进行排序。
  • 3、遍历所有用户自定义扩展类名称,根据用户URL配置的顺序,调整扩展点激活顺序。
  • 4、返回所有自动激活类集合。获取Activate扩展类实现,也是通过getExtension得到的。因此,可以认为getExtension是其他两种Extenson的底层支撑。如果传入-default,则所有的默认@Activate都不会激活。

三、Apache Dubbo学习整理---扩展点加载机制(2)_加载_03