Dubbo源码分析 - Dubbo SPI

本文深入剖析Dubbo的SPI机制,对比JDK SPI,讲解Dubbo SPI的优化和增强,包括选择性实例化、错误处理改进、IoC和AOP支持,以及多维度缓存策略。通过实例演示Dubbo SPI的配置和使用,分析核心源码,揭示ExtensionLoader的工作流程。

概述

dubbo并未使用jdk标准的SPI机制,而是对其进行了增强,优化了性能问题并且相比jdk spi更加健壮。

dubbo spi 对 jdk spi的改进

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源,而dubbo可以选择性实例化。
  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时会报不支持ruby,而不是真正失败的原因。
  • 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
  • 原始jdk spi 不支持缓存,dubbo设计了多维度缓存,提高了框架的性能。

dubbo spi 配置规范

  • spi配置文件路径
META-INF/dubbo/internal : 主要用于 Dubbo 内部提供的拓展点实现
META-INF/dubbo : 主要用于自定义扩展点实现
META-INF/services : 用于兼容jdk的spi

说明: 上面的spi配置文件路径是种规范,实际上在使用的时候写在哪个文件下都可以被加载到,但是实际开放种最好按照规范配置。

  • spi配置文件名称
扩展点全路径名
  • 文件内容
key=value形式,多个使用换行符分割,这是dubbo配置的方式
value形式,没有指定扩展名,这是jdk配置方式,dubbo进行了兼容,会自动为扩展实现类生成默认的扩展名

说明: dubbo spi 通过键值对的方式进行配置,这样我们可以按需实例化扩展点的实现,而不是一次实例化所有的扩展实现类。

  • 加载扩展实现
dubbo使用ExtensionLoader加载指定实现类,dubbo spi的逻辑几乎都封装在该类中。

示例

前面简单介绍了dubbo spi机制,下面我们通过一个例子来演示dubbo spi的简单用法。扩展点接口及实现复用 spi机制之jdk示例 中代码,区别是dubbo spi的接口使用@SPI注解进行标注。

定义扩展接口,使用@SPI注解进行标注

package com.alibaba.dubbo.spi;

@SPI
public interface Command {
    /**
     * 执行方法
     */
    void execute();
} 

在META-INF/dubbo文件目录下创建一个文件,名称为Command的全路径名 com.alibaba.dubbo.spi.Command。 配置内容为扩展实现类及其扩展名,如下:

start=com.alibaba.dubbo.spi.impl.StartCommand
shutdown=com.alibaba.dubbo.spi.impl.ShutdownCommand

准备就绪后,最后写测试代码,如下:

public class Main {
    public static void main(String[] args) {
       
        // ExtensionLoader是dubbo提供的,用来加载拓展实现类
       ExtensionLoader<Command> extensionLoader = ExtensionLoader.getExtensionLoader(Command.class);
       
       // 加载指定扩展名对应的扩展实现对象(获取的时候会进行实例化)
       Command startCommand = extensionLoader.getExtension("start");
       startCommand.execute();

       Command shutdownCommand = extensionLoader.getExtension("shutdown");
       shutdownCommand.execute();
       
    }
}

测试结果如下:

start command
shut down command

Process finished with exit code 0

简单说明和演示dubbo spi后,我们对dubbo spi有了一定的认识,使用起来还是比较简单的,接下来进入源码分析阶段,让我们一起去看看dubbo底层是怎么加载和选择扩展实现的。

dubbo spi 源码分析

进行源码分析之前,我们先看下dubbo spi整体的代码结构,然后对核心注解和类进行说明。

代码结构

扩展点 SPI 注解

扩展点接口标识,dubbo的扩展点必须标注该注解,否则在执行spi逻辑时框架会报异常。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {

    /**
     * @return 缺省扩展名
     */
    String value() default "";

}

SPI注解的value属性是用来指定扩展点的默认扩展名,如Protocol扩展接口:

@SPI("dubbo")
public interface Protocol {//...}  // dubbo对应的扩展实现类就是DubboProtocol,即Protocol默认的扩展实现类
扩展点 Adaptive 注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    /**
     * 根据URL的Key获取对应的Value作为自适应拓展名。比如,<code>String[] {"key1", "key2"}</code>,表示
     * <ul>
     *   <li>先在URL上找key1的Value作为自适应拓展名;
     *   <li>key1没有value,则使用key2的value作为自适应拓展名。
     *   <li>key2没有value,就使用缺省的扩展,即: 如果{@link URL}这些Key都没有value,使用缺省的扩展(在接口的{@link SPI}中设定的值)
     *   <li>如果没有设置缺省扩展名或者缺省扩展名也没有value,则方法调用会抛出{@link IllegalStateException}。
     * </ul>
     * 注意:如果没有使用Adaptive注解指定扩展名,扩展接口也没有指定@SPI默认值,则在加载扩展实现的时候dubbo会自动把扩展接口名称根据驼峰大小写分开,并使用 '.' 符号连接起来,
     *      以此名称作为默认扩展名。如:SimpleExt -> simple.ext
     * 
     * @return parameter key names in URL
     */
    String[] value() default {};
}

一个拓展接口,在框架中同时只能存在一个 Adaptive 拓展实现类,可能是固定的扩展实现类,也可能是自动生成、编译得到的扩展实现类。@Adaptive 注解,可添加类或方法上,分别代表了两种不同的使用方式。第一种,标记在类上(属于装饰类),整个实现类会作为自适应扩展类,dubbo不会为该类生成代理类,它主要用于固定已知类。目前 Dubbo 项目里,只有 ExtensionFactory 拓展的实现类 AdaptiveExtensionFactory 和Compiler 拓展的实现AdaptiveCompiler这么使用。第二种,标记在扩展接口的方法上,代表自动生成、编译一个该接口的动态Adaptive拓展实现类(属于动态代理类,如Protocol$Adaptive),扩展的加载逻辑由框架自动生成,这依赖dubbo的URL中的参数。

扩展点 Activate 注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
    /**
     * group过滤条件。在调用{@link ExtensionLoader#getActivateExtension(URL, String, String)} 方法时,如果传入的group参数符合该注解设置的group属性值,则匹配。
     */
    String[] group() default {};

    /**
     * key过滤条件。在调用{@link ExtensionLoader#getActivateExtension(URL, String, String)} 方法时,如果url中的参数中存在该注解设置的key值,则匹配。
     */
    String[] value() default {};

    /**
     * 排序属性
     *
     * @return extension list which should be put before the current one
     */
    String[] before() default {};

    /**
     * 排序属性
     *
     * @return extension list which should be put after the current one
     */
    String[] after() default {};

    /**
     * 排序属性
     *
     * @return absolute ordering info
     */
    int order() default 0;
}

该注解用于设置扩展实现类被自动激活的加载条件,如:过滤器扩展点有多个实现,那么就可以使用该注解设置激活条件,在获取自动激活扩展实现时需要符合条件才能获取到。框架通过ExtensionLoader#getActivateExtension方法获得激活条件的扩展实现集合。

ExtensionLoader

dubbo的扩展加载器,dubbo spi 的相关逻辑几乎都被封装在该类中,该类是 dubbo spi 的 核心

public class ExtensionLoader<T> {

    //========================================= 类属性,所有ExtensionLoader对象共享 ================================================

    /**
     * dubbo扩展点目录 ,该目录是为了兼容jdk的spi
     */
    private static final String SERVICES_DIRECTORY = "META-INF/services/";
    /**
     * dubbo扩展点目录,主要用于自定义扩展点实现
     */
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
    /**
     * dubbo扩展点目录,用于 Dubbo 内部提供的拓展点实现
     */
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

    /**
     * 扩展点实现名的分隔符 正则表达式,多个扩展点名之间使用 ',' 进行分割
     */
    private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");


    /**
     * 扩展点加载器集合
     * key: 拓展点接口
     * value: 扩展点加载器。 一个扩展点接口对应一个 扩展点加载器
     */
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

    /**
     * 扩展点实现类集合
     * key: 扩展点实现类
     * value: 扩展点实现对象
     * 说明:
     * 一个扩展点通过对应的ExtensionLoader去加载它的具体实现,考虑到性能和资源问题,在加载拓展配置后不会立马进行扩展实现的对象的初始化,而是先把扩展配置存起来。
     * 等到真正使用对应的拓展实现时才进行扩展实现的对象的初始化,初始化后也进行缓存。即:
     * 1 缓存加载的拓展配置
     * 2 缓存创建的拓展实现对象
     */
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();


    // ==============================  实例属性 ,每个ExtensionLoader对象独有 ====================================================

    /**
     * 扩展点,如:Protocol
     */
    private final Class<?> type;

    /**
     * 扩展点实现工厂,用于向扩展对象中注入依赖属性,一般通过调用 {@link #injectExtension(Object)} 方法进行实现。
     * 特别说明:
     *  除了ExtensionFactory扩展接口,其余的所有扩展接口的ExtensionLoader对象都会拥有一个自己的扩展工厂,即 objectFactory = AdaptiveExtensionFactory;
     * @see ExtensionLoader 构造方法
     */
    private final ExtensionFactory objectFactory;

    /**
     * 扩展点实现类 到 扩展名 的映射
     * 如:
     * dubbo=dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol ===> <DubboProtocol,dubbo>
     */
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();

    /**
     * 扩展名 到 扩展点实现类 的映射
     * 不包括以下两种类型:
     * 1 自适应扩展实现类,如:AdaptiveExtensionFactory
     * 2 扩展点的Wrapper实现类,如:ProtocolFilterWrapper
     * 如:
     * dubbo=dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol ===> <dubbo,DubboProtocol>
     */
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();

    /**
     * 扩展名 到 @Activate注解 的映射, 如: ContextFilter -> Activate
     */
    private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();

    /**
     * 扩展名 到 扩展点实现对象 的映射
     * 如:
     * dubbo=dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol ===> <dubbo,Holder<DubboProtocol对象>>
     */
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();

    /**
     * 自适应扩展对象
     * 注意: 一个扩展点最多只能有一个自适应扩展对象,> 1 框架就会报错
     */
    private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();

    /**
     * 自适应扩展实现类 {@link #getAdaptiveExtensionClass()}
     */
    private volatile Class<?> cachedAdaptiveClass = null;

    /**
     * 扩展点的默认扩展名,通过 {@link SPI} 注解获得
     */
    private String cachedDefaultName;

    /**
     * 创建自适应对象时发生的异常 -> {@link #createAdaptiveExtension()}
     */
    private volatile Throwable createAdaptiveInstanceError;

    /**
     * 扩展点Wrapper实现类集合,如:ProtocolFilterWrapper
     */
    private Set<Class<?>> cachedWrapperClasses;

    /**
     * 扩展名 到 加载对应扩展类发生的异常 的映射
     */
    private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();

    /**
     * 构造方法
     * 说明:
     * 1 任意一个扩展点在获取对应的ExtensionLoader时,都会先尝试获取属于它的ExtensionFactory自适应扩展,即 AdaptiveExtensionFactory,
     * 它管理着SpiExtensionFactory和SpringExtensionFactory这两大扩展点工厂,用于调用 {@link #injectExtension(Object)}方法,向扩展实现中注入依赖属性,
     * 需要注意的是,SpiExtensionFactory和SpringExtensionFactory获得对象是不同的,前者获取自适应对象,后者从Spring容器中获取对象。
     * 2 当扩展点是ExtensionFactory时,那么它的对应的ExtensionLoader的objectFactory 属性为null
     *
     * @param type 扩展点
     */
    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
   
   // 省略其它代码...

}

基于性能的考虑,dubbo spi相比较与jdk spi的一个改进就是使用了大量的缓存,dubbo spi 缓存从大的方向可分为 类缓存实例缓存,这两种缓存又能根据扩展实现类的种类分为 普通扩展缓存包装扩展缓存自适应扩展缓存

  • 类缓存

dubbo spi在查询扩展类时,会先从缓存中获取,如果缓存中不存在,再加载配置文件并分类缓存,注意,这个过程不会进行初始化流程。

  • 实例缓存

dubbo spi缓存的Class是按需进行实例化的,在查询实例时会先从缓存中获取,如果缓存不存在则会进行加载/初始化,然后缓存起来。

多类型的扩展点

根据扩展实现类的特点及用途可以分为普通扩展类、自动激活扩展类、包装扩展类以及自适应扩展类,其中自动激活扩展类属于普通扩展类。需要注意的是,除了动态编译得到的自适应扩展类,其它的所有扩展类都需要在配置文件中进行配置,否则框架无法加载到。下面我们简单说明下各个类型的扩展类及其特点。

普通扩展类

属于最基础的扩展类,一般通过扩展名获取对应的扩展实现就是该类型。在配置普通扩展类时需要指定扩展名,不指定会按规则自动生成,因为普通扩展实现都是根据扩展名获取的。

包装扩展类

包括扩展类又叫 Wrapper类,一般不是扩展点的真正实现,主要用来对扩展实现进行功能增强或通用逻辑处理。Wrapper类有两个特征:实现扩展接口存在一个参数类型是扩展点的构造方法。Wrapper类是dubbo AOP的实现。在配置Wrapper类时,可以不指定扩展名,即使指定了也不会使用,但一般情况根据dubbo spi的约定还是统一配置。

自适应类

自适应类非常灵活,也叫 Adaptive类,有两种实现方式。Adaptive类的两个特征:实现扩展接口实现类或扩展接口的方法上需要使用 @Adaptive 标注。类上标注@Adaptive是一个Adaptive类可以理解,但是扩展接口的方法上标注@Adaptive怎么会是一个类呢?是因为标注在扩展接口的方法上,dubbo spi机制在获取自适应扩展实现类时,如果当前环境中没有自适应扩展实现类就会对标注的方法所在接口进行javassist操作,生成自适应扩展类的字符串,然后通过动态编译成一个自适应类。@Adaptive标注在扩展接口的方法上的方式,可以动态地通过URL中的参数来确定使用哪个扩展实现。在配置文件中可以不指定扩展名,即使指定了也不会使用,但一般情况根据dubbo spi的约定还是统一配置。

自动激活类

自动激活类属于特殊的普通扩展类,该类的两个特征:实现接口类上使用 @Activate 标注。它支持某个扩展点需要同时激活多个实现的特性,如 dubbo中的过滤器扩展点,需要激活多个扩展实现。

ExtensionLoader 工作流程

ExtensionLoader封装了dubbo spi的主要逻辑,配置的加载、扩展类缓存、扩展实现的实例化及缓存、自适应类的生成与编译及缓存、自适应对象的实例化及缓存以及dubbo ioc和aop的实现等。这些逻辑主要体现在三个入口方法中,每个入口方法获取到的扩展实现类型会有所不同,但是方法内部逻辑有相同之处。下面我们分别从三个入口方法开始详细分析dubbo spi的整个流程,需要说明的是,getExtension方法是最核心的方法,其它两个入口方法都会依赖该方法中的部分流程,因此我们会先分析getExtension方法,在分析其它两个方法的时候涉及重复的流程就不再分析。

getExtension 方法

ExtensionLoader中最核心的方法,因为它实现了一个完整的查询扩展实现的逻辑。获取过程中的每一步都会先检查缓存是否命中,命中就直接返回或进行赋值,没有命中则加载配置文件,然后缓存配置文件中的扩展实现。

   /**
     * 获得指定扩展名的扩展对象
     *
     * @param name 扩展名
     * @return
     */
    @SuppressWarnings("unchecked")
    public T getExtension(String name) {
        if (name == null || name.length() == 0) {
            throw new IllegalArgumentException("Extension name == null");
        }

        // 如果当前扩展名是 'true',就获取默认的扩展对象
        if ("true".equals(name)) {
            // 方法简化为 getExtension(cachedDefaultName) , cacheDefaultName的值参见 @SPI注解
            return getDefaultExtension();
        }

        // 从缓存中获得对应的扩展对象
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<Object>());
            holder = cachedInstances.get(name);
        }
        Object instance = holder.get();

        // 缓存中没有, 双重检锁获取扩展名对应扩展实现对象
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    // 缓存中确实没有,就创建扩展名对应的扩展实现对象
                    instance = createExtension(name);
                    // 将扩展实现对象放入缓存中
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

上面的代码逻辑比较简单,根据扩展名获取扩展对象,先检查缓存中是否有目标对象,没有则调用 createExtension方法开始创建扩展对象。需要特被说明的是,如果name是true的情况,加载的就是默认扩展类。那么下面我们来分析createExtension方法流程。

 /**
     * 创建扩展名对应的扩展点实现对象并缓存到类属性的集合中
     *
     * @param name
     * @return
     */
    @SuppressWarnings("unchecked")
    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);
            }

            // dubbo ioc实现,进行setter注入
            injectExtension(instance);

            /**
             * dubbo aop实现
             * 注意:
             *  如果当前扩展点存在 Wrapper类,那么从ExtensionLoader 中获得的实际上是 Wrapper 类的实例,Wrapper 持有了实际的扩展点实现类,因此调用方法时调用的是Wrapper类中的方法,并非直接调用扩展点的真正实现。
             *  即 如果在Wrapper的方法中不显示调用扩展点的真正实现的话,那么结果一定不是预期的。
             */
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    // 创建 Wrapper 实例,然后进行 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);
        }
    }

createExtension(String name)方法的逻辑代码中已经详细注释说明,下面小结关键的步骤:

  1. 调用getExtensionClasses()刷新扩展点实现类集合
  2. 通过反射创建扩展点的扩展对象并放入类缓存中
  3. 使用dubbo的setter注入向扩展对象中注入依赖属性
  4. 使用扩展点的Wrapper对扩展对象实现dubbo的aop处理逻辑

通过扩展名获取扩展对象时可能不能命中缓存,此时就要创建扩展对象,创建扩展对象需要扩展实现类,下面我们看下dubbo获取扩展名到扩展实现类的映射集合。

  /**
     * 获取扩展点实现类的集合,先从缓存中获取,没有命中缓存就从配置文件中加载并分类放入缓存。
     *
     * @return
     */
    private Map<String, Class<?>> getExtensionClasses() {

        // 先从缓存中获取
        Map<String, Class<?>> classes = cachedClasses.get();

        // 双重检锁,获取扩展点的扩展实现类集合
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // 加载扩展类
                    classes = loadExtensionClasses();
                    // 将 扩展名到扩展点实现类的映射 加入到 cachedClasses 集合中,缓存起来
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

如果缓存不能命中扩展名对应的扩展实现类就只能加载配置文件刷新扩展点实现类集合,下面我们看下dubbo是如何加载配置文件的。

    private Map<String, Class<?>> loadExtensionClasses() {
        //1、 通过@SPI注解获得扩展点的默认扩展名(前提是当前拓展点需要有@SPI注解,其实程序执行到这里type一定是有@SPI注解的,因为在获取扩展点的扩展加载器的时候已经判断了)
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);

        //1.1 如果扩展点的@SPI注解设置了默认值
        if (defaultAnnotation != null) {

            // @SPI注解的值就是扩展点的默认扩展名
            String value = defaultAnnotation.value();

            if ((value = value.trim()).length() > 0) {

                // 对默认扩展名进行分隔处理,以逗号分隔为字符串数组
                String[] names = NAME_SEPARATOR.split(value);

                // 检测 SPI 注解内容是否合法,不合法则抛出异常
                if (names.length > 1) {
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }

                /** 设置默认名称,cachedDefaultName 是用来加载扩展点的默认实现 {@link #getDefaultExtension()} */
                if (names.length == 1) {
                    cachedDefaultName = names[0];
                }

            }
        }

        //2、 从配置文件中加载拓展实现类集合,这里分别对应三类文件(1. Dubbo内置的 2. Dubbo自定义 3. JDK SPI)
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadDirectory(extensionClasses, DUBBO_DIRECTORY);
        loadDirectory(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }

我们可以看到该方法没有太多的逻辑,主要处理扩展点的默认扩展名,如果存在的化就放入缓存中,具体加载配置文件的逻辑由loadDirectory方法实现。 需要注意的是,唯一调用该方法的入口 {@link #getExtensionClasses()} 已经加过了锁,因此此处无需再次加锁。接下来继续分析dubbo如何加载配置文件。

    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {

        // 拼接完整的文件名(相对路径): 目录 + type全类名
        String fileName = dir + type.getName();

        try {

            Enumeration<java.net.URL> urls;

            // 类加载器
            ClassLoader classLoader = findClassLoader();

            /** 获得文件名对应的所有文件数组(可能同一个文件名在不同的目录结构中,这样就会获取多个文件),每个文件内容封装到一个java.net.URL中*/
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }

            // 遍历java.net.URL集合
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    // 加载java.net.URL
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }

        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

通过上面代码可以看出,loadDirectory方法主要就做一件事,加载配置文件并将每个配置文件内容封装到java.net.URL集合中,接下来在loadResource方法中就可以从该URL中依次解析扩展名和扩展实现类。

 /**
     * 加载配置文件内容(已经封装成了java.net.URL)
     *
     * @param extensionClasses 扩展类集合
     * @param classLoader      类加载器
     * @param resourceURL      文件内容资源
     */
    private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        try {

            // 读取文件内容
            BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));

            try {

                String line;

                // 一行一行的读取。会跳过当前被注释掉行,例如:#dubbo=xxx
                while ((line = reader.readLine()) != null) {

                    // 如果有#注释,那么ci为0,没有就为-1
                    final int ci = line.indexOf('#');

                    // 在有#注释的情况下,此时line的长度为0
                    if (ci >= 0) {
                        line = line.substring(0, ci);
                    }

                    // 去除前后端空格,防止自定义扩展点实现时配置不规范
                    line = line.trim();

                    // 没有#注释的情况
                    if (line.length() > 0) {
                        try {

                            /**
                             * 拆分 key=value ,name为拓展名 line为拓展实现类名。注意:
                             * 1 这里name可能为空,这种情况扩展名会自动生成(因为Dubbo SPI兼容Java SPI,Dubbo SPI配置强调key=value格式,应该尽可能遵守规则)
                             * 2 扩展名只对普通扩展才有意义,对自适应扩展、Wrapper是没用的,之所以要配置,是为了统一dubbo spi配置规则
                             */

                            String name = null;

                            // i > 0,有扩展名; i < 0 没有配置扩展名,即兼容Java SPI
                            int i = line.indexOf('=');

                            if (i > 0) {
                                /** 获取 = 左边的key 即扩展名 */
                                name = line.substring(0, i).trim();
                                /** 获取 = 右边的value 即拓展点的实现的全限定性类名 */
                                line = line.substring(i + 1).trim();
                            }

                            // 加载当前行对应的扩展点配置
                            if (line.length() > 0) {
                                /**
                                 * 1 通过反射,根据名称获取扩展点实现类
                                 * 2 对扩展实现类进行分类缓存
                                 */
                                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);
                        }
                    }
                }

            } finally {
                reader.close();
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }

loadResource方法用于将配置文件中的每行记录读取出来,经过解析和反射处理就能拿到扩展名和对应的扩展实现类,扩展名和扩展实现类的获取逻辑已经在代码中详细注释。最后调用loadClass方法进行分类缓存,这些缓存很多,我们来看下dubbo是如何处理实例缓存的分类的。

    /**
     * 对扩展点实现类进行分类缓存
     *
     * @param extensionClasses 扩展实现类集合
     * @param resourceURL      文件内容资源
     * @param clazz            扩展点实现类
     * @param name             扩展名  【只对普通扩展才有意义】
     * @throws NoSuchMethodException
     */
    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        // 判断拓展点实现类,是否实现了当前type接口,没有实现就会报错
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Error when load extension class(interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + "is not subtype of interface.");
        }

        //-------------------------------------- 根据扩展点实现类的类型可分为三大类 ,在进行分类缓存中有优先级,即同一个实现类只能归属到某个分类中 --------------------------------/

        /**
         * 1、自适应扩展类
         * 说明:
         * (1)当前扩展点实现类是否标注@Adaptive注解,标记的话就是自适应扩展类,直接缓存到 cachedAdaptiveClass 属性中,然后结束逻辑,即不会进行下面的 Wrapper、普通扩展类以及自动激活类逻辑判断。
         * (2)自适应固定扩展实现类其实不需要配置扩展名,即使配置了也用不到,因为自适应扩展类和自适应扩展对象整个转换闭环都用不到扩展名。之所以配置,是为了统一规则。
         */
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            // 一个扩展点有且仅允许一个自适应扩展实现类,如果符合条件就加入到缓存中,否则抛出异常
            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());
            }

            /**
             *  2、Wrapper类型 (该类需要有有一个参数的构造方法,且这个参数类型是当前的扩展点type)
             *  说明:
             *  (1)当前扩展点实现类如果是Wrapper类,直接缓存到 cachedWrapperClasses 属性集合中,然后结束逻辑,即不会进行下面的 普通扩展类以及自动激活类逻辑判断。
             *  (2)Wrapper类其实不需要配置扩展名,即使配置了也用不到。之所以配置,是为了统一规则。
             */
        } else if (isWrapperClass(clazz)) {
            Set<Class<?>> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                wrappers = cachedWrapperClasses;
            }
            wrappers.add(clazz);


            /**
             * 3、普通的扩展实现类,注意Activate自动激活类从大的方面也属于普通的扩展实现类
             */
        } else {

            // 判断是否有默认的构造方法,没有会抛出异常
            clazz.getConstructor();

            // 未配置扩展名,则自动生成。适用于Java SPI的配置方式(Dubbo SPI 兼容Java SPI) 例如: xxx.yyy.DemoFilter生成的拓展名为demo
            if (name == null || name.length() == 0) {
                // 自动生成扩展名
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }

            // 对扩展名进行分割处理,dubbo支持配置多个扩展名。如果配置多个扩展名需要以','分割
            String[] names = NAME_SEPARATOR.split(name);
            if (names != null && names.length > 0) {

                // 3.1、 如果当前类标注了@Activate,就缓存到 cachedActivates集合。需要注意的是,即使扩展点配置多个,cachedActivates 的key 只取第一个。
                Activate activate = clazz.getAnnotation(Activate.class);
                if (activate != null) {
                    // 拓展名与 @Activate的映射
                    cachedActivates.put(names[0], activate);
                }

                /**
                 * 3.2、缓存当前扩展点分类到 cachedNames 集合 和 cachedClasses 集合
                 * 说明:
                 *  (1)cachedNames 缓存集合中的数据特点:同一个扩展点实现类对应的扩展名即使在配置多个扩展名的情况下也只取第一个
                 *  (2)cachedClasses 缓存集合的数据特点:同一个扩展点实现类对应的扩展名可能存在多个
                 */
                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());
                    }
                }
            }
        }
    }

通过上面的代码可知,loadClass方法主要就是分类缓存不同扩展实现类,这个过程不涉及扩展实现类的实例化,这也验证了前面的结论,dubbo spi是按需实例化对象。到这里getExtension方法主要过就分析完了,前面也说到该方法是加载扩展实现的完整逻辑,其它的两个入口中的逻辑也会使用上面过程中的部分逻辑,在下面的代码分析中我们可以看到。

getActivateExtension 方法

该方法只是根据不同的条件同时激活多个普通扩展实现类,即会做一些通用的判断来筛选是否是激活扩展扩展对象。前面多次提到该法会依赖getExtension方法中的逻辑,下面我就一起来看看。

/**
     * 获得激活条件的扩展实现对象集合
     *
     * @param url    url
     * @param values 激活的扩展名数组,可能为空。如:获取dubbo内置的过滤器时,key=service.filter,url中没有对应的值
     * @param group  过滤分组名
     * @return 被激活的扩展实现对象集合
     * @see com.alibaba.dubbo.common.extension.Activate
     */
    public List<T> getActivateExtension(URL url, String[] values, String group) {

        // 激活扩展实现对象结果集
        List<T> exts = new ArrayList<T>();

        // 激活的扩展名集合
        List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);

        //  判断扩展名集合中是否有 '-default' , 如: <dubbo:service filter="-default"/> 代表移出所有默认的过滤器。注意,names是个空的List是符合条件的
        if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {

            // 获取/刷新 扩展点实现类的集合
            getExtensionClasses();

            /**
             * 遍历cachedActivates (拓展名 到 @Activate 的映射)
             * 1 匹配分组,匹配成功则继续逻辑,否则不处理 加载配置文件时收集到的激活扩展类
             * 2 对激活扩展类进行实例化[初次才会,以后就从缓存中取]
             * 3 判断当前缓存中的激活扩展类是否和传入的激活扩展类冲突,如果有冲突,就忽略缓存中的激活扩展类,以传入的扩展类为主
             */
            for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {

                // 扩展名
                String name = entry.getKey();
                // Activate
                Activate activate = entry.getValue();

                // 匹配分组,判断Activate注解的group属性值是否包含当前传入的group,包含就符合分组条件
                if (isMatchGroup(group, activate.group())) {

                    // 获取扩展名对应的扩展点实现对象
                    T ext = getExtension(name);

                    // 是否忽略 加载配置文件时收集到的激活扩展类
                    if (!names.contains(name)
                            && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)
                            && isActive(activate, url)) {

                        exts.add(ext);
                    }
                }
            }

            // 对扩展对象进行排序(根据注解的before、after、order属性)
            Collections.sort(exts, ActivateComparator.COMPARATOR);
        }

        List<T> usrs = new ArrayList<T>();

        // 遍历传入的激活扩展名集合
        for (int i = 0; i < names.size(); i++) {

            // 获取激活扩展名
            String name = names.get(i);

            // 判断是否是 移除激活扩展名,如果是就忽略。 如: <dubbo:service filter="-demo"/>,那么此时demo对应的扩展实现就是属于无效的
            if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX) && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {

                // 处理 自定义的激活扩展配置在默认的激活扩展前面的情况, 如: <dubbo:service filter="demo,default"/>,那么自定义的demo激活扩展就优先默认的激活扩展。主要是exts中的值变化,前面已经处理了默认的激活扩展(加载配置文件时收集到的激活扩展类)
                if (Constants.DEFAULT_KEY.equals(name)) {
                    if (!usrs.isEmpty()) {
                        exts.addAll(0, usrs);
                        usrs.clear();
                    }
                } else {
                    // 获得激活扩展实现对象
                    T ext = getExtension(name);
                    usrs.add(ext);
                }
            }
        }

        if (!usrs.isEmpty()) {
            exts.addAll(usrs);
        }
        return exts;
    }

获取激活的扩展对象逻辑在代码中已经详细注释说明,获取扩展实现对象还是调用了getExtension方法。该方法主要步骤:

  1. 如果触发获取扩展实现类动作时,会检查缓存,如果缓存中没有,就加载配置文件来刷新扩展实现类集合。
  2. 遍历缓存中的激活集合(这个缓存内容是加载的带有@Activate注解的扩展类信息),根据传入的URL匹配条件筛选出符合激活条件的扩展类实现,然后进行排序操作。
  3. 遍历传入的激活扩展名集合,根据设置的顺序调整扩展点激活顺序,其中default代表的是所有@Activate标注并且配置在配置文件中的扩展实现类
  4. 通过getExtension(name)获取激活扩展名对应的扩展对象并加入结果集合
  5. 返回符合条件的激活类集合
getAdaptiveExtension 方法

获取自适应扩展对象的入口,如果获取的自适应扩展类属于固定的,那么该方法相对独立,几乎不依赖getExtension方法的逻辑。如果属于动态生成则内部也会调用getExtension方法。由于该方法会涉及到javassist、动态编译等技术,内容较多且比较复杂,这里不再进行分析,我会单独写一篇文章进行详细说明。下面先给出固定的自适应扩展类和自动生成的自适应扩展类的示例,让胖友们有个概念。

固定的自适应扩展类,以编译扩展接口为例:

/**
 * AdaptiveCompiler. (SPI, Singleton, ThreadSafe)
 * 实现Compiler接口,自适应Compiler实现类
 */
@Adaptive
public class AdaptiveCompiler implements Compiler {

    /**
     * 默认编辑器的拓展名
     */
    private static volatile String DEFAULT_COMPILER;

    /**
     * 静态方法,设置默认编辑器的拓展名。该方法被 {@link com.alibaba.dubbo.config.ApplicationConfig#setCompiler(java.lang.String)}方法调用.
     * 在<dubbo:application compiler=""/> 配置下可触发该方法
     *
     * @param compiler
     */
    public static void setDefaultCompiler(String compiler) {
        DEFAULT_COMPILER = compiler;
    }

    @Override
    public Class<?> compile(String code, ClassLoader classLoader) {
        Compiler compiler;
        // 获得Compiler的ExtensionLoader对象
        ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
        // 声明 name 变量,引用 DEFAULT_COMPILER 的值,避免下面的值变了
        String name = DEFAULT_COMPILER;
        // 使用设置的拓展名,获得Compiler拓展对象
        if (name != null && name.length() > 0) {
            compiler = loader.getExtension(name);
            // 获得默认的Compiler拓展对象
        } else {
            compiler = loader.getDefaultExtension();
        }
        // 使用真正的Compiler对象,动态编译代码
        return compiler.compile(code, classLoader);
    }
}

动态生成的自适应扩展类,以ZookeeperTransporter扩展接口为例

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

public class ZookeeperTransporter$Adaptive implements com.alibaba.dubbo.remoting.zookeeper.ZookeeperTransporter {

    public com.alibaba.dubbo.remoting.zookeeper.ZookeeperClient connect(com.alibaba.dubbo.common.URL arg0) {
        if (arg0 == null) {
            throw new IllegalArgumentException("url == null");
        }
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("client", url.getParameter("transporter", "curator"));
        if (extName == null) {
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.zookeeper.ZookeeperTransporter) name from url(" + url.toString() + ") use keys([client, transporter])");
        }
        com.alibaba.dubbo.remoting.zookeeper.ZookeeperTransporter extension = (com.alibaba.dubbo.remoting.zookeeper.ZookeeperTransporter) ExtensionLoader
                .getExtensionLoader(com.alibaba.dubbo.remoting.zookeeper.ZookeeperTransporter.class)
                .getExtension(extName);
        return extension.connect(arg0);
    }
}
hasExtension 方法
/**
     * 判断是否有对应的扩展实现类
     *
     * @param name 扩展名
     * @return
     */
    public boolean hasExtension(String name) {
        if (name == null || name.length() == 0) {
            throw new IllegalArgumentException("Extension name == null");
        }
        try {
            // 没有name对应的扩展实现类就抛出异常,即最后返回false
            this.getExtensionClass(name);
            return true;
        } catch (Throwable t) {
            return false;
        }
    }

上面代码比较简单,根据扩展名判断是否有对应的扩展实现类,之所以单独拿出来介绍是Dubbo的很多流程会用到该方法,有个印象就可以了。

dubbo ioc 实现

dubbo的ioc实现目前仅支持setter注入,严谨来说,dubbo的ioc实现方式还可以通过构造注入,即Wrapper类的实现。dubbo的setter注入要求是,如果某个扩展类是另外一个扩展点实现类的成员属性,并且拥有对应的setter方法,那么dubbo就会自动注入对应的扩展点实现对象。这个功能在上面创建扩展实现的时候需要用到,当时没有详细说明,下面我们单独来分析。

 /**
     * 依赖注入
     *
     * @param instance 扩展实现对象 (注意,可能会是一个Wrapper)
     * @return
     */
    private T injectExtension(T instance) {
        try {

            // 只有ExtensionFactory扩展点对应的ExtensionLoader对象的该属性为null,其它扩展点的ExtensionLoader对象的该属性必然不为null
            if (objectFactory != null) {

                // 反射获得扩展实现对象中的所有方法
                for (Method method : instance.getClass().getMethods()) {

                    // 过滤规则为 ' set开头 + 仅有一个参数 + public ' 的方法
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {

                        /**
                         * 检查方法是否有 @DisableInject 注解,有该注解就忽略依赖注入
                         */
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }

                        // 获取setter方法参数类型
                        Class<?> pt = method.getParameterTypes()[0];

                        try {

                            // 获得属性名,如:setXxx -> xxx
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";

                            /**
                             * 通过扩展工厂获得属性值,即 方法参数类型作为扩展点,属性名作为扩展名。
                             * ExtensionFactory的实现有三个,AdaptiveExtensionFactory是对其它两个工厂的管理,getExtension方法的真正调用的是其它两个工厂的方法:
                             *  1)SpringExtensionFactory
                             *   getExtension方法会返回容器中名称为property并且类型为pt的bean对象
                             *  2)SpiExtensionFactory
                             *   getExtension方法会返回类型为pt的自适应拓展对象,因为该方法会校验pt是接口类型并且有@SPI注解,然后pt有拓展类的情况下,就会获取pt的自适应拓展对象,property没用到
                             */
                            Object object = objectFactory.getExtension(pt, property);

                            // 通过反射设置属性值
                            if (object != null) {
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

dubbo的ioc基于setter方法注入依赖的,注入的依赖来源则需要通过扩展工厂提供,接下来我们来分析dubbo的扩展工厂。

扩展工厂接口
@SPI
public interface ExtensionFactory {

    /**
     * Get extension. 获得扩展对象
     *
     * @param type object type. 扩展接口
     * @param name object name. 扩展名
     * @return object instance. 扩展实现实例
     */
    <T> T getExtension(Class<T> type, String name);
}

ExtensionFactory 扩展工厂,是dubbo的一个扩展点。主要用于获取扩展实现对象所需的依赖,然后完成依赖注入,该接口的uml关系如下:

由uml图可知,该接口有三个扩展实现类。AdaptiveExtensionFactory是它的自适应实现类,只是用来管理SpiExtensionFactory和SpringExtensionFactory,具体依赖的查找还是由这两个类完成,下面我们分别来分析。

AdaptiveExtensionFactory 工厂
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    /**
     * ExtensionFactory扩展实现对象集合
     */
    private final List<ExtensionFactory> factories;

    /**
     * AdaptiveExtensionFactory也是ExtensionFactory的扩展实现类,只是比较特殊,是自适应扩展类,不同于普通的扩展类
     */
    public AdaptiveExtensionFactory() {

        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();

        // 使用ExtensionLoader 加载拓展点实现类,getSupportedExtensions() 返回的是ExtensionFactory扩展点实现类对应的扩展名集合
        for (String name : loader.getSupportedExtensions()) {

            // 根据扩展名获取 ExtensionFactory 的扩展实现对象 并加入缓存中
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    /**
     * 获取目标对象,主要用于 {@link ExtensionLoader#injectExtension(java.lang.Object)} 方法中,用于获取扩展实现对象所需要的依赖属性值
     *
     * @param type object type. 扩展接口
     * @param name object name. 扩展名
     * @param <T>
     * @return
     */
    @Override
    public <T> T getExtension(Class<T> type, String name) {

        // 遍历扩展工厂对象,获取指定的扩展对象或Spring中的Bean对象
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }
}

AdaptiveExtensionFactory 自适应扩展工厂,内部维护了一个 ExtensionFactory 列表,用来管理其它的ExtensionFactory。在用户没有自定义ExtensionFactory的情况下,dubbo目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建 自适应的拓展,后者从Spring容器中获取所需依赖。

SpiExtensionFactory 工厂
public class SpiExtensionFactory implements ExtensionFactory {
    /**
     * 获取自适应扩展对象
     *
     * @param type object type. 扩展接口
     * @param name object name. 扩展名
     * @param <T>
     * @return
     */
    @Override
    public <T> T getExtension(Class<T> type, String name) {
        // 校验是接口类型并且有@SPI注解
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            // 加载拓展接口对应的 ExtensionLoader
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);

            // 判断当前扩展点是否有普通的扩展实现类,注意:当前扩展点存在普通的扩展实现类才会去获取对应的自适应扩展对象
            if (!loader.getSupportedExtensions().isEmpty()) {
                // 获取自适应扩展对象
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }
}
SpringExtensionFactory 工厂
public class SpringExtensionFactory implements ExtensionFactory {
    private static final Logger logger = LoggerFactory.getLogger(SpringExtensionFactory.class);

    /**
     * Spring上下文
     */
    private static final Set<ApplicationContext> contexts = new ConcurrentHashSet<ApplicationContext>();

    /**
     * 保存Spring上下文
     *
     * @param context
     */
    public static void addApplicationContext(ApplicationContext context) {
        contexts.add(context);
    }

    public static void removeApplicationContext(ApplicationContext context) {
        contexts.remove(context);
    }

    // currently for test purpose
    public static void clearContexts() {
        contexts.clear();
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T getExtension(Class<T> type, String name) {

        // 遍历SpringContext上下集合
        for (ApplicationContext context : contexts) {
            // 判断容器中是否包含名称为name的bean
            if (context.containsBean(name)) {
                // 获得bean对象
                Object bean = context.getBean(name);
                // 判断获得的bean类型是否是type类型
                if (type.isInstance(bean)) {
                    return (T) bean;
                }
            }
        }

        logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());

        if (Object.class == type) {
            return null;
        }

        for (ApplicationContext context : contexts) {
            try {
                return context.getBean(type);
            } catch (NoUniqueBeanDefinitionException multiBeanExe) {
                logger.warn("Find more than 1 spring extensions (beans) of type " + type.getName() + ", will stop auto injection. Please make sure you have specified the concrete parameter type and there's only one extension of that type.");
            } catch (NoSuchBeanDefinitionException noBeanExe) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Error when get spring extension(bean) for type:" + type.getName(), noBeanExe);
                }
            }
        }
        logger.warn("No spring extension (bean) named:" + name + ", type:" + type.getName() + " found, stop get bean.");
        return null;
    }
}

dubbo使用spring容器管理的依赖为扩展对象注入依赖属性。dubbo是如何与spring容器打通的呢?有两处结合点,分别是服务暴露和服务引用的时候,利用ApplicationContextAware的回调方法设置spring上下文。

  • 服务暴露结合点
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
        ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware,
        ApplicationEventPublisherAware {

   // 省略无关代码

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {// 当前加载的上下文
        this.applicationContext = applicationContext;
        // 为Spring拓展工厂注入上下文 ,todo dubbo和Spring容器打通
        SpringExtensionFactory.addApplicationContext(applicationContext);
        if (applicationContext != null) {
            SPRING_CONTEXT = applicationContext;
            try {
                Method method = applicationContext.getClass().getMethod("addApplicationListener", new Class<?>[]{ApplicationListener.class}); // backward compatibility to spring 2.0.1
                method.invoke(applicationContext, new Object[]{this});
                supportedApplicationListener = true; // 当前Spring容器是否支持上下文监听
            } catch (Throwable t) {
                if (applicationContext instanceof AbstractApplicationContext) {
                    try {
                        Method method = AbstractApplicationContext.class.getDeclaredMethod("addListener", new Class<?>[]{ApplicationListener.class}); // backward compatibility to spring 2.0.1
                        if (!method.isAccessible()) {
                            method.setAccessible(true);
                        }
                        method.invoke(applicationContext, new Object[]{this});
                        supportedApplicationListener = true;
                    } catch (Throwable t2) {
                    }
                }
            }
        }
    }
}
  • 服务引用结合点
public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {
    // 省略无关代码
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        // dubbo 和 spring容器打通
        SpringExtensionFactory.addApplicationContext(applicationContext);
    }
}
dubbo aop 实现

dubbo aop 实现需要Wrapper类,关于Wrapper类前面已经介绍过了,这里不再说明。关于dubbo aop的功能也在前面的流程中体现出来了,单独把dubbo aop拿出来进行说明是考虑到前面的篇幅没有具体到Wrapper类,只是阐述了其功能和实现。实现一个Wrapper类的基本步骤如下:

  1. 定义一个Wrapper类并实现扩展接口,然后编写AOP逻辑。
  2. 在配置文件配置自定义的Wrapper类
定义 Wrapper类
public class CommandWrapper implements Command {

    Command command;

    /**
     * 构造方法的参数必须是扩展点类型
     *
     * @param command
     */
    public CommandWrapper(Command command) {
        this.command = command;
    }

    @Override
    public void execute() {
        
        System.out.println("CommandWrapper is running ...");
        // 执行扩展实现对象,注意,如果不显示调用扩展实现,那么就达不到目标结果,只会执行这个并没有真正实现的Wrapper
        command.execute();
    }
}

定义Wrapper类很简单,只要按照Wrapper类的要求进行实现即可。需要说明的是,我们所说的Wrapper类其实不强制类名以Wrapper结尾,只要符合Wrapper类的要求就是一个Wrapper类,并不是用名字进行区分是否Wrapper类,只是这样写是dubbo的一种约定罢了。

配置 Wrapper类

dubbo 内置的Wrapper 举例
/**
 * 实现 Cluster接口,MockClusterWrapper实现类,注意它是个Wrapper类,对应的Cluster对象都会被它所包装。
 */
public class MockClusterWrapper implements Cluster {

    /**
     * 真正的Cluster 对象
     */
    private Cluster cluster;

    public MockClusterWrapper(Cluster cluster) {
        this.cluster = cluster;
    }

    /**
     * 创建MockClusterInvoker对象
     *
     * @param directory Directory 对象
     * @param <T>
     * @return
     * @throws RpcException
     */
    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new MockClusterInvoker<T>(directory, this.cluster.join(directory));
    }
}

总结

本篇文章简单介绍了dubbo spi 用法,并对 dubbo spi 的核心源码进行了分析,总体上不算复杂但很繁琐,细节点很多,比如,扩展名生成的规则,扩展类的种类区别,自动激活扩展生效条件。想要掌握整个流程需要耐心调试源码,笔者差点被spi及接下来要分析的dubbo配置给劝退了。另外,由于dubbo spi自适应机制涉及到的代码量较多,逻辑比较复杂,我将会在下一篇文章中单独进行分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值