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

最近抽空开始了 Dubbo 源码的阅读之旅,但愿能够经过写文章的方式记录和分享本身对 Dubbo 的理解。若是在本文出现一些纰漏或者错误之处,也但愿你们不吝指出。javascript

Dubbo SPI 介绍

Java SPI

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

Dubbo SPI 的改进点

如下内容摘录自 dubbo.gitbooks.io/dubbo-dev-b…
Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制增强而来。 JDK 标准的 SPI 会一次性实例化扩展点全部实现,若是有扩展实现初始化很耗时,但若是没用上也加载,会很浪费资源。 若是扩展点加载失败,连扩展点的名称都拿不到了。好比:JDK 标准的 ScriptEngine,经过 getName() 获取脚本类型的名称,但若是 RubyScriptEngine 由于所依赖的 jruby.jar 不存在,致使 RubyScriptEngine 类加载失败,这个失败缘由被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的缘由。 增长了对扩展点 IoC 和 AOP 的支持,一个扩展点能够直接 setter 注入其它扩展点java

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

Dubbo SPI 机制详解

Dubbo 扩展点的加载

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

  • 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 是如何获取的。ruby

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 的扩展自适应类。bash

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 。app

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 机制,方便咱们添加监控和打印日志。