您的位置:首页 > 其它

Dubbo 源码解读——自定义 Classloader 之 ExtensionLoader

2018-07-16 16:01 696 查看
众所周知,Dubbo 是阿里巴巴公司自主研发开源的一个高性能的服务框架(现已捐献给 Apache 基金会组织),应用之间可以通过 RPC 的方式来互相调用并返回结果。主要基于 Java 语言开发,它提供了三大核心能力:

1. 面向接口的远程方法调用;

2. 智能容错和负载均衡;

3. 以及服务自动注册和发现;



(图来自 dubbo 官网)

对于 Dubbo 的特性,我这里不做过多的介绍。接下来,进入正题。

1. 首先,下载源码,源代码地址为:

git clone https://github.com/apache/incubator-dubbo.git
,如果网速不好的话,耗费时间会稍长一些,请耐心等待。( 本人版本为:2.7.0-SNAPSHOT)

2. 下载成功后,用 IDEA 打开,如下图:



3. 下面我们进入正题, 直接看 dubbo-common 模块中的 org.apache.dubbo.common.extension 包,也就是本文重点解读的自定义 ClassLoader 位置。Dubbo 扩展点主要都是从 ExtensionFactory 接口进行的,我们从这个接口类开始进行分析。



4. ExtensionFactory 接口很简单,里面只有一个方法 getExtension, 而这个 接口一共有三个实现类。分别为:SpiExtensionFactory (SPI 的方式类动态加载)、AdaptiveExtensionFactory(Adaptive 的方式类动态加载)、SpringExtensionFactory(Spring 的方式类动态加载), 这里我们重点以 SpiExtensionFactory 的方式进行分析。

/**
* ExtensionFactory
*/
@SPI
public interface ExtensionFactory {

/**
* Get extension.
*
* @param type object type.
* @param name object name.
* @return object instance.
*/
<T> T getExtension(Class<T> type, String name);

}




5. 进行 SpiExtensionFactory 类中,只有一个 ExtensionFactory 的实现方法。首先,判断该 type 是否为接口类型,并且具有 SPI 注解的标识,则调用 自定义扩张类加载器,对 type 类进行动态加载,如果加载成功,则返回该 type 自适应的类的对象实例。

public class SpiExtensionFactory implements ExtensionFactory {

@Override
public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension();
}
}
return null;
}

}

6. 接下来,我们进入 ExtensionLoader.getExtensionLoader(type); 进行分析。对 type 进行类型检查判断,如果不符合要求抛出异常,最后由 EXTENSION_LOADERS.get(type) 获取该类的扩展的 ClassLoader 。

EXTENSION_LOADERS 为 ConcurrentMap, 其中 Key 为 Class<?> 类型,Value 为 ExtensionLoader<?> 类型。也就是说,对应的类有自己的自定义类加载器。

在这里获取的时候为 Null , 接下来 new ExtensionLoader 对象,构造方法中,传入 type ,并回放到 ConcurrentMap 中。

最终,返回 type 对应的自定义 ExtensionLoader 对象。

@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null)
throw new IllegalArgumentException("Extension type == null");
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}

ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}


7. 进入到 new ExtensionLoader<T>(type) 中,继续阅读源码。 这里需要注意,该类的构造方法为私有! type 赋值给了全局变量 type 。

private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}


8. 接下来是 ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()) 方法。

这里是连着两个调用, getExtensionLoader 我们在第 6 步的时候已经阅读过,返回的对象是参数 type 的类加载器。这里返回的是 ExtensionFactory 类加载器,进入到 getAdaptiveExtension() 方法继续阅读。

9. 从 cachedAdaptiveInstance 获取实例, cachedAdaptiveInstance 是一个泛型类,其中只有两个方法 get 、set 两个方法。 还有一个被 volatile 表示的属性 value 。主要是为了将动态加载类对象 instance 临时缓存。

接下来,对 createAdaptiveInstanceError 进行判断。如果不为空,直接抛出 IllegalStateException 信息。

再往下走,对 cachedAdaptiveInstance 进行加锁,再进行一次 非空判断(你应该能够想到了——单例模式),结合上面的 private 构造函数、volatile 和 双重检查判断,可以说种种迹象都是为了避免并发造成多个对象创建出现问题。

下面进入真正的创建实例方法 createAdaptiveExtension();

@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}

return (T) instance;
}

10. 在该方法中,首先是获取 class ,最后利用反射成该对象。

@SuppressWarnings("unchecked")
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}


11. 调用 getExtensionClasses() ,加载可扩展类。接着判断 cachedAdaptiveClass 是否为空,如果为空,说明该 cachedAdaptive 类还么有初始化,则对其进行初始化。之后,返回该 class 对象。

private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}


12. 从缓存 Holder 里面获取该类 class ,第一次初始化时为空,对其加锁,同步的方式进行加载;

并对其进行双重检查;执行 loadExtensionClasses() 加载改 class , 并将该 class 类型存入到 cachedClasses 中。这样做有一个好处就是,只需要初始化一次,后期如果需要加载,直接从内存中获取即可。

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;
}


13. 由于在在上一步的时候,我们有看到 synchronized ,所以,这里的 反射加载为 同步的方式。

获取该扩展类上的 SPI 注解信息(关于 SPI 注解,该类比较简单,只有 value 属性,读者可以自行查看),获取其 value 值,以该值作为 cacheName 的 Key。如果 value 值为多个值,以 “,” 分割,取第一个为 cacheName 。

紧接着,分别加载 classpath 下 【META-INF/services/ 】、【META-INF/services/internal/

】和 【META-INF/dubbo/ 】文件夹中的 SPI 配置。

type.name 将 com.alibaba 替换为 org.apache 是为了确保,需要加载的类包老版本兼容问题。统一成 org.apache 版本。

// synchronized in getExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if (names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
if (names.length == 1) cachedDefaultName = names[0];
}
}

Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}


14. 获取 ExtensionLoader 类的 classloader, 如果获取到,使用该 classloader 加载 type 类;未获取到,则使用 java.lang.ClassLoader 从所有的搜索路径中查找 type 名称的资源。正常获取到了,则进行加载 资源。

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls;
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", description file: " + fileName + ").", t);
}
}

15. 使用流的方式加载资源(也就是上面提到的 META-INF 目录下的文件),按行读取,并进行加载。这里有一个小细节,需要说明一下,按行读取后,使用了 “=” 分隔符,前面为 name 后面为需要加载的class 类。(为了节省篇幅,这里略去部分代码)

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
......
BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
......
while ((line = reader.readLine()) != null) {
......
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();

loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
......
}
}
}
......

}

也就是为什么我们能够看到在类似下图文件中所看到的内容(JDK 自带的 SPI 方式,是不能解析 xx=xxx.xxx ),dubbo 通过扩展的方式,进行了支持,同时这个等号左边的 name, 也就是我们在动态配置时,需要指定的名称。可以说,具有一举两得的功效。



16. 到 loadClass 方式中,开始了真正的类加载工作。

这个方法主要是对 clazz 进行加载,并对该动态类上的不同类型分别做不同的初始化工作,将加载后的 clazz 对象放入到 cachedNames 、cachedActivates、cachedAdaptiveClass 或 cachedWrapperClasses 中,进行了缓存。方便下一次获取时不用再次加载。

源码,读者可自行查看。

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if (!cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getClass().getName()
+ ", " + clazz.getClass().getName());
}
} else if (isWrapperClass(clazz)) {
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} else {
clazz.getConstructor();
if (name == null || name.length() == 0) {
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(names[0], activate);
}
for (String n : names) {
if (!cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
Class<?> c = extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}


以上内容为 Dubbo 动态加载扩展类的机制。

================================================

由于本文作者水平有限,难免有些内容分析错误。

感谢你的理解与反馈!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: