Dubbo扩展点加载机制

发布于:2025-02-11 ⋅ 阅读:(69) ⋅ 点赞:(0)

        加载机制中已经存在的一些关键注解,如@SPI、©Adaptive> ©Activateo然后介绍整个加载机制中最核心的ExtensionLoader的工作流程及实现原理。最后介绍扩展中使用的类动态编译的实 
现原理。

Java SPI

Java 5 中的服务提供商icon-default.png?t=O83Ahttps://docs.oracle.com/javase/1.5.0/docs/guide/jar/jar.html#Service%20Provider

SPI 插件扩展点使用手册

https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/spi/icon-default.png?t=O83Ahttps://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/spi/

JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现则初始化很耗时,如果没 
用上也加载,则浪费资源。

如果扩展加载失败,则连扩展的名称都蕤取不到了。比如JDK标准的ScriptEngine,通过 
getName ()获取脚本类型的名称,如果RubyScriptEngine因为所依赖的j ruby .jar不存在,导致 
RubyScriptEngine类加载失败,这个失败原因被“吃掉” 了,和Ruby对应不起来,当用户 
执行Ruby脚本时,会报不支持Ruby,而不是真正失败的原因。


增加了对扩展IoC和AOP的支持,一个扩展可以直接setter注入其他扩展。在Java SPI的使 
用示例章节(代码清单4-1 )中已经看到,java.util.ServiceLoader会一次把Printservice 
接口下的所有实现类全部初始化,用户直接调用即可oDubbo SPI只是加载配置文件中的类, 
并分成不同的种类缓存在内存中,而不会立即全部初始化,在性能上有更好的表现。

 ProtocolFilterWrapper 是 Dubbo 框架中的一个核心类,用于在服务提供者和消费者之间添加过滤器链。ProtocolFilterWrapper 通过 @Activate 注解来确定哪些过滤器适用于当前的 URL。以下是 ProtocolFilterWrapper 确定过滤器适用当前 URL 的详细过程:
1. ProtocolFilterWrapper 类
ProtocolFilterWrapper 是一个装饰器模式的实现,它包装了一个 Protocol 实例,并在其上添加了过滤器链。以下是 ProtocolFilterWrapper 的主要逻辑:

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
import com.alibaba.dubbo.rpc.Exporter;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Protocol;

public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolFilterWrapper(Protocol protocol) {
        this.protocol = protocol;
    }

    @Override
    public int getDefaultPort() {
        return protocol.getDefaultPort();
    }

    @Override
    public Exporter<T> export(Exporter<T> exporter) {
        return new InvokerDelegator<>(wrapInvoker(exporter.getInvoker()), exporter);
    }

    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) {
        return wrapInvoker(protocol.refer(type, url));
    }

    private <T> Invoker<T> wrapInvoker(Invoker<T> invoker) {
        URL url = invoker.getUrl();
        // 获取所有激活的过滤器
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(url, Constants.KEYS, Constants.DEFAULT_KEY);
        if (filters.size() == 0) {
            return invoker;
        }
        return new FilterChainWrapper(invoker, filters);
    }

        SPI 提供商可以调用 ExtensionLoader.getActivateExtension(URL, String, String) 以查找具有给定条件的所有已激活的扩展。而getActivateExtension 会间接调用 com.alibaba.dubbo.common. extension.ExtensionLoader#loadExtensionClasses

        其中 Type 是 由 ExtensionLoader.getExtensionLoader(Filter.class),决定为 Filter.L565 - 567 就是解析 Filter 的接口上@SPI注解信息.(Filter.class 也可以替换成其他的类性)com.alibaba.dubbo.common.extension.ExtensionLoader#loadDirectory 会间接调用 

com.alibaba.dubbo.common.extension.ExtensionLoader#loadClass 在此方法中会解析注解@Adaptive、@Activate

    /**
     * @param extensionClasses ExtensionLoader#cachedClasses 成员变量
     * @param resourceURL
     * @param clazz ExtensionLoader#loadResource 中 加载 Class.forName(  类全限定名 )
     * @param name  ExtensionLoader#loadResource 中 在配置文件中设置的别名
     *              上两参数请参考 com.alibaba.dubbo.common.extension.SPI
     * @throws NoSuchMethodException
     */
    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        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.");
        }
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            // 由于调用者 ExtensionLoader.loadResource 循环调用了 loadClass ,如果类上标注了 @Adaptive 注解,则该类为 Adaptive 类,并且只能有一个
            if (cachedAdaptiveClass == null) {
                cachedAdaptiveClass = clazz;
            } else if (!cachedAdaptiveClass.equals(clazz)) {
                throw new IllegalStateException("More than 1 adaptive class found: "
                        + cachedAdaptiveClass.getClass().getName()
                        + ", " + clazz.getClass().getName());
            }
        } else if (isWrapperClass(clazz)) {
            // 判断为 包装类,则维护到 ExtensionLoader.cachedWrapperClasses
            Set<Class<?>> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                wrappers = cachedWrapperClasses;
            }
            wrappers.add(clazz);
        } else {
            clazz.getConstructor();
            if (name == null || name.length() == 0) {
                // 如果没有名字则尝试扫描 @Extension 注解, Extension 注解已经弃用了
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }
            // 将首个类上@Activate 信息维护到 ExtensionLoader.cachedActivates 中
            // 将 别名 维护到 ExtensionLoader.cachedNames 中
            // 将 别名&类 维护到 ExtensionLoader#cachedClasses 中
            String[] names = NAME_SEPARATOR.split(name);
            if (names != null && names.length > 0) {
                Activate activate = clazz.getAnnotation(Activate.class);
                if (activate != null) {
                    cachedActivates.put(names[0], activate);
                }
                for (String n : names) {
                    if (!cachedNames.containsKey(clazz)) {
                        cachedNames.put(clazz, n);
                    }
                    Class<?> c = extensionClasses.get(n);
                    if (c == null) {
                        extensionClasses.put(n, clazz);
                    } else if (c != clazz) {
                        throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                    }
                }
            }
        }
    }

工作流程

  1. 框架读取SPI对应路径下的配置文件,并根据配置加载所有扩展类并缓存(不初始化)。
  2. 根据传入的名称初始化对应的扩展类。
  3. 尝试查找符合条件的包装类:包含扩展点的setter方法,
  4. 返回对应的扩展类实例。

        getAdaptiveExtension也相对独立,只有加载配置信息部分与getExtension共用了同一个方法。和获取普通扩展类一样,框架会先检查缓存中是否有已经初始化化好的Adaptive实例, 没有则调用createAdaptiveExtension重新初始化。初始化过程分为4步:和getExtension 一样先加载配置文件。

  1. 生成自适应类的代码字符串。
  2.  获取类加载器和编译器,并用编译器编译刚才生成的代码字符串。Dubbo 一共有三种 
  3. 类型的编译器实现。
  4. 返回对应的自适应类实例。

getExtension 的实现原理

        ExtensionLoader#getExtension 会调用ExtensionLoader#createExtension 方法

    /**
     *  创建扩展实例
     * @param name  别名
     * @return
     */
    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);
            }
            // 反射执行 setter 方法
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                // 检查是否有包装类
                for (Class<?> wrapperClass : wrapperClasses) {
                    // 通过反射创建包装类实例,再执行包装实例的 setter 方法, 最后更新包装类实例
                    // 这里我们能看出 包装类需要 实现 接口,并且包装类需要有一个构造函数,构造参数是接口类型
                    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);
        }
    }

getAdaptiveExtension 的实现原理

        在getAdaptiveExtension()方法中,会为扩展点接口自动生成实现类字符串,实现类主要包含以下逻辑:为接口中每个有^Adaptive注解的方法生成默实现(没有注解的方法则生成空实现),每个默认实现都会从URL中提取Adaptive参数值,并以此为依据动态加载扩展点。然后,框架会使用不同的编译器,把实现类字符串编译为自适应类并返回。

         生成完代码之后就要对代码进行编译,生成一个新的Classo Dubbo中的编译器也是一个自适应接口,但@Adaptive注解是加在实现类AdaptiveCompiler上的。这样一来AdaptiveCompiler 就会作为该自适应类的默认实现,不需要再做代码生成和编译就可以使用了。

    private Class<?> createAdaptiveExtensionClass() {
        String code = createAdaptiveExtensionClassCode();
        ClassLoader classLoader = findClassLoader();
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }

举例说明@Adpative

        经 Compiler 生成的代码

package com.alibaba.dubbo.samples.echo.refer;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }

    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
}

getActivateExtension 的实现原理

  1. 检查缓存,如果没有则初始化所有扩展类实现的集合。
  2. 遍历整个 @Activate 注解集合,根据传入的URL 匹配条件,得到所有复合激活条件的扩展类实现,再根据@Acticate 配置的before、after、order 等参数进行排序
  3. 遍历用户自定义扩展类名称,调整扩展点激活顺序。
  4. 返回所有自动激活类集合。

        如果URL的参数中传入了-default,则所有的默认@Activate都不会被激活,只有URL参数中指定的扩展点会被激活。如果传入了“-”符号开头的扩展点名,则该扩展点也不会被自动激活。例如:-xxxx,表示名字为xxxx的扩展点不会被激活。

 Extension Factory 的实现原理 

        默认实现AdaptiveExtensionFactory 上.,因为该工厂上有 ©Adaptive注解。这个默认工厂在构造方法中就获取了所有扩展类工厂并缓存起来,包括另外两个工厂。代码如下

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList();
        Iterator i$ = loader.getSupportedExtensions().iterator();

        while(i$.hasNext()) {
            String name = (String)i$.next();
            list.add(loader.getExtension(name));
        }

        this.factories = Collections.unmodifiableList(list);
    }

        AdaptiveExtensionFactory持有了所有的具体工厂实现,它的getExtension方法中只是遍历了它持有的所有工厂,最终还是调用 SPI或Spring工厂实现的getExtension方法。顺序是 SPI -> Spring

 扩展点动态编译的实现

 

        Compiler接口上含有一个SPI注解,注解的默认值是@SPI(”javassist”), 很明显,Javassist编译器将作为默认编译器。如果用户想改变默认编译器,则可以通过 <dubbo:application compiler="jdk" /> 标签进行配置。 

以上内容摘自 《深入理解 Apache Dubbo 与实战》 诣极 林琳◎著


网站公告

今日签到

点亮在社区的每一天
去签到