最近抽空开始了 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 机制,方便我们添加监控和打印日志。

【Dubbo源码阅读系列】之 Dubbo SPI 机制的更多相关文章

  1. 【Dubbo源码阅读系列】之远程服务调用(上)

    今天打算来讲一讲 Dubbo 服务远程调用.笔者在开始看 Dubbo 远程服务相关源码的时候,看的有点迷糊.后来慢慢明白 Dubbo 远程服务的调用的本质就是动态代理模式的一种实现.本地消费者无须知道 ...

  2. 【Dubbo源码阅读系列】服务暴露之远程暴露

    引言 什么叫 远程暴露 ?试着想象着这么一种场景:假设我们新增了一台服务器 A,专门用于发送短信提示给指定用户.那么问题来了,我们的 Message 服务上线之后,应该如何告知调用方服务器,服务器 A ...

  3. 【Dubbo源码阅读系列】服务暴露之本地暴露

    在上一篇文章中我们介绍 Dubbo 自定义标签解析相关内容,其中我们自定义的 XML 标签 <dubbo:service /> 会被解析为 ServiceBean 对象(传送门:Dubbo ...

  4. 【Dubbo源码阅读系列】之 Dubbo XML 配置加载

    今天我们来谈谈 Dubbo XML 配置相关内容.关于这部分内容我打算分为以下几个部分进行介绍: Dubbo XML Spring 自定义 XML 标签解析 Dubbo 自定义 XML 标签解析 Du ...

  5. 源码阅读系列:EventBus

    title: 源码阅读系列:EventBus date: 2016-12-22 16:16:47 tags: 源码阅读 --- EventBus 是人们在日常开发中经常会用到的开源库,即使是不直接用的 ...

  6. Spring源码阅读系列总结

    最近一段时间,粗略的查看了一下Spring源码,对Spring的两大核心和Spring的组件有了更深入的了解.同时在学习Spring源码时,得了解一些设计模式,不然阅读源码还是有一定难度的,所以一些重 ...

  7. dubbo源码分析5-dubbo的扩展点机制

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  8. JDK1.8源码阅读系列之三:Vector

    本篇随笔主要描述的是我阅读 Vector 源码期间的对于 Vector 的一些实现上的个人理解,用于个人备忘,有不对的地方,请指出- 先来看一下 Vector 的继承图: 可以看出,Vector 的直 ...

  9. SpringMVC源码阅读系列汇总

    1.前言 1.1 导入 SpringMVC是基于Servlet和Spring框架设计的Web框架,做JavaWeb的同学应该都知道 本文基于Spring4.3.7源码分析,(不要被图片欺骗了,手动滑稽 ...

随机推荐

  1. BZOJ5372: PKUSC2018神仙的游戏

    传送门 Sol 自己还是太 \(naive\) 了,上来就构造多项式和通配符直接匹配,然后遇到 \(border\) 相交的时候就 \(gg\) 了 神仙的游戏蒟蒻还是玩不来 一个小小的性质: 存在长 ...

  2. Cloud Computing Causing Digital Business Transformation

    2015-04-13 Cloud Computing Causing Digital Business Transformation We hear all about the cloud, and  ...

  3. Node服务端极速搭建 -- nvmhome

    > 本文意在让你掌握极速搭建Node服务端(任何Project) ```$ whoaminame: kelvinemail: kelvv@outlook.comhomepage: www.kel ...

  4. [算法练习]Add Two Numbers

    题目说明: You are given two linked lists representing two non-negative numbers. The digits are stored in ...

  5. MUI框架-12-使用原生底部选项卡(凸出图标案例)

    MUI框架-12-使用原生底部选项卡(凸出图标案例) 今天,用 mui 做 app 时,遇到了可能各位都遇到过的头疼问题:底部中间图标凸起,如下图: 最后有源代码 [提示]:有人问我在 HBuilde ...

  6. Mybatis学习第二天——mapper的动态代理

    传统的Dao层开发通过接口与实现类的方式,Mybatis中通过mapper动态代理是需要定义接口. 1.传统Dao层封装 那么可以将公共资源提取出来,剩余的封装成方法来实现.下面是UserDaoImp ...

  7. 使用ajax请求后返回数据显示undefinded解决办法

    今天在使用七牛的sdk做断点续传时候,当文件过大,本地没有存储时候,想要通过ajax将本地信息存到服务器缓存,之后通过读取缓存的方式来完成文件过大断点续传无法使用问题. 但是在使用ajax请求的时候, ...

  8. SQL点点滴滴_SQL分页查询

    假如tb_customer表中的数据量非常大,在显示时要分页显示而且每页只显示10条信息.为了效果我们取第三页的数据. 方法一:遍历两次表,取不同的数据. select top 10 * from t ...

  9. 沉淀,再出发:AngularJS初探

    沉淀,再出发:AngularJS初探 一.前言 知识的学习需要形成一个闭环,在这个闭环之内可以自圆其说,从而触类旁通,加以理想创造,从而产生灵感.关于前端的知识,我已经写得差不多了,但是还有一个知识点 ...

  10. 骑士周游问题跳马问题C#实现(附带WPF工程代码)

    骑士周游问题,也叫跳马问题. 问题描述: 将马随机放在国际象棋的8×8棋盘的某个方格中,马按走棋规则进行移动.要求每个方格只进入一次,走遍棋盘上全部64个方格. 代码要求: 1,可以任意选定马在棋盘上 ...