扩展点开发指南

本文介绍 Dubbo SPI 的原理和实现细节

1. 简介

SPI 全称 Service Provider Interface,是一种服务发现机制。SPI 的本质是在文件中配置接口实现类的全限定名,服务加载器读取配置文件并加载实现类。这样,就可以在运行时为接口动态替换实现类。正因为此特性,我们可以通过 SPI 机制轻松地为程序提供扩展功能。SPI 机制也被用在第三方框架中。例如,Dubbo 就是通过 SPI 机制来加载所有的组件的。不过,Dubbo 并没有使用 Java 原生的 SPI 机制,而是对其进行了增强,以更好地满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行扩展。在 Dubbo 中,SPI 主要有两种用法,一种是加载固定的扩展类,另一种是加载自适应的扩展类。这两种方式会在下文详细介绍。特别注意:在 Dubbo 中,基于 SPI 扩展加载的类是单例的。

1.1 加载固定的扩展类

如果让你设计,加载固定的扩展类,你会怎么做?一种常见的思路是读取指定目录下的配置文件,然后解析出全类名,通过反射机制实例化类,然后将类存储到集合中。需要的时候,直接从集合中获取即可。Dubbo 中的实现也是这样的思路。不过,在 Dubbo 中,实现的更加完善,它实现了 IOC 和 AOP 的功能。IOC 指的是,如果这个扩展类依赖其他的属性,Dubbo 会自动的将这个属性注入进来。这个功能是如何实现的?一种常见的思路是,获取这个扩展类方法的 setter,调用 setter 方法进行属性注入。那 AOP 指的是什么呢?指的是 Dubbo 可以将自己的 Wrapper 类,注入到扩展类中。比如,DubboProtocol 是 Protocol 的扩展类,ProtocolListenerWrapper 是 DubboProtocol 的 Wrapper 类。

1.2 加载自适应的扩展类

先说明一下,自适应扩展类的使用场景。比如,我们有一个需求,在调用某个方法的时候,可以根据参数选择调用不同的实现类。有点类似工厂方法,根据不同的参数构造不同的实例对象。Dubbo 中的实现思路也和这个类似,不过 Dubbo 的实现更加灵活,它的实现有点类似策略模式。每一个扩展类就相当于一个策略。基于 URL 消息总线,将参数传递给 ExtensionLoader,ExtensionLoader 根据参数加载对应的扩展类,从而实现运行时动态调用目标实例。

2. Dubbo SPI 源码分析

2.1 加载固定的扩展类

在 Dubbo 中,SPI 加载固定的扩展类的入口是 ExtensionLoader 的 getExtension 方法。接下来,我们就来详细分析获取扩展类对象的流程。

public T getExtension(String name) {
    if (name == null || name. length() == 0)
        throw new IllegalArgumentException("Extension name == null");
    if ("true".equals(name)) {
        // Get the default extension implementation class
        return getDefaultExtension();
    }
    // Holder, as the name suggests, is used to hold the target object
    Holder<Object> holder = cachedInstances. get(name);
    // This logic ensures that only one thread can create a Holder object
    if (holder == null) {
        cachedInstances. putIfAbsent(name, new Holder<Object>());
        holder = cachedInstances. get(name);
    }
    Object instance = holder. get();
    // double check
    if (instance == null) {
        synchronized (holder) {
            instance = holder. get();
            if (instance == null) {
                // create extension instance
                instance = createExtension(name);
                // set the instance to the holder
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

上面代码的逻辑比较简单,先检查缓存,如果缓存未命中,则创建扩展对象。我们再来看一下创建扩展对象的流程。

private T createExtension(String name, boolean wrap) {
    // Load all extension classes from the configuration file to get the mapping relationship table from "configuration item name" to "configuration class"
    Class<?> clazz = getExtensionClasses().get(name);
    // If there is no extension of the interface, or the implementation class of the interface does not allow repetition but actually repeats, an exception is thrown directly
    if (clazz == null || unacceptableExceptions. contains(name)) {
        throw findException(name);
    }
    try {
        T instance = (T) EXTENSION_INSTANCES. get(clazz);
        // This code ensures that the extended class will only be constructed once, which is a singleton.
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance());
            instance = (T) EXTENSION_INSTANCES. get(clazz);
        }
        // Inject dependencies into the instance
        injectExtension(instance);

        // Automatically wrap if wrapping is enabled.
        // For example, I defined the extension of DubboProtocol based on Protocol, but in fact, DubboProtocol is not directly used in Dubbo, but its wrapper class
        // ProtocolListenerWrapper
        if (wrap) {

            List<Class<?>> wrapperClassesList = new ArrayList<>();
            if (cachedWrapperClasses != null) {
                wrapperClassesList.addAll(cachedWrapperClasses);
                wrapperClassesList.sort(WrapperComparator. COMPARATOR);
                Collections. reverse(wrapperClassesList);
            }
    
            // Loop to create Wrapper instances
            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))) {
                        // Pass the current instance as a parameter to the constructor of Wrapper, and create a Wrapper instance through reflection.
                        // Then inject dependencies into the Wrapper instance, and finally assign the Wrapper instance to the instance variable again
                        instance = injectExtension((T) wrapperClass. getConstructor(type). newInstance(instance));
                    }
                }
            }
        }
        // initialization
        initExtension(instance);
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                type + ") couldn't be instantiated: " + t. getMessage(), t);
    }
}

createExtension 方法的逻辑稍微复杂一点,主要包含以下几个步骤

  1. 通过 getExtensionClasses 获取所有的扩展类
  2. 通过反射创建扩展对象
  3. 向扩展对象中注入依赖
  4. 将扩展对象封装到对应的 Wrapper 对象中
  5. 初始化扩展对象

在上面的步骤中,第一步是加载扩展类的关键,第三、四步是 Dubbo IOC 和 AOP 的具体实现。在接下来的章节中,我们会重点分析 getExtensionClasses 方法的逻辑,并简单介绍 Dubbo IOC 的具体实现。

2.1.1 获取所有的扩展类

在我们根据名称获取扩展类之前,首先需要根据配置文件解析出扩展项名称到扩展类的映射关系表(Map<name, extension class>),然后根据扩展项名称从映射关系表中提取出对应的扩展类即可。相关流程的代码分析如下

private Map<String, Class<?>> getExtensionClasses() {
    // Get the loaded extension class from the cache
    Map<String, Class<?>> classes = cachedClasses. get();
    // double check
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses. get();
            if (classes == null) {
                // load extension class
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

这里也是先检查缓存,如果缓存未命中,则通过 synchronized 加锁。加锁之后,再次检查缓存,发现为空。此时,如果 classes 仍然为空,则通过 loadExtensionClasses 加载扩展类。我们来分析一下 loadExtensionClasses 方法的逻辑。

private Map<String, Class<?>> loadExtensionClasses() {
    // Cache the default SPI extension
    cacheDefaultExtensionName();

    Map<String, Class<?>> extensionClasses = new HashMap<>();
    
    // Load the files in the specified folder based on the policy
    // Currently there are four strategies to read the configuration files in META-INF/services/ META-INF/dubbo/ META-INF/dubbo/internal/ META-INF/dubbo/external/ respectively
    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;
}

loadExtensionClasses 方法总共做了两件事,一件是解析 SPI 注解,另一件是调用 loadDirectory 方法加载指定文件夹下的配置文件。SPI 注解解析的过程比较简单,这里就不再赘述,我们重点看一下 loadDirectory 做了什么。

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                           boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
    // fileName = folder path + type fully qualified name
    String fileName = dir + type;
    try {
        Enumeration<java.net. URL> urls = null;
        ClassLoader classLoader = findClassLoader();

        // try to load from ExtensionLoader's ClassLoader first
        if (extensionLoaderClassLoaderFirst) {
            ClassLoader extensionLoaderClassLoader = ExtensionLoader. class. getClassLoader();
            if (ClassLoader. getSystemClassLoader() != extensionLoaderClassLoader) {
                urls = extensionLoaderClassLoader. getResources(fileName);
            }
        }
        // Load all files with the same name according to the file name
        if (urls == null || !urls.hasMoreElements()) {
            if (classLoader != null) {
                urls = classLoader. getResources(fileName);
            } else {
                urls = ClassLoader. getSystemResources(fileName);
            }
        }

        if (urls != null) {
            while (urls. hasMoreElements()) {
                java.net.URL resourceURL = urls.nextElement();
                // load resources
                loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
            }
        }
    } catch (Throwable t) {
        logger.error("Exception occurred when loading extension class (interface: " +
                type + ", description file: " + fileName + ").", t);
    }
}

loadDirectory 方法首先通过 classLoader 获取所有资源链接,然后通过 loadResource 方法加载资源。我们继续往下看,看一下 loadResource 方法的实现。

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;
            String clazz = null;
            // Read configuration content line by line
            while ((line = reader. readLine()) != null) {
                // locate # characters
                final int ci = line. indexOf('#');
                if (ci >= 0) {
                    // Intercept the string before #, the content after # is a comment, which needs to be ignored
                    line = line. substring(0, ci);
                }
                line = line. trim();
                if (line. length() > 0) {
                    try {
                        String name = null;
                        // Use the equal sign = as the boundary to intercept the key and value
                        int i = line. indexOf('=');
                        if (i > 0) {
                            name = line.substring(0, i).trim();
                            clazz = line.substring(i + 1).trim();
                        } else {
                            clazz = line;
                        }
                        // Load the class and cache the class through the loadClass method
                        if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages)) {
                            loadClass(extensionClasses, resourceURL, Class. forName(clazz, true, classLoader), name, overridden);
                        }
                    } 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);
    }
}

loadResource 方法用于读取和解析配置文件,通过反射加载类,最后调用 loadClass 方法进行其他操作。loadClass 方法主要用于操作缓存,该方法的逻辑如下

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.");
    }
    // Check if there are Adaptive annotations on the target class
    if (clazz. isAnnotationPresent(Adaptive. class)) {
        cacheAdaptiveClass(clazz, overridden);
    } else if (isWrapperClass(clazz)) {
        // cache wrapper class
        cacheWrapperClass(clazz);
    } else {
        // Enter here, indicating that this class is just an ordinary extension class
        // Check if clazz has a default constructor, if not, throw an exception
        clazz. getConstructor();
        if (StringUtils. isEmpty(name)) {
            // If name is empty, try to get name from Extension annotation, or use lowercase class name as 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)) {
            // If the class has the Activate annotation, use the first element of the names array as the key,
            // Store the mapping relationship between name and Activate annotation object
            cacheActivateClass(clazz, names[0]);
            for (String n : names) {
                // Store the mapping relationship from Class to name
                cacheName(clazz, n);
                // Store the mapping relationship from name to Class.
                // If there are multiple implementation classes corresponding to the same extension, whether overriding is allowed based on the override parameter, if not, an exception is thrown.
                saveInExtensionClass(extensionClasses, clazz, n, overridden);
            }
        }
    }
}

如上所示,loadClass 方法操作不同的缓存,比如 cachedAdaptiveClass、cachedWrapperClasses 和 cachedNames 等,除此之外,该方法中就没有其他逻辑了。

至此,缓存类加载的流程分析完毕。整个流程中并没有什么特别复杂的地方,大家可以一步步分析,实在不理解的地方可以断点调试一下。接下来,我们聊聊 Dubbo IOC。

2.1.2 Dubbo IOC

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

private T injectExtension(T instance) {

    if (objectFactory == null) {
        return instance;
    }

    try {
        // traverse all methods of the target class
        for (Method method : instance. getClass(). getMethods()) {
            // Check whether the method starts with set, and the method has only one parameter, and the method access level is public
            if (!isSetter(method)) {
                continue;
            }
            /**
             * Detect whether there is DisableInject annotation modification.
             */
            if (method. getAnnotation(DisableInject. class) != null) {
                continue;
            }
            
            /**
             * Detect whether the ScopeModelAware and ExtensionAccessorAware classes are implemented, and if implemented, do not inject
             */
            if (method. getDeclaringClass() == ScopeModelAware. class) {
                    continue;
            }
            if (instance instanceof ScopeModelAware || instance instanceof ExtensionAccessorAware) {
                if (ignoredInjectMethodsDesc. contains(ReflectUtils. getDesc(method))) {
                    continue;
                }
            }
            
            // Primitive types are not injected
            Class<?> pt = method. getParameterTypes()[0];
            if (ReflectUtils. isPrimitives(pt)) {
                continue;
            }

            try {
                // Get the attribute name, for example, the setName method corresponds to the attribute name name
                String property = getSetterProperty(method);
                // Get dependent objects from ObjectFactory
                Object object = objectFactory. getExtension(pt, property);
                if (object != null) {
                    // inject
                    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 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory,前者用于创建自适应扩展,后者用于从 Spring 的 IOC 容器中获取所需的扩展。这两个类的代码也不是很复杂,这里就不再一一分析了。

Dubbo IOC 目前只支持 setter 注入,总体来说,逻辑比较简单,容易理解。

2.2 加载自适应扩展类

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

加载自适应扩展类的入口方法为 ExtensionLoader 的 getAdaptiveExtension 方法。

public T getAdaptiveExtension() {
    // Get the adaptive extension from the cache
    Object instance = cachedAdaptiveInstance. get();
    if (instance == null) {
        // If there is an exception, throw it directly
        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 {
                    // create adaptive extension
                    // There are two cases here: one is that there is an Adaptive class, and the other is that an Adaptive class needs to be generated
                    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 方法的代码。

private T createAdaptiveExtension() {
    try {
        // Get the adaptive extension class and instantiate it through reflection
        return injectExtension((T) getAdaptiveExtensionClass(). newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can not create adaptive extension ...");
    }
}

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

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

前两个逻辑比较容易理解,第三个逻辑是为自适应扩展对象注入依赖。这个逻辑看起来有点多余,但是它是必须存在的,这里简单解释一下。前面提到,Dubbo 中的自适应扩展有两种,一种是手动编码实现的,另一种是自动生成的。手动编码实现的 Adaptive 扩展可能会有一些依赖,而自动生成的 Adaptive 扩展则不会依赖其他类。这里调用 injectExtension 方法的目的就是为手动编码实现的 Adaptive 扩展注入依赖,这一点需要大家注意。关于 injectExtension 方法,在前面的文章中已经分析过了,这里就不再赘述了。接下来,分析 getAdaptiveExtensionClass 方法的逻辑。

private Class<?> getAdaptiveExtensionClass() {
    // Get all extension classes through SPI
    getExtensionClasses();
    // Check the cache, if the cache is not empty, return the cache directly
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    // Create an adaptive extension class
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

getAdaptiveExtensionClass 方法也包含了三个逻辑,分别是

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

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

private Class<?> createAdaptiveExtensionClass() {
    // Build adaptive extension code
    String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
    ClassLoader classLoader = findClassLoader();
    // Get the compiler implementation class
    org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    // Compile the code and generate Class
    return compiler.compile(code, classLoader);
}

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

2.2.1 自适应扩展类代码生成

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

public String generate() {
    // If there is no method in the interface modified by the @Adaptive annotation, an exception is thrown directly
    if (!hasAdaptiveMethod()) {
        throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
    }

    StringBuilder code = new StringBuilder();
    // Generate package name, import, method, etc.
    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();
}

2.2.2 生成方法

在上面的代码中,生成方法的逻辑最为关键,我们来详细分析一下。

private String generateMethod(Method method) {
    String methodReturnType = method. getReturnType(). getCanonicalName();
    String methodName = method. getName();
    // generate method content
    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) {
    // This method must be decorated with @Adaptive annotation
    Adaptive adaptiveAnnotation = method. getAnnotation(Adaptive. class);
    StringBuilder code = new StringBuilder(512);
    if (adaptiveAnnotation == null) {
        // Without @Adaptive annotation modification, exception information is generated
        return generateUnsupported(method);
    } else {
        // Get the index of the URL on the parameter list
        int urlTypeIndex = getUrlTypeIndex(method);
        
        if (urlTypeIndex != -1) {
            // Generate a null check for the URL if it exists on the parameter list
            code.append(generateUrlNullCheck(urlTypeIndex));
        } else {
            // If there is no parameter of URL type in the parameter list, then it depends on whether the parameter object on the parameter list contains the getUrl method
            // If there is, generate a URL null check
            code.append(generateUrlAssignmentIndirectly(method));
        }
        // parse the value attribute on the Adaptive annotation
        String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
        // If there is a parameter of type Invocation on the parameter list, generate a null check and get the methodName.
        boolean hasInvocation = hasInvocationArgument(method);
        
        code.append(generateInvocationArgumentNullCheck(method));
        // This logic is mainly to generate extName (that is, the extension)
        // Divided into multiple situations:
        // 1. Does defaultExtName exist?
        // 2. Whether there is an invocation type parameter in the parameter
        // 3. Whether to generate a proxy for the protocol
        // Why should the protocol be considered separately? Because there is a method to get the protocol value in the URL
        code.append(generateExtNameAssignment(value, hasInvocation));
        // check extName == null?
        code.append(generateExtNameNullCheck(value));
    
        // generate get extension (using ExtensionLoader.getExtension method)
        code.append(generateExtensionAssignment());

        // generate return statement
        code.append(generateReturnAndInvocation(method));
    }

    return code. toString();
}

上面的逻辑主要做了以下几件事

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

2.2.3 附上一段动态生成的代码示例

package org.apache.dubbo.common.extension.adaptive;

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


public class HasAdaptiveExt$Adaptive implements org.apache.dubbo.common.extension.adaptive.HasAdaptiveExt {
    public java.lang.String echo(org.apache.dubbo.common.URL arg0,
        java. lang. String arg1) {
        // URL null check
        if (arg0 == null) {
            throw new IllegalArgumentException("url == null");
        }

        org.apache.dubbo.common.URL url = arg0;
        // get the extension
        String extName = url. getParameter("has. adaptive. ext", "adaptive");
        // extension null check
        if (extName == null) {
            throw new IllegalStateException(
                "Failed to get extension (org.apache.dubbo.common.extension.adaptive.HasAdaptiveExt) name from url (" +
                url.toString() + ") use keys([has.adaptive.ext])");
        }
        // get extension
        org.apache.dubbo.common.extension.adaptive.HasAdaptiveExt extension = (org.apache.dubbo.common.extension.adaptive.HasAdaptiveExt) ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.adaptive.HasAdaptiveExt.class)
                                                                                                                                                         .getExtension(extName);
        // Execute the corresponding method
        return extension.echo(arg0, arg1);
    }
}

3. SPI 扩展示例

3.1 加载固定扩展类

3.1.1 编写 SPI 接口和实现类

无论是 Java SPI 还是 Dubbo 中实现的 SPI,都需要编写接口。不过,Dubbo 中的接口需要使用 @SPI 注解修饰。

@SPI
public interface DemoSpi {
    void say();
}

public class DemoSpiImpl implements DemoSpi {
    public void say() {
    }
}

3.1.2 将实现类放到特定目录下

从上面的代码中,我们可以看到,dubbo 在加载扩展类的时候,会从四个目录下读取。我们在 META-INF/dubbo 目录下新建一个以 DemoSpi 接口命名的文件,内容如下

demoSpiImpl = com.xxx.xxx.DemoSpiImpl (full class name for the implementation class of the DemoSpi interface)

3.1.3 使用

public class DubboSPITest {

    @Test
    public void sayHello() throws Exception {
        ExtensionLoader<DemoSpi> extensionLoader =
            ExtensionLoader. getExtensionLoader(DemoSpi. class);
        DemoSpi dmeoSpi = extensionLoader. getExtension("demoSpiImpl");
        optimusPrime. sayHello();
    }
}

3.2 加载自适应扩展类

这里以 Protocol 为例进行说明

3.2.1 Protocol 接口(摘取部分核心方法)

@SPI("dubbo")
public interface Protocol {
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
}

public class DubboProtocol extends AbstractProtocol {
     …
    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        return protocolBindingRefer(type, url);
    }
    
    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
         …
        return exporter;
    }
}

3.2.2 将实现类放到特定目录下

在 dubbo 中,配置路径为 META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol

dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol

需要注意的是,在 dubbo 中,并没有直接使用 DubboProtocol,而是使用了它的包装类。

3.2.3 使用

public class DubboAdaptiveTest {

    @Test
    public void sayHello() throws Exception {
        URL url = URL.valueOf("dubbo://localhost/test");
        Protocol adaptiveProtocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
        adaptiveProtocol. refer(type, url);
    }
}

上次修改时间:2023 年 1 月 2 日: 增强英文文档 (#1798) (95a9f4f6c1c)