Dubbo SPI源码分析

在Dubbo的官网上,能够看到这样一句话:java

Dubbo具备高度的可扩展能力,遵循微内核+插件的设计原则,全部核心能力如Protocol、Transport、Serialization被设计为扩展点,平等对待内置实现和第三方实现。apache

对于一个开源的RPC框架,可扩展性是十分重要的,那么Dubbo是怎么来实现这一点的呢,咱们能够在Dubbo的源码中随处能够看到下面这样的代码:缓存

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
复制代码

这个ExtensionLoader就是Dubbo扩展能力的基础,也是理解Dubbo运行机制的基石,那么下面咱们先来了解了解SPI是什么。安全

SPI机制

SPI(Service Provider Interface),是一种服务发现机制,Dubbo的SPI是从Java SPI加强而来,Dubbo的文档中给了三个加强的理由:ruby

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

其中第一点懒加载和第三点IOC是咱们平时所熟知的,也是我我的认为比较重要的,至于第二点笔者也从未碰见过这样的场景。bash

首先来看一个小小的Dubbo SPI的使用案例:app

  1. 定义一个Interface Transporter负载均衡

    @SPI("udp") 
    public interface Transporter {
    
        void send(String msg);
    
        @Adaptive("transporter")
        void send(String msg, URL url);
    }
    复制代码
  2. 定义Transporter的实现类框架

    UDPTransportertcp

    public class UDPTransporter implements Transporter{
        public void send(String msg) {
            System.out.println("Transfer " + msg + " thorough UDP");
        }
        public void send(String msg, URL url) {
            send(msg);
        }
    }
    复制代码

    TCPTransporter

    @Activate(value = "reliability")
    public class TCPTransporter implements Transporter{
        public void send(String msg) {
            System.out.println("Transfer " + msg + " thorough TCP");
        }
        public void send(String msg, URL url) {
            send(msg);
        }
    }
    复制代码
  3. 关联SPI机制

    咱们须要在/resources/META-INF/dubbo目录下建立以下文件

image-20191217231726993.png

文件内容为:

tcp=com.wanglaomo.playground.dubbo.provider.spi.TCPTransporter
udp=com.wanglaomo.playground.dubbo.provider.spi.UDPTransporter
复制代码

相比于Java的SPI机制,咱们能够看到Dubbo的SPI多了一层映射关系{tcp -> TCPTransporter, udp -> UDPTransporter}。

  1. 最后调用Transporter实现

    ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Transporter.class);
    
    System.out.println(">>>>>>>>> 基础用法");
    Transporter transporter = (Transporter) extensionLoader.getExtension("tcp");
            transporter.send("msg");
    
    System.out.println(">>>>>>>>> 默认实现");
    Transporter defaultTransporter = (Transporter) extensionLoader.getDefaultExtension();
    defaultTransporter.send("msg");
    
    System.out.println(">>>>>>>>> 自适应实现");
    Transporter adaptiveTransporter = (Transporter) extensionLoader.getAdaptiveExtension();
            adaptiveTransporter.send("msg", URL.valueOf("test://localhost/test?transporter=udp"));
    
    System.out.println(">>>>>>>>> 自动激活实现");
    List<Transporter> activeTransporters = extensionLoader.getActivateExtension(URL.valueOf("test://localhost/test?reliability=true"), (String) null);
    for(Transporter activateTransporter : activeTransporters) {
        activateTransporter.send("msg");
    }
    复制代码

结果为:

>>>>>>>>> 基础用法
Transfer msg thorough TCP
>>>>>>>>> 默认实现
Transfer msg thorough UDP
>>>>>>>>> 自适应实现
Transfer msg thorough TCP
>>>>>>>>> 自动激活实现
Transfer msg thorough TCP
复制代码

在这个小小的Demo里面咱们一次性地把Dubbo SPI的全部用法都过了个遍,在Dubbo源码中也无外乎就这几种。

如今看起来可能还有些不知因此然,看完下面的几个小节就会明白了。

Dubbo SPI 基本概念

Dubbo中全部可扩展的接口都统称为扩展点,如:注册中心的扩展(Registry)、序列化方式的扩展(Serialization)等。

当在接口上标注**@SPI**注解时,则代表该接口是一个Dubbo的扩展点(如Demo中的Transporter接口)。在Dubbo启动时,会将这些接口及相应的实现类扫描并进行相应类的加载。所以当咱们能够能够经过实现扩展点接口或@SPI注解让Dubbo自动帮咱们管理扩展点。

META-INF/dubbo下的文件中,等号左边为扩展点的别名,等号右边为扩展点的实现类。至于为何要放在这个目录下,则是Dubbo约定俗成的一种写法。

Dubbo URL至关于一次Dubbo调用的配置信息,是可变化的。

@Activate代表该扩展点是可被激活的扩展点,当value为空表示默认激活,当value不为空时,则表示Dubbo URL中包含相关参数时才会激活。同时还有一个group的属性,通常填写为CONSUMER或PROVIDER,代表只有在消费者或生产者中才会激活。

@Adaptive能够被标注在类和方法上,当标注在方法上时,代表Dubbo会对生成的代理类的该方法进行自适应拓展,会根据URL中的参数调用相应的扩展点的方法。从而实如今运行时动态的决定加载某一个扩展点。至关于代理模式和策略模式的一个结合。基本上,每个扩展点都会被Dubbo生成一个相应的自适应扩展类。当@Adaptive被标注在类上时,则代表Dubbo直接使用该类做为已实现自适应扩展的类,而不用Dubbo再自行生成。目前Dubbo有这么两个类被加上了@Adaptive注解:AdaptiveCompilerAdaptiveExtensionFactory。能够看看这两个类,加深理解。

扩展点的IOC工厂

在上面Demo中高频出现的ExtensionLoader类则实现了对这些扩展点的管理。能够看到ExtensionLoader中维护了这个静态的ConcurrentHashMap,键为扩展点接口对应的Class,值为相对应的ExtensionLoader。

static ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
复制代码

而在每一个ExtensionLoader的每个对象中维护着下面的四种成员变量:

// 可激活的ExtensionLoader
Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
// 自适应的实现
Class<?> cachedAdaptiveClass;
// 默认实现的名称 
String cachedDefaultName;
// 全部的扩展点别名和扩展点实体的集合
ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
复制代码

经过这四种成员变量咱们能够发现ExtensionLoader是一个很重的工厂类对象,再结合下面会讲到的扩展点自动注入,ExtensionLoader基本上实现了一个功能完备的扩展点IOC工厂。

那么咱们来经过源码看看这个IOC工厂是怎么运行的,咱们先以这一句代码为出发点来理解ExtensionLoader

// ExtensionLoader#getExtensionLoader
// ExtensionLoader的集合
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();

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 an interface!");
    }
    // 判断是否为@SPI标注的扩展点
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type (" + type +
                                           ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
    }

    // 建立ExtensionLoader
    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;
}

复制代码

首先作一些扩展点接口的参数判断,而后经过ConcurrentHashMap#putIfAbsent方法来实现ExtensionLoader的延迟加载。而后让咱们看一下ExtensionLoader的建立过程。

// ExtensionLoader#construction
private final ExtensionFactory objectFactory;

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

咱们能够看到基本上这里只是个工厂类的壳子,相应的扩展类并无被加载,由于扩展点实现类的加载也是延迟的。至于这个ExtensionFactory,由于它自己也被标注为@SPI扩展点,在获取它的ExtensionLoader时也会来这里走一遭,因此在这里进行了特殊的判断,打断了循环。

顾名思义,ExtensionFactory也是个工厂类,包含了一个接口,该接口用于获取一个扩展点的实现类

<T> T getExtension(Class<T> type, String name);
复制代码

Dubbo会在自动注入扩展点时使用到该方法,它一共有三个实现类:

ExtensionFactory.png

在Duubo中通常会直接使用AdaptiveExtensionFactory来获取扩展点实现类。

// AdapttiveExtensionFactory 
public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        // 获取全部的扩展点实现类 
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        // 逐个去获取实现类
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }
复制代码

在建立AdaptiveExtensionFactory时,会利用ExtensionLoader加载全部的ExtensionFactory扩展点的实现类,并在getExtension方法中逐个尝试获取扩展点实现类。

SPIExtensionFactory比较简单,就是直接经过ExtensionLoader得到实现类的自适应扩展点实现类。

// SPIExtensionFactory#getExtension
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;
}
复制代码

而SpringExtensionFactory则是依托于Spring的IOC容器加载实现类。

// SpringExtensionFactory#getExtension
public <T> T getExtension(Class<T> type, String name) {
    if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
        return null;
    }

    for (ApplicationContext context : CONTEXTS) {
        if (context.containsBean(name)) {
            Object bean = context.getBean(name);
            if (type.isInstance(bean)) {
                return (T) bean;
            }
        }
    }
    // ...省略
}
复制代码

我的理解,SpringExtensionFactory是对SPIExtensionFactory的一个补充,能够绕过SPI配置而直接使用开发者熟悉的Spring Bean的这一套东西。

讲到这里,可能很多读者以为ExtensionLoader和ExtensionFactory有些重复的感受。我我的理解是,ExtensionFactory是一个SPI加载机制的扩展点,能够将别的加载类的体系归入到Dubbo SPI的这个模型, 从而实现和Dubbo框架的融合,方便Dubbo统一的进行扩展点的管理,而不用在代码中四处“打补丁”.

而鉴于如今有且只有Spring的一种,若是在不使用Spring Bean的状况下,并且彻底能够去除掉ExtensionFactory这个类。

Dubbo SPI的扩展点分类

上面讲清楚Dubbo的扩展点工厂后,咱们就来接入本文的核心,一个扩展点在被ExtensionLoader加载后,它的实现类会造成下面这几种扩展点:

  1. 普通扩展点实现
  2. 自适应扩展点实现(有且仅有一个)
  3. 可激活扩展点实现
  4. 默认扩展点实现(零个或一个)
  5. wrapper扩展点实现

一个扩展点的实现可能同时隶属于上面的一种或多种。下面来看一个普通的扩展点实现是怎么加载的。

普通扩展点

// ExtensionLoader#getExtension
public T getExtension(String name) {
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    // 若是name为true,则返回默认实现
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    final Holder<Object> holder = getOrCreateHolder(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;
}

private Holder<Object> getOrCreateHolder(String name) {
    
    // 由于Holder对象很轻量,因此这里没有使用双重检查锁
    Holder<Object> holder = cachedInstances.get(name);
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<>());
        holder = cachedInstances.get(name);
    }
    return holder;
}

// Holder
public class Holder<T> {
    private volatile T value;
    public void set(T value) {
        this.value = value;
    }
    public T get() {
        return value;
    }
}
复制代码

由于createExtension是一个很重的方法,会加载不少类,为了不重复加载,包装了一层Holder对象,并对每一次加载执行双重检查锁确保线程安全。

// ExtensionLoader#createExtension
private T createExtension(String name) {
    // 获取和扩展点别名配对的实现类的Class文件
    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);
        // 处理wrapperClass,在下面自动包装扩展点讲解
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (CollectionUtils.isNotEmpty(wrapperClasses)) {
            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 + ") couldn't be instantiated: " + t.getMessage(), t);
    }
}
复制代码

上面这段代码主要包含三个主要逻辑,咱们下面一个一个看.

1. 获取扩展点实现类的Class文件

private Map<String, Class<?>> getExtensionClasses() {
    Map<String, Class<?>> classes = cachedClasses.get();
    // 双重检查锁
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                // 扫描并收集全部扩展点的Class文件
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}
复制代码

Dubbo会将全部的扩展点的Class文件收集起来,同时对不一样的实现类的Class做区分,使用双重检查锁避免重复加载。

// ExtensionLoader#loadExtensionClasses
private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

// synchronized in getExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
    // 在下面默认扩展点讲解
    cacheDefaultExtensionName();

    Map<String, Class<?>> extensionClasses = new HashMap<>();
    // [套娃一]从给定目录中加载SPI配置文件及收集扩展点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会从约定的目录下(/META-INF/services/META-INF/dubbo/META-INF/dubbo/internal)中扫描SPI配置文件。后面的replace应该是为了适配到Apache包的缘故

// ExtensionLoader#loadDirectory
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
    // 在上面demo中
    // fileName = "METAINF/dubbo/com.wanglaomo.playground.dubbo.provider.spi.Transporter"
    String fileName = dir + type;
    try {
        Enumeration<java.net.URL> urls;
        // 依次从这几个地方找ClassLoader 
        // Thread ClassLoader -> class.getClassLoader() -> SystemClassLoader
        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 occurred when loading extension class (interface: " +
                     type + ", description file: " + fileName + ").", t);
    }
}
复制代码

这一层套娃主要是获取当前环境中的ClassLoader,确保可以正确的加载扩展点实现类Class。同时使用该ClassLoader加载SPI配置文件

// ExtensionLoader#loadResource
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
    try {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
            String line;
            // 以咱们的demo中的例子来讲 
            // line = "tcp=com.wanglaomo.playground.dubbo.provider.spi.TCPTransporter"
            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;
                        int i = line.indexOf('=');
                        // 以等号分割line得到扩展点别名和扩展点实现类Class的全限定名
                        if (i > 0) {
                            name = line.substring(0, i).trim();
                            line = line.substring(i + 1).trim();
                        }
                        if (line.length() > 0) {
                            // [套娃三] 真正得到Class的地方
                            loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                        }
                    } catch (Throwable t) {
                        IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                        exceptions.put(line, e);
                    }
                }
            }
        }
    } catch (Throwable t) {
        logger.error("Exception occurred when loading extension class (interface: " +
                     type + ", class file: " + resourceURL + ") in " + resourceURL, t);
    }
}
复制代码

这一层套娃是解析SPI配置文件,获得扩展点别名和扩展点实现类Class的全限定名

// ExtensionLoader#loadClass
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    // 判断实现类是否implements扩展点接口
    if (!type.isAssignableFrom(clazz)) {
        throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                                        type + ", class line: " + clazz.getName() + "), class "
                                        + clazz.getName() + " is not subtype of interface.");
    }
    // 在下面自适应扩展点中讲解 讲解若是当前扩展点实现类上被标注了@Adaptive则代表该类为可适应扩展点实现类
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        cacheAdaptiveClass(clazz);
    // 在下面自动包装扩展点中讲解
    } else if (isWrapperClass(clazz)) {
        cacheWrapperClass(clazz);
    } else {
        // 检查扩展点实现类的无参构造方法
        clazz.getConstructor();
        // 若是扩展点别名为空则从别的地方获取别名
        if (StringUtils.isEmpty(name)) {
            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 (ArrayUtils.isNotEmpty(names)) {
            // 在下面自动激活扩展点中讲解
            cacheActivateClass(clazz, names[0]);
            for (String n : names) {
                // 将扩展点实现类Class和扩展点别名的对应关系保存在Map中,方便查询
                cacheName(clazz, n);
                // 将扩展点别面和扩展点实现类Class的对应关系保存在Map中
                saveInExtensionClass(extensionClasses, clazz, n);
            }
        }
    }
}

// ExtensionLoader#findAnnotationName
private String findAnnotationName(Class<?> clazz) {
    org.apache.dubbo.common.Extension extension = clazz.getAnnotation(org.apache.dubbo.common.Extension.class);
    if (extension == null) {
        // 没有被标注,则以驼峰命名法获取扩展点别名
        String name = clazz.getSimpleName();
        if (name.endsWith(type.getSimpleName())) {
            name = name.substring(0, name.length() - type.getSimpleName().length());
        }
        return name.toLowerCase();
    }
    // 若是扩展点实现类上被标注了@Extension注解,则以@Extension注解的value值为扩展点别名
    return extension.value();
}

private void saveInExtensionClass(Map<String, Class<?>> extensionClasses, Class<?> clazz, String name) {
    Class<?> c = extensionClasses.get(name);
    if (c == null) {
        extensionClasses.put(name, clazz);
    } else if (c != clazz) {
        // 不容许有两个扩展点别名相同
        throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + name + " on " + c.getName() + " and " + clazz.getName());
    }
}
复制代码

这最后一层套娃主要是区分不一样的扩展点实现类,咱们能够看到,自适应扩展点和自动包装扩展点是和其余类型扩展点实现类是各自分隔的。而自动激活扩展点、默认扩展点和普通扩展点是相容的。

至此,全部的获取扩展点实现类的Class文件的相关逻辑已经追踪完毕。结合ExtensionLoader#createExtension中的代码,咱们能够发现,Dubbo一次性会将全部的扩展点的Class文件收集起来,可是并不会将这些Class文件实例化,而是在使用到该扩展点实现类时才会实例化,从而达到了延迟加载的效果。

2.扩展点实现类Class实例化

T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
    // 经过反射建立实例
    EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
    instance = (T) EXTENSION_INSTANCES.get(clazz);
}
复制代码

扩展点实例化的代码就比较简单,由于以前已经检查过扩展点实现类的无参构造方法,因此这里能够直接使用clazz.newInstance()来实例化。同时也使用了ConcurrentHashMap的putIfAbsent来确保线程安全性。

3.扩展点自动注入

// ExtensionLoader#injectExtension
private T injectExtension(T instance) {
    try {
        // 前面所述的ExtensionFactory的实例不能为空
        if (objectFactory != null) {
            // 循环当前实例的方法
            for (Method method : instance.getClass().getMethods()) {
                // 全部对的set方法
                if (isSetter(method)) {
                    // 当方法上被标注@DisableInject时忽略自动注入
                    if (method.getAnnotation(DisableInject.class) != null) {
                        continue;
                    }
                    // 只根据set方法的第一个参数作匹配并获得该参数的类文件
                    Class<?> pt = method.getParameterTypes()[0];
                    // 若是为基本数据类型则跳过
                    if (ReflectUtils.isPrimitives(pt)) {
                        continue;
                    }
                    try {
                        // 根据驼峰命名法得到参数的扩展点别名
                        String property = getSetterProperty(method);
                        // 经过ExtensionFactory得到扩展点实例
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            // 执行set方法进行注入
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) {
                        logger.error("Failed to inject via method " + method.getName()
                                     + " of interface " + type.getName() + ": " + e.getMessage(), e);
                    }
                }
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}
复制代码

IOC注入的方式在上面的注释中已经写得很清楚了,并无想象中那么智能,甚至能够说有一点笨拙,须要对全部的扩展点的set方法有了一些约束,可是胜在简单好用。

4. 普通扩展点总结

实现原理主要是依赖了ClassLoader的一些机制,同时使用了反射的技术来操做扩展点实现类的Class文件和其上对应的注解。我的感受是一个比较经典的根据配置文件来加载工厂类的实现。其中的延迟加载、根据注解自动配置以及自动注入值得好好的吸取一下

普通扩展点的加载是逻辑最长的一段,其余类型的扩展点的加载都是依托于普通扩展点加载完成。若是有什么疑惑的地方,能够反复理解一下。

自适应扩展点

自适应扩展点的加载咱们从加载普通扩展点时的那个逻辑分支继续追踪

private volatile Class<?> cachedAdaptiveClass = null; 

// ExtensionLoader#loadClass
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    // ... 省略
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        cacheAdaptiveClass(clazz);
    // ... 省略
}

private void cacheAdaptiveClass(Class<?> clazz) {
    if (cachedAdaptiveClass == null) {
        // 缓存自适应扩展点为
        cachedAdaptiveClass = clazz;
        // 被标注为@Adaptive的扩展点实现类只能有一个
    } else if (!cachedAdaptiveClass.equals(clazz)) {
        throw new IllegalStateException("More than 1 adaptive class found: "
                                        + cachedAdaptiveClass.getClass().getName()
                                        + ", " + clazz.getClass().getName());
    }
}
复制代码

这一段代码就是以前所说的这句

当@Adaptive被标注在类上时,则代表Dubbo直接使用该类做为已实现自适应扩展的类,而不用Dubbo再自行生成。

的具体实现。那么Dubbo是怎么自动生成自适应扩展点呢?让咱们从加载自适应扩展点的逻辑开始分析

// ExtensionLoader#getAdaptiveExtension
private final Holder<Object> cachedAdaptiveInstance = new Holder<>();

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("Failed to create adaptive instance: " + t.toString(), t);
                    }
                }
            }
        } else {
            throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
        }
    }
    return (T) instance;
}

private T createAdaptiveExtension() {
    try {
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
    }
}
复制代码

建立自适应扩展点实例有三个步骤,其中injectExtension自动注入和以前的加载普通扩展点时同样的。并且也是经过无参构造方法新建一个新的实例。重点在于getAdaptiveExtensionclass获取自适应扩展点实现类的Class。

// ExtensionLoader#getAdaptiveExtensionClass
private Class<?> getAdaptiveExtensionClass() {
    // 和以前相似加载全部的扩展点Class文件,有可能已经加载过了。
    getExtensionClasses();
    // 若是该type全部的扩展点中有标注为@Adaptive的扩展点实现类,则直接使用
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    // 不然建立一个
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
复制代码

这一段的逻辑仍是和以前所述一致。

// ExtensionLoader#createAdaptiveExtensionClass
private Class<?> createAdaptiveExtensionClass() {
    // 生成自适应扩展点的源代码
    String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
    ClassLoader classLoader = findClassLoader();
    org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    // 使用Compiler编译该源代码并生成Class文件
    return compiler.compile(code, classLoader);
}
复制代码

经过给定的扩展点接口的Class文件及默认扩展点的名称生成自适应扩展点的源代码,由于该方法比较繁琐,主要就是字符串的组装,因此这里直接贴出咱们上面的demo生成的自适应扩展点实现的源代码,方便理解。

package com.wanglaomo.playground.dubbo.provider.spi;

import org.apache.dubbo.common.extension.ExtensionLoader;

public class Transporter$Adaptive implements com.wanglaomo.playground.dubbo.provider.spi.Transporter {
    // 由于adptive方法必需要传入URL对象,因此这里直接抛出一异常
    public void send(java.lang.String arg0) {
        throw new UnsupportedOperationException("The method public abstract void com.wanglaomo.playground.dubbo.provider.spi.Transporter.send(java.lang.String) of interface com.wanglaomo.playground.dubbo.provider.spi.Transporter is not adaptive method!");
    }

    public void send(java.lang.String arg0, org.apache.dubbo.common.URL arg1) {
        // 检查URL参数
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg1;
        // 得到URL对象中有关咱们事先配置好的@Adaptive参数对应的值,没有的话使用默认扩展点名称
        String extName = url.getParameter("transporter", "udp");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (com.wanglaomo.playground.dubbo.provider.spi.Transporter) name from url (" + url.toString() + ") use keys([transporter])");
        // 根据从参数得到的扩展点别名得到相应的扩展点实现类
        com.wanglaomo.playground.dubbo.provider.spi.Transporter extension = (com.wanglaomo.playground.dubbo.provider.spi.Transporter) ExtensionLoader.getExtensionLoader(com.wanglaomo.playground.dubbo.provider.spi.Transporter.class).getExtension(extName);
        // 调用具体扩展点实现类的具体方法
        extension.send(arg0, arg1);
    }
}
复制代码

从生成出来的代码来看,主要是配合工厂类和URL对象来实现自动适配。

有了源代码以后,就会调用Compiler对象进行编译,目前支持两种编译器,JavaassistCompiler和JDKCompiler。Javassist是一个操做字节码文件的库,能够动态的更改字节码文件,固然也能够实时编译,建立新的字节码文件。具体Compiler是怎么工做的和SPI机制关系不大,因此这里不作讲解。

自适应扩展点很好的实现了了在运行时动态决定调用某个扩展点实现的功能,而且默认会给每个扩展点生成一个,方便调用,主要的难点就是生成类的源代码和动态编译。

自动激活扩展点

自动激活扩展点的加载咱们从加载普通扩展点时的那个逻辑分支继续追踪

// ExtensionLoader#cacheActivateClass
private void cacheActivateClass(Class<?> clazz, String name) {
    // 当扩展点实现类被标注上@Activate注解时,则将扩展点别名和Activate对象的关联关系保存在Map中
    Activate activate = clazz.getAnnotation(Activate.class);
    if (activate != null) {
        cachedActivates.put(name, 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(name, oldActivate);
        }
    }
}
复制代码

这段代码没什么特别的,主要是将关联关系提早收集并保存。主要的逻辑在加载自动激活扩展点的代码中

// ExtensionLoader#getActivateExtension
public List<T> getActivateExtension(URL url, String key, String group) {
    
    // 从URL对象中经过给定键获取对应扩展点别名集合
    String value = url.getParameter(key);
    // 若是扩展点别名集合不为空则以逗号分隔
    return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
}

public List<T> getActivateExtension(URL url, String[] values, String group) {
    List<T> exts = new ArrayList<>();
    List<String> names = values == null ? new ArrayList<>(0) : Arrays.asList(values);
    // REMOVE_VALUE_PREFIX + DEFAULT_KEY = -default
    // 即若是URL配置对象中没有-default的配置
    if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
        getExtensionClasses();
        // 迭代收集起来的扩展点别名和Activate对象的关联关系
        for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
            String name = entry.getKey();
            Object activate = entry.getValue();

            String[] activateGroup, activateValue;
            // 得到@Active注解上标注的激活Value集合和激活Group集合
            if (activate instanceof Activate) {
                activateGroup = ((Activate) activate).group();
                activateValue = ((Activate) activate).value();
            } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
                activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
                activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
            } else {
                continue;
            }
            // 判断当前给定group是否在激活group中
            if (isMatchGroup(group, activateGroup)) {
                // 根据扩展点别名得到扩展点实例
                T ext = getExtension(name);
                
                // 若是指定扩展点别名不包含该扩展点,若是包含在下面会处理
                if (!names.contains(name)
                    // 若是URL配置对象中没有指定去除该扩展点
                    && !names.contains(REMOVE_VALUE_PREFIX + name)
                    // 而且该扩展点是激活的
                    && isActive(activateValue, url)) {
                    exts.add(ext);
                }
            }
        }
        exts.sort(ActivateComparator.COMPARATOR);
    }
    List<T> usrs = new ArrayList<>();
    // 迭代这次指定的指定的扩展点集合
    for (int i = 0; i < names.size(); i++) {
        String name = names.get(i);
        // 若是不包含去除该扩展点的设置
        if (!name.startsWith(REMOVE_VALUE_PREFIX)
            && !names.contains(REMOVE_VALUE_PREFIX + name)) {
            // 将给定激活的扩展点放在默认的激活点前面 -> 笔者也不知道这有啥用
            if (DEFAULT_KEY.equals(name)) {
                if (!usrs.isEmpty()) {
                    exts.addAll(0, usrs);
                    usrs.clear();
                }
            } else {
                T ext = getExtension(name);
                usrs.add(ext);
            }
        }
    }
    // 合并两种扩展点集合usrs和exts
    if (!usrs.isEmpty()) {
        exts.addAll(usrs);
    }
    return exts;
}

private boolean isMatchGroup(String group, String[] groups) {
    if (StringUtils.isEmpty(group)) {
        return true;
    }
    if (groups != null && groups.length > 0) {
        for (String g : groups) {
            if (group.equals(g)) {
                return true;
            }
        }
    }
    return false;
}

// 经过URL配置对象中是否存在参数名为 `value`或`.value` ,而且参数值非空。
private boolean isActive(String[] keys, URL url) {
    if (keys.length == 0) {
        return true;
    }
    for (String key : keys) {
        for (Map.Entry<String, String> entry : url.getParameters().entrySet()) {
            String k = entry.getKey();
            String v = entry.getValue();
            if ((k.equals(key) || k.endsWith("." + key))
                && ConfigUtils.isNotEmpty(v)) {
                return true;
            }
        }
    }
    return false;
}
复制代码

-default配置的意思是不包含@Activate注解事先配置的匹配规则,只在这次给定的键从URL配置对象得到的扩展点别名集合中进行匹配。

自动激活扩展点比较绕的就是会有两种匹配逻辑。把他们割裂看起来会好理解一点。我我的感受这种写法虽然会让配置时少配置一些参数,可是难以一眼就判断出到底是哪些扩展点是激活的。

默认扩展点

默认扩展点的加载咱们从加载普通扩展点时的那个逻辑分支继续追踪

//ExtensionLoader#cacheDefaultExtensionName
private String cachedDefaultName;

private void cacheDefaultExtensionName() {
    // type就是扩展点接口的Class文件
    // 以上面demo为例,则type = com.wanglaomo.playground.dubbo.provider.spi.Transporter
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    // SPI注解标注不能为空
    if (defaultAnnotation != null) {
        // @SPI注解的value熟悉
        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));
            }
            // 若是value不为空且只有一个,则将默认扩展点别名保存下来
            if (names.length == 1) {
                cachedDefaultName = names[0];
            }
        }
    }
}
复制代码

从上面所知,获取默认扩展点别名是在加载全部扩展点的Class文件时,顺带收集的,并且只能有一个。由于loadExtensionClasses几乎是全部方法的前置方法,因此在获取默认默认扩展点时,cachedDefaultName确定已经被赋值。下面来看看获取默认扩展点实例的逻辑。

public T getDefaultExtension() {
     // 确保cachedDefaultName必定已被收集过
     getExtensionClasses();
     // cachedDefaultName为空和为"true"时返回空
     if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
         return null;
     }
     // 继续得到普通扩展点实例的流程
     return getExtension(cachedDefaultName);
 }
复制代码

默认扩展点的逻辑比较简单,就是事先配置在注解中,而后获取。

自动包装扩展点

自动包装扩展点在外面上面的Demo中并无包含,由于我没有找到比较适合的例子来代表它的做用。可是自动包装也是很重要的,是一个很好玩的特性,Dubbo官方对于Wrapper类的介绍为:

  • 自动包装扩展点的 Wrapper 类。ExtensionLoader 在加载扩展点时,若是加载到的扩展点有拷贝构造函数,则断定为扩展点 Wrapper 类。

  • Wrapper 类一样实现了扩展点接口,可是 Wrapper 不是扩展点的真正实现。它的用途主要是用于从 ExtensionLoader 返回扩展点时,包装在真正的扩展点实现外。即从 ExtensionLoader 中返回的其实是 Wrapper 类的实例,Wrapper 持有了实际的扩展点实现类。

    扩展点的 Wrapper 类能够有多个,也能够根据须要新增。

    经过 Wrapper 类能够把全部扩展点公共逻辑移至 Wrapper 中。新加的 Wrapper 在全部的扩展点上添加了逻辑,有些相似 AOP,即 Wrapper 代理了扩展点。

  • AOP 类都命名为 XxxWrapper

  • 尽可能采用 AOP 实现扩展点的通用行为,而不要用基类,好比负载均衡以前的 isAvailable 检查,它是独立于负载均衡以外的,不须要检查的是URL参数关闭。

因此说,咱们能够把Wrapper类看做是Dubbo对于扩展点AOP的简单实现,在Dubbo内部大量的使用了该特性,好比ProtocolFilterWrapper

public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;
    
    private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        Invoker<T> last = invoker;
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);

        if (!filters.isEmpty()) {
            for (int i = filters.size() - 1; i >= 0; i--) {
                final Filter filter = filters.get(i);
                // 组成责任链模式的调用
                // ... 省略
            }
        }

        return new CallbackRegistrationInvoker<>(last, filters);
    }
}
复制代码

经过这个例子咱们能够看到,利用Wrapper类的特性,咱们能够将某个扩展点的方法的调用包装起来,在方法调用的先后加上咱们本身的逻辑,如上面就造成了一种责任链模式的拦截器调用。

理解了Wrapper的做用后,就很好理解自动包装相关的代码了。对于SPI来讲,须要作的是给Wrapper类自动注入真正扩展点类的实现。让咱们从以前的那个逻辑分支继续开始。

// ExtensionLoader#loadClass
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        // ... 省略
    } else if (isWrapperClass(clazz)) {
        cacheWrapperClass(clazz);
    } else {
        // ... 省略
    }
}

private boolean isWrapperClass(Class<?> clazz) {
    try {
        // 若是Class包含一个有且只有扩展点接口参数的方法,则为自动扩展实现类
        clazz.getConstructor(type);
        return true;
    } catch (NoSuchMethodException e) {
        return false;
    }
}

private void cacheWrapperClass(Class<?> clazz) {
    if (cachedWrapperClasses == null) {
        cachedWrapperClasses = new ConcurrentHashSet<>();
    }
    // 收集全部该扩展点的自动包装扩展点Class
    cachedWrapperClasses.add(clazz);
}
复制代码

比较简单,仍是收集。下面继续看组装自动包装扩展点的逻辑:

// ExtensionLoader#createExtension
private T createExtension(String name) {
    // ... 省略
    try {
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
	    // ... 省略
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (CollectionUtils.isNotEmpty(wrapperClasses)) {
            for (Class<?> wrapperClass : wrapperClasses) {
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
    }
    // ... 省略
}
复制代码

当咱们真正的扩展点实现类被建立出来后,会进行自动包装类的实体注入。经过调用相应的构造方法实现。

我我的感受自动包装是一个自动注入的特殊状况,属于扩展点实现之间的注入。算是个AOP的一种简易实现吧。

结语

这篇文章没想到会写的这么长,彻底梳理完后,Dubbo的SPI机制仍是比较清晰的。Dubbo依托于微内核+插件的设计机制的确扩展性很好,并且实现并不复杂,四种特殊的扩展点也都颇有特色并且颇有用,其中有不少有意思的设计(延迟加载、类aop的简易模型、默认生成的自适应扩展点...)值得好好再品一下。若是文章中有什么问题,欢迎指正。

相关文章
相关标签/搜索