【Dubbo源码阅读系列】之 Dubbo SPI 机制

时间:2022-08-08 16:43:38

最近抽空开始了 Dubbo 源码的阅读之旅,希望可以通过写文章的方式记录和分享自己对 Dubbo 的理解。如果在本文出现一些纰漏或者错误之处,也希望大家不吝指出。

Dubbo SPI 介绍

Java SPI

在阅读本文之前可能需要你对 Java SPI(Service Provider Interface) 机制有过简单的了解。这里简单介绍下:在面向对象的设计中,我们提倡模块之间基于接口编程。不同模块可能会有不同的具体实现,但是为了避免模块的之间的耦合过大,我们需要一种有效的服务(服务实现)发现机制来选择具体模块。SPI 就是这样一种基于接口编程+策略模式+配置文件,同时可供使用者根据自己的实际需要启用/替换模块具体实现的方案。

Dubbo SPI 的改进点

以下内容摘录自 https://dubbo.gitbooks.io/dubbo-dev-book/SPI.html

Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。

JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。

如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。

增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点

在 Dubbo 中,如果某个 interface 接口标记了 @SPI 注解,那么我们认为它是 Dubbo 中的一个扩展点。扩展点是 Dubbo SPI 的核心,下面我们就扩展点加载、扩展点自动包装、扩展点自动装配几方面来聊聊具体实现。

Dubbo SPI 机制详解

Dubbo 扩展点的加载

在阅读本文前,如果你阅读过Java SPI 相关内容,大概能回忆起来有 /META-INF/services 这样一个目录。在这个目录下有一个以接口命名的文件,文件的内容为接口具体实现类的全限定名。在 Dubbo 中我们也能找到类似的设计。

  • META-INF/services/(兼容JAVA SPI)
  • META-INF/dubbo/(自定义扩展点实现)
  • META-INF/dubbo/internal/(Dubbo内部扩展点实现)

非常好~我们现在已经知道了从哪里加载扩展点了,再回忆一下,JAVA SPI是如何加载的。

ServiceLoader<DubboService> spiLoader = ServiceLoader.load(XXX.class);

类似的,在 Dubbo 中也有这样一个用于加载扩展点的类 ExtensionLoader。这一章节,我们会着重了解一下这个类到底是如何帮助我们加载扩展点的。我们先来看一段简短的代码。

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

在 Dubbo 的实现里面用到了大量类似的代码片段,我们只需要提供一个 type ,即可获取该 type 的自适应(关于自适应的理解在后文会提到)扩展类。在获取对应自适应扩展类时,我们首先获取该类型的 ExtensionLoader。看到这里我们应该下意识的感觉到对于每个 type 来说,都应该有一个对应的 ExtensionLoader 对象。我们先来看看 ExtensionLoader 是如何获取的。

getExtensionLoader()

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!");
}
// 是否被 SPI 注解标识
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
//EXTENSION_LOADERS 为一个 ConcurrentMap集合,key 为 Class 对象,value 为ExtenLoader 对象
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;
} private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

上面这一段的代码比较简单,根据 type 从 EXTENSION_LOADERS 集合中获取 loader ,如果返回的值为 null 则新建一个 ExtensionLoader 对象。这里的 objectFactory 获取也用到了类似的方法,获取到了 ExtensionFactory 的扩展自适应类。

getAdaptiveExtension()

public T getAdaptiveExtension() {
//cachedAdaptiveInstance用于缓存自适应扩展类实例
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) {
// ...
}
}
}
} return (T) instance;
}

getAdaptiveExtension()方法用于获取当前自适应扩展类实例,首先会从 cachedAdaptiveInstance 对象中获取,如果值为 null 同时 createAdaptiveInstanceError 为空,则调用 createAdaptiveExtension 方法创建扩展类实例。创建完后更新 cachedAdaptiveInstance 。

createAdaptiveExtension()

private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
// 省略异常
}
}

这里有两个方法值得我们关注,injectExtension() 和 getAdaptiveExtensionClass()。injectExtension() 看名字像是一个实现了注入功能的方法,而 getAdaptiveExtensionClass() 则用于获取具体的自适应扩展类。我们依次看下这两个方法。

injectExtension()

private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
//如果存在 DisableInject 注解则跳过
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
//获取 method 第一个参数的类型
Class<?> pt = method.getParameterTypes()[0];
try {
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}

简单的总结下这个方法做了什么:遍历当前实例的 set 方法,以 set 方法第四位开始至末尾的字符串为关键字,尝试通过 objectFactory 来获取对应的 扩展类实现。如果存在对应扩展类,通过反射注入到当前实例中。这个方法相当于完成了一个简单的依赖注入功能,我们常说 Dubbo 中的 IOC 实际上也是在这里体现的。

getAdaptiveExtensionClass()

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

接着看 getAdaptiveExtensionClass() 方法。首先调用 getExtensionClasses() 方法,如果 cachedAdaptiveClass() 不为 null 则返回,如果为 null 则调用 createAdaptiveExtensionClass() 方法。依次看下这两个方法。

getExtensionClasses()

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

内容比较简单,我们直接看 loadExtensionClasses() 方法。

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

绕来绕去这么久,终于要进入主题了。为什么说进入主题了呢?看看这几个变量的值

  • DUBBO_INTERNAL_DIRECTORY:META-INF/dubbo/internal/
  • DUBBO_DIRECTORY:META-INF/dubbo/
  • SERVICES_DIRECTORY:META-INF/services/

熟悉的配方熟悉的料。。没错了,我们马上就要开始读取这三个目录下的文件,然后开始加载我们的扩展点了。

loadDirectory()

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) {
// ...
}
}
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
try {
String line;
while ((line = reader.readLine()) != null) {
//#对应注释位置,剔除存在的注释
final int ci = line.indexOf('#');
if (ci >= 0) {
line = line.substring(0, ci);
}
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
//文件中的内容以 key=value 的形式保存,拆分 key 和 vlaue
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
// ...
}
}
}
} finally {
reader.close();
}
} catch (Throwable t) {
// ...
}
}
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
// 用于判断 class 是不是 type 接口的实现类
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.");
}
// 如果当前 class 被 @Adaptive 注解标记,更新 cachedAdaptiveClass 缓存对象
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if (!cachedAdaptiveClass.equals(clazz)) {
// 省略异常
}
} else if (isWrapperClass(clazz)) {
// 这里涉及到了 Dubbo 扩展点的另一个机制:包装,在后文介绍
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} else {
clazz.getConstructor();
// 如果 name 为空,调用 findAnnotationName() 方法。如果当前类有 @Extension 注解,直接返回 @Extension 注解value;
// 若没有 @Extension 注解,但是类名类似 xxxType(Type 代表 type 的类名),返回值为小写的 xxx
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 注解用于配置扩展被自动激活条件
// 如果当前 class 包含 @Activate ,加入到缓存中
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(names[0], activate);
} else {
// support com.alibaba.dubbo.common.extension.Activate
com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
if (oldActivate != null) {
cachedActivates.put(names[0], oldActivate);
}
}
for (String n : names) {
if (!cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
Class<?> c = extensionClasses.get(n);
if (c == null) {
// 还记得文件内容长啥样吗?(name = calssvalue),我们最后将其保存到了 extensionClasses 集合中
extensionClasses.put(n, clazz);
} else if (c != clazz) {
// ...
}
}
}
}
}

这一段代码真的相当长啊。。梳理下之后发现其实他做的事情也很简单:

  1. 拼接生成文件名:dir + type,读取该文件
  2. 读取文件内容,将文件内容拆分为 name 和 class 字符串
  3. 如果 clazz 类中包含 @Adaptive 注解,将其加入到 cachedAdaptiveClass 缓存中

    如果 clazz 类中为包装类,添加到 wrappers 中

    如果文件不为 key=class 形式,会尝试通过 @Extension 注解获取 name

    如果 clazz 包含 @Activate 注解(兼容 com.alibaba.dubbo.common.extension.Activate 注解),将其添加到 cachedActivates 缓存中
  4. 最后以 name 为 key ,clazz 为 vlaue,将其添加到 extensionClasses 集合中并返回

获取自适应扩展类

getAdaptiveExtensionClass()

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

Ok,我们已经分析了 getExtensionClasses 方法,并且已经将扩展点实现加载到了缓存中。这个方法是由 getAdaptiveExtensionClass() 方法引出来的,它看起来是像是创建自适应扩展类的。这里会先判断缓存对象 cachedAdaptiveClass 是否会空,cachedAdaptiveClass 是什么时候被初始化的呢?回顾一下之前的代码:

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
// 省略...
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if (!cachedAdaptiveClass.equals(clazz)) {
// 省略...
}
}
}

在 loadClass() 方法中如果发现当前 clazz 包含 @Adaptive 注解,则将当前 clazz 作为缓存自适应类保存。例如在 AdaptiveExtensionFactory 类中就有这么用,我们会将 AdaptiveExtensionFactory 类作为 ExtensionFactory 类型的自适应类缓存起来。

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory

我们继续分析该方法的后部分。如果 cachedAdaptiveClass 为 null,则会调用 createAdaptiveExtensionClass() 方法动态生成一个自适应扩展类。

private Class<?> createAdaptiveExtensionClass() {
String code = createAdaptiveExtensionClassCode();
System.out.println(code);
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);
}

这一段代码在本次分享中不打算重点叙述,可以简单的理解为 dubbo 帮我生成了一个自适应类。我摘取了生成的一段代码,如下所示:

package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory {
private static final org.apache.dubbo.common.logger.Logger logger = org.apache.dubbo.common.logger.LoggerFactory.getLogger(ExtensionLoader.class);
private java.util.concurrent.atomic.AtomicInteger count = new java.util.concurrent.atomic.AtomicInteger(0); public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException {
if (arg2 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg2;
String extName = url.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
org.apache.dubbo.rpc.ProxyFactory extension = null;
try {
extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
}catch(Exception e){
if (count.incrementAndGet() == 1) {
logger.warn("Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.", e);
}
extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension("javassist");
}
return extension.getInvoker(arg0, arg1, arg2);
}
public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean arg1) 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();
String extName = url.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
org.apache.dubbo.rpc.ProxyFactory extension = null;
try {
extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
}catch(Exception e){
if (count.incrementAndGet() == 1) {
logger.warn("Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.", e);
}
extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension("javassist");
}
return extension.getProxy(arg0, arg1);
}
public java.lang.Object getProxy(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();
String extName = url.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
org.apache.dubbo.rpc.ProxyFactory extension = null;
try {
extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
}catch(Exception e){
if (count.incrementAndGet() == 1) {
logger.warn("Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.", e);
}
extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension("javassist");
}
return extension.getProxy(arg0);
}
}
extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);

这一段代码实际上才是自适应适配类的精髓,看看 extName 是怎么来的?

String extName = url.getParameter("proxy", "javassist");

extName 又是从 url 中取得的,实际上 url 对于 Dubbo 来说是一种非常重要的上下文传输载体,在后续系列文章中大家会逐步感受到。

public T getExtension(String name) {
if (name == null || name.length() == 0) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
// 从缓存中读取扩展实现类
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}

上面的逻辑比较简单,这里也不赘述了,直接看 createExtension() 方法。

private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}

getExtensionClasses() 方法在前文已经分析过了,但是需要注意的是:getExtensionClasses 返回给我们的不过是使用 Class.forName() 加载过的类而已,充其量执行了里面的静态代码段,而并非得到了真正的实例。真正的实例对象仍需要调用 class.newInstance() 方法才能获取。

了解了这些之后我们继续看,我们通过 getExtensionClasses() 尝试获取系统已经加载的 class 对象,通过 class 对象再去扩展实例缓存中取。如果扩展实例为 null,调用 newInstance() 方法初始化实例,并放到 EXTENSION_INSTANCES 缓存中。之后再调用 injectExtension() 方法进行依赖注入。最后一段涉及到包装类的用法,下一个章节进行介绍。

扩展类的包装

在 createExtension() 方法中有如下一段代码:

private T createExtension(String name) {
// ···省略···
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
// ···省略···
}

还记得 wrapperClasses 在什么地方被初始化的吗?在前文中的 loadClass() 方法中我们已经有介绍过。再回顾一下:

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
// ···省略···
if (isWrapperClass(clazz)) {
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
}
// ···省略···
} private boolean isWrapperClass(Class<?> clazz) {
try {
clazz.getConstructor(type);
return true;
} catch (NoSuchMethodException e) {
return false;
}
}

在看这个方法前我们先了解下 Dubbo 中 wrapper 类的定义。

举个例子:

class A {
private A a;
public A(A a){
this.a = a;
}
}

我们可以看到 A 类有一个以 A 为参数的构造方法,我们称它为复制构造方法。有这样构造方法的类在 Dubbo 中我们称它为 Wrapper 类。

继续看 isWrapperClass() 方法,这个方法比较简单,尝试获取 clazz 中以 type 为参数的构造方法,如果可以获取到,则认为 clazz 则是当前 type 类的包装类。再结合上面的代码,我们会发现在加载扩展点时,我们将对应 type 的包装类缓存起来。

private T createExtension(String name) {
// ···省略···
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
// ···省略···
}

为了更好的理解这段代码,我们假设当前 type 值为 Protocol.class ,我们可以在 org.apache.dubbo.rpc.Protocol 文件中找到 Protocol 接口的包装类 ProtocolFilterWrapper 和 ProtocolListenerWrapper,他们会依次被添加到 cachedWrapperClasses 集合中。依次遍历 cachedWrapperClasses 集合,比如第一次取到的是 ProtocolFilterWrapper 类,则会以调用 ProtocolFilterWrapper 的复制构造方法将 instance 包装起来。创建完 ProtocolFilterWrapper 对象实例后,调用 injectExtension() 进行依赖注入。此时 instance 已经为 ProtocolFilterWrapper 的实例,继续循环,会将 ProtocolFilterWrapper 类包装在 ProtocolListenerWrapper 类中。因此我们最后返回的是一个 ProtocolListenerWrapper 实例。最后调用时,仍会通过一层一层的调用,最后调用原始 instance 的方法。

这里的包装类有点类似 AOP 思想,我们可以通过一层一层的包装,在调用扩展实现之前添加一些日志打印、监控等自定义的操作。

Dubbo 中的 IOC 机制

上文中我们已经讨论过 Dubbo 中利用反射机制实现一个类 IOC 功能。在这一章节中,我们再回顾一下 injectExtension() 方法,仔细的来看看 Dubbo 中 IOC 功能的实现。

createExtension()
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); private T injectExtension(T instance) {
// ···
Class<?> pt = method.getParameterTypes()[0];
try {
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
}
// ···
} public class StubProxyFactoryWrapper implements ProxyFactory {
// ...
private Protocol protocol;
public void setProtocol(Protocol protocol) {
this.protocol = protocol;
}
//...
}

在上一章节中我们已经讲过 wrapper 类,在这里我们举个例子说明一下。比如我们当前的 wrapperClass 类为 StubProxyFactoryWrapper,那么代码执行逻辑大致如下所示:

  1. 创建 StubProxyFactoryWrapper 实例;
  2. 获取流程1创建的实例作为 injectExtension() 的参数,执行;
  3. injectExtension() 方法循环遍历到 StubProxyFactoryWrapper 的 setProtocol()方法(此时 pt=Protocol.class,property=protocol),执行 objectFactory.getExtension(pt,property) 方法。objectFactory 在 ExtensionLoader 的构造方法中被初始化,在这里获取到自适应扩展类为 AdaptiveExtensionFactory。
  4. 执行 AdaptiveExtensionFactory.getExtension()。AdaptiveExtensionFactory 类中有一个集合变量 factories。factories 在 AdaptiveExtensionFactory 的构造方法中被初始化,包含了两个工厂类:SpiExtensionFactory、SpringExtensionFactory。执行 AdaptiveExtensionFactory 类的 getExtension() 方法会依次调用 SpiExtensionFactory 和 SpringExtensionFactory 类的 getExtension() 方法。
  5. 执行 SpiExtensionFactory 的 getExtension() 方法。上面有说到此时的 type=Procotol.class,property=protocol,从下面的代码我们可以发现 Protocol 是一个接口类,同时标注了 @SPI 注解,此时会获取 Protocol 类型的 ExtensionLoader 对象,最后又去调用 loader 的 getAdaptiveExtension() 方法。最终获取到的自适应类为 Protocol$Adaptive 动态类。
  6. objectFactory.getExtension(pt, property); 最后得到的类为 Protocol$Adaptive 类,最后利用反射机制将其注入到 StubProxyFactoryWrapper 实例中。
@SPI("dubbo")
public interface Protocol {
}
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;
}
}

END

在最后,我们再回顾下开头关于 Dubbo SPI 基于 JAVA SPI 改进的那段话:

Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。

JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。

如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点

总结如下:

  1. Dubbo SPI 在加载扩展点,会以 key-value 的形式将扩展类保存在缓存中,但此时的扩展类只是调用 Class.forName() 加载的类,并没有实例化。扩展类会在调用 getExtension() 方法时被实例化。
  2. Dubbo 通过工厂模式和反射机制实现了依赖注入功能。
  3. Dubbo 中通过包装类实现了 AOP 机制,方便我们添加监控和打印日志。