Dubbo核心 - SPI 机制详解

本文主要介绍了 Dubbo 中的 SPI 机制。文章部分参考自 Dubbo SPI 官方文档,并在此基础上进行了扩展深入。

涉及到Dubbo的源码分析,Dubbo版本是 2.8.7

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。接下来,我们先来了解一下 Java SPI 与 Dubbo SPI 的用法,然后再来分析 Dubbo SPI 的源码。

Java 中的 SPI 机制

前面简单介绍了 SPI 机制的原理,本节通过一个示例演示 Java SPI 的使用方法。首先,我们定义一个接口,名称为 Robot。

public interface Robot {
    void sayHello();
}

接下来定义两个实现类,分别为 OptimusPrime 和 Bumblebee。

public class OptimusPrime implements Robot {
    
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Optimus Prime.");
    }
}

public class Bumblebee implements Robot {

    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee.");
    }
}

接下来 META-INF/services 文件夹下创建一个文件,名称为 Robot 的全限定名 cn.mvbbb.spi.Robot。文件内容为实现类的全限定的类名,如下:

cn.mvbbb.spi.OptimusPrime
cn.mvbbb.spi.Bumblebee

做好所需的准备工作,接下来编写代码进行测试。

public class JavaSPITest {

    @Test
    public void sayHello() throws Exception {
        ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
        System.out.println("Java SPI");
        // 遍历
        serviceLoader.forEach(Robot::sayHello);
    }
}

对上面代码的解释:

JDK中,基于SPI的思想,提供了默认具体的实现,ServiceLoader,位于java.util包中,实现了 Iterable 接口,可以解析 /META-INF/services/ 目录下的配置文件完成对接口具体实现类的实例化。ServiceLoader#load 方法可以加载指定类型(接口)的实现类。

Java SPI机制:ServiceLoader实现原理及应用剖析

执行上述代码:

从测试结果可以看出,我们的两个实现类被成功的加载,并输出了相应的内容。关于 Java SPI 的演示先到这里,接下来演示 Dubbo SPI。

Dubbo中SPI的基本流程与使用

@SPI 注解

Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下。

optimusPrime = cn.mvbbb.spi.OptimusPrime
bumblebee = cn.mvbbb.spi.Bumblebee

与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。下面来演示 Dubbo SPI 的用法:

public class DubboSPITest {

    @Test
    public void sayHello() throws Exception {
        // 获取扩展点加载器
        ExtensionLoader<Robot> extensionLoader = 
            ExtensionLoader.getExtensionLoader(Robot.class);
        // 获取扩展点
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        bumblebee.sayHello();
    }
}

对上述代码的解释:

Dubbo 中的 SPI 机制是 ExtensionLoader(扩展点加载器) 实现的,作用是加载所有打上了 @SPI 注解的接口,并根据配置进行实例化、封装,Dubbo本身的服务调用,暴露等功能也是使用 ExtensionLoader 实现的。简单点说,他就是拿来创建对象的。ExtensionLoader#getExtension 可以根据指定的 key 来获取实现类(扩展点)。

执行上述代码。测试结果如下:

Dubbo SPI 除了支持按需加载接口实现类,还增加了 IOC 和 AOP 等特性,这些特性将会在接下来章节中一一进行介绍。

@Adaptive 注解

dubbo扩展机制有三个重要的注解,分别是:@SPI,@Adaptive,@Activate。此前,我们已经了解了 @SPI 注解,现在来看看 @Adaptive 注解。

@Adaptive标注一般标注在接口的方法上,也可以标注在类,枚举类,方法上,但是比较少见。

@Adaptive标注在接口方法上时,要求接口方法入参中有一个URL,这个URL类是 org.apache.dubbo.common.URL,注解的功能是可以根据方法中URL的入参,来选择对哪一个实现进行调用,使用如下:

  1. 首先给接口的方法上加 @Adaptive 注解,设置 value 的默认值为 key1
  2. 之后在调用接口方法时需要传入 URL ,其中需要指定 key1 参数的值,为 /META-INF/dubbo 配置文件下的其中一个键。

修改接口方法:

@SPI
public interface Robot {
    
    @Adaptive(value = "key1")
    void sayHello(URL url);
}

测试方法:

URL url = URL.valueOf("dubbo://localhost:8080?key1=bumblebee");
ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
Robot robot = extensionLoader.getAdaptiveExtension();
robot.sayHello(url);

与本文开篇直接在 getExtension 不同,使用 @Adaptive 注解需要在方法参数中添加 URL。目的是为了灵活加载接口的实现类,URL中的参数不同,接口的加载类也就不同。

Dubbo中AOP的源码实现

上一章简单演示了 Dubbo SPI 的使用方法。我们首先通过 ExtensionLoader 的 getExtensionLoader 方法获取一个 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。

ExtensionLoader#getExtensionLoader 方法源码:

@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    if (type == null) {
        throw new IllegalArgumentException("Extension type == null");
    }
    // type 必须是接口类型
    if (!type.isInterface()) {
        throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
    }
    // type 必须有 @SPI 注解
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type (" + type +
                                           ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
    }
    // EXTENSION_LOADERS: ConcurrentMap 类型, 维护 类型->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;
}

可以看到 getExtensionLoader 方法并没有涉及到加载类的过程。getExtensionLoader 方法用于从缓存中获取与拓展类对应的 ExtensionLoader,若缓存未命中,则创建一个新的实例。


接着查看 ExtensionLoader#getExtension 方法

/**
* 根据给定的key获取扩展点, 没有找到就抛出异常
*/
@SuppressWarnings("unchecked")
public T getExtension(String name) {
    return getExtension(name, true);
}

public T getExtension(String name, boolean wrap) {
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    if ("true".equals(name)) {
        // 获取默认的拓展实现类
        return getDefaultExtension();
    }
    // Holder,顾名思义,用于持有目标对象, 包装了目标对象
    // getOrCreateHolder 从 cachedInstances 缓存中取出 Holder
    final Holder<Object> holder = getOrCreateHolder(name);
    Object instance = holder.get();
    // double check 双重检查
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                // 创建拓展实例
                instance = createExtension(name, wrap);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

上面的代码的主要逻辑是,先检查缓存未命中则创建拓展对象,将其包装为 Holder,放入缓存。

有两个地方需要说明

  1. ExtensionLoader#getDefaultExtension 方法
  2. ExtensionLoader#createExtension 方法。

关于 ExtensionLoader#getDefaultExtension 方法:

public T getDefaultExtension() {
    // 加载拓展类
    getExtensionClasses();
    // cacheDefaultName 为 @SPI 注解中的内容。也就是说必须在 @SPI 注解中指定一个名称,否则会发生空指针异常
    if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
        return null;
    }
    // 根据 @SPI 注解获取扩展类
    return getExtension(cachedDefaultName);
}

什么时候会调用 ExtenstionLoader#getDefaultExtension 方法? 当调用 ExtensionLoader#getExtension 方法时只传入了一个 “true” 。其中的 getExtensionClassed 方法在之后讲解。

之后讲 ExtensionLoader#createExtension 方法。ExtensionLoader#createExtension 的源码如下:

@SuppressWarnings("unchecked")
private T createExtension(String name, boolean wrap) {
    // getExtensionClasses 从配置文件中加载所有的扩展类, 维护 配置项名称->配置类类型 的映射
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        // EXTENSION_INSTANCES 中维护了从 类型->ExtensionLoader 的映射
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        // 向扩展对象中注入依赖, Dubbo IOC 的体现
        injectExtension(instance);

        // Dubbo AOP 的体现
        // 如果是 Wapper类就将扩展对象包裹在相应的 Wapper 对象中。
        // 比如我基于 Protocol 定义了 DubboProtocol 的扩展,但实际上在 Dubbo 中不是直接使用的 DubboProtocol, 
        // 而是其包装类ProtocolListenerWrapper
        if (wrap) {

            List<Class<?>> wrapperClassesList = new ArrayList<>();
            // loadExtensionClasses 调用之后会缓存 cachedWrapperClasses。 cachedWrapperClasses 是一个 类型set
            if (cachedWrapperClasses != null) {
                wrapperClassesList.addAll(cachedWrapperClasses);
                wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                Collections.reverse(wrapperClassesList);
            }
            // 循环创建 Wrapper 实例
            if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                for (Class<?> wrapperClass : wrapperClassesList) {
                    Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                    if (wrapper == null
                        || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
                        // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
                        // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
                        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                    }
                }
            }
        }
        // 初始化扩展类对象
        initExtension(instance);
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                                        type + ") couldn't be instantiated: " + t.getMessage(), t);
    }
}

ExtensionLoader#createExtension 方法的逻辑如下:

  1. 通过 getExtensionClasses 获取所有的拓展类
  2. 通过反射创建拓展对象
  3. 向拓展对象中注入依赖
  4. 将拓展对象包裹在相应的 Wrapper 对象中

Dubbo 是就是通过第 3 步实现 IOC,第 4 步实现 AOP

一句话总结 Dubbo 中的 AOP 是如何实现的:循环将一个实例当作另外一个类的构造参数然后实例化注入。

接下来看 Dubbo 是如何实现 IOC 的。

Dubbo中的依赖注入的源码实现

不同于 Spring 可以使用 @Autowaired 注解注入依赖,Dubbo IOC 通过 setter 方法注入依赖。

Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。整个过程(ExtensionLoader#injectExtension 方法)对应的代码如下:

private T injectExtension(T instance) {
    // objectFactory 是什么? 稍后解释
    if (objectFactory == null) {
        return instance;
    }

    try {
        // 遍历类中的所有方法, 筛选出 setter方法. setter 的特征: set开头, 一个入参, public 修饰.
        for (Method method : instance.getClass().getMethods()) {
            if (!isSetter(method)) {
                continue;
            }
            /**
             * Check {@link DisableInject} to see if we need auto injection for this property
             *  setter 方法上添加 @DisableInject 注解禁用 Dubbo 注入
             */
            if (method.getAnnotation(DisableInject.class) != null) {
                continue;
            }
            // 获取第一个参数,判断是否是原始类型及其包装类
            Class<?> pt = method.getParameterTypes()[0];
            if (ReflectUtils.isPrimitives(pt)) {
                continue;
            }

            try {
                // 获取属性名,比如 setName 方法对应属性名 name
                String property = getSetterProperty(method);
                // 从 ObjectFactory 中获取依赖对象
                Object object = objectFactory.getExtension(pt, property);
                if (object != null) {
                    // 通过反射调用 setter 方法设置依赖
                    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;
}

在上面代码中,objectFactory 变量的类型 ExtensionFactory。Dubbo 中 ExtensionFactory 的实现类共有 3 种, 分别是 AdaptiveExtensionFactory、SpiExtensionFactory、SpringExtensionFactory。ExtensionFactory 中的作用是根据类型和名字来获取对象。

我们可以见得 Dubbo IOC 的核心就是 ExtensionFactory。


先来看看 SpiExtensionFactroy 的源代码:

/**
 * SpiExtensionFactory
 */
public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        // type类型是否为接口类型, 是否被 SPI 注解修饰
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            // 获取 type 的 ExtensionLoader(用于创建对象的)
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            // 这个 type 的扩展点不为空
            if (!loader.getSupportedExtensions().isEmpty()) {
                // 加载自适应扩展类
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }
}

对 Dubbo 中 IOC 实现的源码大概就深入到这里,做一下总结

Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。

我们需要重点关注的是 ObjectFactory 是如何被加载的,ObjectFactory 是 ExtensionFactory 接口类型,该接口有三个实现类,在其中一个实现类 SpiExtensionFactory 中调用了 ExtensionFactory#getAdaptiveExtension 方法。我们接下来就讲一讲这个方法的由来和作用。

Dubbo 中的自适应拓展点

Dubbo中SPI的基本流程与使用 中我们介绍了 @Adaptive 注解使用方法,这种通过 URL 参数来指定实现类的方式就是基于 Dubbo 底层的自适应扩展点。在对自适应拓展进行深入分析之前先来看看 @Adaptive 注解:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
 
    String[] value() default {};
}

从上面的代码中可知,Adaptive 可注解在类或方法上。当 Adaptive 注解在类上时,Dubbo 不会为该类生成代理类。注解在方法(接口方法)上时,Dubbo 则会为该方法生成代理逻辑。Adaptive 注解在类上的情况很少,在 Dubbo 中,仅有两个类被 Adaptive 注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory。此种情况,表示拓展的加载逻辑由人工编码完成。更多时候,Adaptive 是注解在接口方法上的,表示拓展的加载逻辑需由框架自动生成。Adaptive 注解的地方不同,相应的处理逻辑也是不同的。注解在类上时,处理逻辑比较简单,本文就不分析了。注解在接口方法上时,处理逻辑较为复杂,本节将会重点分析此块逻辑。

ExtensionLoader#getAdaptiveExtension 方法用于加载自适应扩展类。 自适应扩展类的含义是说,基于参数,在运行时动态选择到具体的目标类,然后执行。 在 Dubbo 中,很多拓展都是通过 SPI 机制进行加载的,比如 Protocol、Cluster、LoadBalance 等。有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。这听起来有些矛盾。拓展未被加载,那么拓展方法就无法被调用(静态方法除外)。拓展方法未被调用,拓展就无法被加载。对于这个矛盾的问题,Dubbo 通过自适应拓展机制很好的解决了。自适应拓展机制的实现逻辑比较复杂,首先 Dubbo 会为拓展接口生成具有代理功能的代码。然后通过 javassist 或 jdk 编译这段代码,得到 Class 类。最后再通过反射创建代理类,整个过程比较复杂。

@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
    // 从缓存中获取自适应拓展 cachedAdaptiveInstance: Holder<T>
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
        // 如果存在异常,则直接抛出
        if (createAdaptiveInstanceError != null) {
            throw new IllegalStateException("Failed to create adaptive instance: " +
                                            createAdaptiveInstanceError.toString(),
                                            createAdaptiveInstanceError);
        }

        synchronized (cachedAdaptiveInstance) {
            instance = cachedAdaptiveInstance.get();
            // double check
            if (instance == null) {
                try {
                    // 创建自适应拓展
                    // 这里分为两种情况:一种是存在 Adaptive 类,另一个是需要生成 Adaptive 类
                    instance = createAdaptiveExtension();
                    // 设置自适应扩展到缓存中
                    cachedAdaptiveInstance.set(instance);
                } catch (Throwable t) {
                    createAdaptiveInstanceError = t;
                    throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                }
            }
        }
    }

    return (T) instance;
}

getAdaptiveExtension 方法首先会检查缓存,缓存未命中,则调用 createAdaptiveExtension 方法创建自适应拓展。下面,我们看一下 createAdaptiveExtension 方法的代码。

@SuppressWarnings("unchecked")
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);
    }
}

createAdaptiveExtension 方法的代码比较少,但却包含了三个逻辑,分别如下:

  1. 调用 getAdaptiveExtensionClass 方法获取自适应拓展 Class 对象
  2. 通过反射进行实例化
  3. 调用 injectExtension 方法向拓展实例中注入依赖

前两个逻辑比较好理解,第三个逻辑用于向自适应拓展对象中注入依赖。这个逻辑看似多余,但有存在的必要,这里简单说明一下。前面说过,Dubbo 中有两种类型的自适应拓展,一种是手工编码的,一种是自动生成的。手工编码的自适应拓展中可能存在着一些依赖,而自动生成的 Adaptive 拓展则不会依赖其他类。这里调用 injectExtension 方法的目的是为手工编码的自适应拓展注入依赖,这一点需要大家注意一下。关于 injectExtension 方法,前文已经分析过了,这里不再赘述。接下来,分析 getAdaptiveExtensionClass 方法的逻辑。

private Class<?> getAdaptiveExtensionClass() {
    // 通过 SPI 获取所有的拓展类
    getExtensionClasses();
    // 检查缓存,若缓存不为空,则直接返回缓存
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    // 创建自适应拓展类
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

getAdaptiveExtensionClass 方法同样包含了三个逻辑,如下:

  1. 调用 getExtensionClasses 获取所有的拓展类
  2. 检查缓存,若缓存不为空,则返回缓存
  3. 若缓存为空,则调用 createAdaptiveExtensionClass 创建自适应拓展类

这三个逻辑看起来平淡无奇,似乎没有多讲的必要。但是这些平淡无奇的代码中隐藏了着一些细节,需要说明一下。首先从第一个逻辑说起,getExtensionClasses 这个方法用于获取某个接口的所有实现类。比如该方法可以获取 Protocol 接口的 DubboProtocol、HttpProtocol、InjvmProtocol 等实现类。在获取实现类的过程中,如果某个实现类被 Adaptive 注解修饰了,那么该类就会被赋值给 cachedAdaptiveClass 变量。此时,上面步骤中的第二步条件成立(缓存不为空),直接返回 cachedAdaptiveClass 即可。如果所有的实现类均未被 Adaptive 注解修饰,那么执行第三步逻辑,创建自适应拓展类。相关代码如下:

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();
    // 编译代码,生成 Class
    return compiler.compile(code, classLoader);
}

createAdaptiveExtensionClass 方法用于生成自适应拓展类,该方法首先会生成自适应拓展类的源码,然后通过 Compiler 实例(Dubbo 默认使用 javassist 作为编译器)编译源码,得到代理类 Class 实例。接下来,我们把重点放在代理类代码生成的逻辑上,其他逻辑大家自行分析。


AdaptiveClassCodeGenerator#generate 方法生成扩展类代码

public String generate() {
    // 如果该接口中没有方法被 @Adaptive 注解修饰,直接抛出异常
    if (!hasAdaptiveMethod()) {
        throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
    }

    StringBuilder code = new StringBuilder();
    // 生成包名、import、方法等.
    code.append(generatePackageInfo());
    code.append(generateImports());
    code.append(generateClassDeclaration());

    Method[] methods = type.getMethods();
    for (Method method : methods) {
        code.append(generateMethod(method));
    }
    code.append("}");

    if (logger.isDebugEnabled()) {
        logger.debug(code.toString());
    }
    return code.toString();
}

AdaptiveClassCodeGenerator#generate 中涉及到了多个方法:

1、AdaptiveClassCodeGenerator#hasAdaptiveMethod 方法

private boolean hasAdaptiveMethod() {
    // 非常优雅的 stream 流
    return Arrays.stream(type.getMethods()).anyMatch(m -> m.isAnnotationPresent(Adaptive.class));
}

2、generatePackageInfo、generateImports、generateClassDeclaration 方法用于生成类。以 Dubbo 的 Protocol 接口为例,生成的代码如下:

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
    // 省略方法代码
}

3、AdaptiveClassCodeGenerator#generateMethod 用于生成方法,在接下来的重点分析

生成方法

private String generateMethod(Method method) {
    String methodReturnType = method.getReturnType().getCanonicalName();
    String methodName = method.getName();
    // 生成方法内容
    String methodContent = generateMethodContent(method);
    String methodArgs = generateMethodArguments(method);
    String methodThrows = generateMethodThrows(method);
    return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
}

generateMethodContent 分析

private String generateMethodContent(Method method) {
    // 该方法上必须有 @Adaptive 注解修饰
    Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
    StringBuilder code = new StringBuilder(512);
    if (adaptiveAnnotation == null) {
        // 没有 @Adaptive 注解修饰,生成异常信息
        return generateUnsupported(method);
    } else {
        // 获取 URL 在参数列表上的索引
        int urlTypeIndex = getUrlTypeIndex(method);
        
        if (urlTypeIndex != -1) {
            // 如果参数列表上存在 URL,生成对 URL 进行空检查
            code.append(generateUrlNullCheck(urlTypeIndex));
        } else {
            // 如果参数列表不存在 URL 类型的参数,那么就看参数列表上参数对象中是否包含 getUrl 方法
            // 有的话,生成 URL 空检查
            code.append(generateUrlAssignmentIndirectly(method));
        }
        // 解析 Adaptive 注解上的 value 属性
        String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
        // 如果参数列表上有 Invocation 类型的参数,生成空检查并获取 methodName.
        boolean hasInvocation = hasInvocationArgument(method);
        
        code.append(generateInvocationArgumentNullCheck(method));
        // 这段逻辑主要就是为了生成 extName(也就是扩展名)
        // 分为多种情况:
        // 1.defaultExtName 是否存在
        // 2.参数中是否存在 invocation 类型参数
        // 3.是否是为 protocol 生成代理
        // 为什么要对 protocol 单独考虑了?因为 URL 中有获取 protocol 值的方法
        code.append(generateExtNameAssignment(value, hasInvocation));
        // check extName == null?
        code.append(generateExtNameNullCheck(value));
    
        // 生成获取扩展(使用 ExtensionLoader.getExtension 方法)
        code.append(generateExtensionAssignment());

        // 生成返回语句
        code.append(generateReturnAndInvocation(method));
    }

    return code.toString();
}

上面那段逻辑主要做了如下几件事:

  1. 检查方法上是否 Adaptive 注解修饰
  2. 为方法生成代码的时候,参数列表上要有 URL(或参数对象中有 URL)
  3. 使用 ExtensionLoader.getExtension 获取扩展
  4. 执行对应的方法

#todo javassist

#todo 打断点完善本部分的内容

扩展 - Dubbo 获取所有扩展类

我们在通过名称获取拓展类之前,首先需要根据配置文件解析出拓展项名称到拓展类的映射关系表(Map<名称, 拓展类>),之后再根据拓展项名称从映射关系表中取出相应的拓展类即可。ExtensionLoader#getExtensionClasses 的作用就是加载名称->扩展类的映射关系到缓存中。相关过程的代码分析如下:

private Map<String, Class<?>> getExtensionClasses() {
    // 从缓存中获取已加载的拓展类
    Map<String, Class<?>> classes = cachedClasses.get();
    // double check
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                // 加载扩展类
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

下面分析 ExtensionLoader#loadExtensionClasses 方法的逻辑。源代码如下:

/**
* synchronized in getExtensionClasses
*/
private Map<String, Class<?>> loadExtensionClasses() {
    // 获取SPI注解的内容, 目的是做错误校验
    cacheDefaultExtensionName();

    Map<String, Class<?>> extensionClasses = new HashMap<>();

    // 遍历全部的加载策略, 加载目录
    for (LoadingStrategy strategy : strategies) {
        loadDirectory(extensionClasses, 
                      strategy.directory(), 
                      type.getName(), 
                      strategy.preferExtensionClassLoader(), strategy.overridden(),
                      strategy.excludedPackages());
        loadDirectory(extensionClasses, 
                      strategy.directory(),
                      type.getName().replace("org.apache", "com.alibaba"), 
                      strategy.preferExtensionClassLoader(), 
                      strategy.overridden(), strategy.excludedPackages());
    }

    return extensionClasses;
}

逻辑比较简单:

  1. ExtensionLoader#cacheDefaultExtensionName 方法的作用是检查 @SPI 注解内容,比如说 @SPI(value=“apple,food”) 时就应该抛出异常。

  2. ExtensionLoader#loadDirectory 的作用是根据 LoadingStrategy 加载目录下的配置文件

ExtensionLoader#loadDirectory 方法中调用了 ExtensionLoader#loadResource 方法,ExtensionLoader#loadResoure 的部分源代码如下:

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
                          java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
    try {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
            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;
                        int i = line.indexOf('=');
                        if (i > 0) {
                            name = line.substring(0, i).trim();
                            line = line.substring(i + 1).trim();
                        }
                        if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
                            // 加载配置文件中的一行
                            loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);
// 。。。。。。。。。。

主要逻辑是:

  1. 根据文件路径读取文件
  2. 剔除注释
  3. 提取键值对
  4. 判断包是否被排除在外
  5. 加载类

主要看 ExtensionLoader#loadClass 方法,上源代码

// 重要的两个入参 class类型,name名称
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
                       boolean overridden) throws NoSuchMethodException {
    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, overridden);
    } 
    // 如果该类是一个 Wapper 类: 该类名以 Wapper 结尾,构造函数的参数中包含其他扩展点
    else if (isWrapperClass(clazz)) {
        // 将该类缓存到 cachedWrapperClasses 中
        cacheWrapperClass(clazz);
    } else {
        // 程序进入此分支,表明 clazz 是一个普通的拓展类
        // 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
        clazz.getConstructor();
        if (StringUtils.isEmpty(name)) {
            // 如果 name 为空,则尝试从 Extension 注解中获取 name,或使用小写的类名作为 name
            name = findAnnotationName(clazz);
            if (name.length() == 0) {
                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
            }
        }
        // 切分name
        String[] names = NAME_SEPARATOR.split(name);
        if (ArrayUtils.isNotEmpty(names)) {
            // 如果类上有 Activate 注解,则使用 names 数组的第一个元素作为键,
            // 存储 name 到 Activate 注解对象的映射关系
            cacheActivateClass(clazz, names[0]);
            for (String n : names) {
                // 存储 Class 到名称的映射关系
                cacheName(clazz, n);
                // 存储名称到 Class 的映射关系
                saveInExtensionClass(extensionClasses, clazz, n, overridden);
            }
        }
    }
}

逻辑分析如下:

  1. loadClass 接收到入参 Clazz 类型。
  2. 对于 @Adaptive 修饰的类,忽略配置文件,将其加入到 @Adaptive 的缓存 cachedAdaptiveClass 中
  3. 对于 Wapper类,将其缓存到 cachedWrapperClasses 中
  4. 对于普通的扩展类,读取 name,储存两个映射关系:
    • Class 到名称的映射关系
    • 名称到 Class 的映射关系

经过如上的流程,下次调用 ExtensionLoader#getExtensionClasses 的方法时,就能直接从 cachedClasses 缓存中获取到所有的名称到类型的映射。

源码总结&TIPS

JDK的标准SPI对比dubbo的SPI

JDK SPI
JDK 标准的 SPI 会一次性加载所有的扩展实现,如果有的扩展加载很耗时,但也没用上,很浪费资源。所以只希望加载某个的实现,就不现实了

DUBBO SPI
1,对 Dubbo 进行扩展,不需要改动 Dubbo 的源码 2,延迟加载,可以一次只加载自己想要加载的扩展实现。 3,增加了对扩展点 IOC 和 AOP 的支持,一个扩展点可以直接 setter 注入其 它扩展点。 3,Dubbo 的扩展机制能很好的支持第三方 IoC 容器,默认支持 Spring Bean。