dubbo源码解析之SPI机制

大纲

1.Java spi 机制简介

2.dubbo 重新实现的spi对比原生的有哪些改进

3.dubbo spi 源码流程分析


1.Java spi机制简介

spi是一种扩展加载机制, SPI 是英文Service Provider Interface的缩写 允许将接口的定义和实现解耦.在传统的面向接口编程中,接口的实现需要写死在代码中.对于一些框架来说十分不便.需要更换实现时还需要去修改源码,因此jdk官方提供了一种机制,将接口的实现配置在配置文件中,可以灵活的进行更换,例如jdbc的驱动加载等.
要使用JAVA的spi也十分简单,主要满足3个条件就阔以:

  • 有一个接口,并在工程中的/META-INF/services 文件夹中建一个以该接口全类名为文件名的文件

  • 为该接口编写若干个实现类,并将实现类的全类名配置到上面建的配置文件中

  • 实现类中必须含有一个无参构造方法(显式隐式均可)

    满足以上条件后就可以使用了,下面看一个代码栗子.


package org.xrq.test.spi;

public interface SpiService {

    public void hello();
    
}

两个实现类 分别为SpiServiceA B


package org.xrq.test.spi;

public class SpiServiceA implements SpiService {

    public void hello() {
        System.out.println("SpiServiceA.Hello");
    }
    
}

package org.xrq.test.spi;

public class SpiServiceB implements SpiService {

    public void hello() {
        System.out.println("SpiServiceB.Hello");
    }
    
}

META-INF/services下的配置文件

文件名为接口的全限定名org.xrq.test.spi.SpiService:

org.xrq.test.spi.SpiServiceA
org.xrq.test.spi.SpiServiceB

这样就ojbk了 让我们写个测试类来试试.


public class SpiTest {

    @Test
    public void testSpi() {
        ServiceLoader<SpiService> serviceLoader = ServiceLoader.load(SpiService.class);
        
        Iterator<SpiService> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            SpiService spiService = iterator.next();
            
            spiService.hello();
        }
    }
    
}

结果很简单 控制台依次输出了 SpiServiceA.Hello和 SpiServiceB.Hello

这是一个示例,如果有兴趣分析其内部实现可以看看ServiceLoader类的源码 1.7与1.8略有不同,但原理都是一样的.其实都是通过配置文件拿到全类名,再通过反射的方式去创建类并调用的.由于本文重点是讲dubbo的spi类,因此对java的介绍就到此为止.

2.dubbo 重新实现的spi对比原生的有哪些改进

这一点我直接copy官方文档里的说法如下:

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。这一点看上面的例子就知道,在使用时无法指定加载哪一个,会一次性全部都加载出来
  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
  • 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。这一点在dubbo源码中大量使用,来实现包装类.

在我看来1,2点是对不足的改进 而第3点可以称为功能的增强. 除了这几点,还有最大的区别就是实现了自适应扩展点和自动激活的扩展点,这一点在下面会详细讲解.

3.dubbo spi 源码流程分析

3.1综述

有了上面jdk spi的例子,其实dubbo的spi也是在其基础上增强的.首先配置文件的位置变化了,变为了以下三个地方:

private static final String SERVICES_DIRECTORY = "META-INF/services/";

private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";

private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

其中第一点其实是为了兼容java的spi,而三个配置文件加载顺序是3>2>1 而优先级是1>2>3 因为后面的相同的key会覆盖前面的.

随便打开一个dubbo源码中 META-INF/dubbo/internal/目录 可以发现dubbo spi中配置文件长这样:

adaptive=com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler
jdk=com.alibaba.dubbo.common.compiler.support.JdkCompiler
javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler

对比发现变成了key-value的形式. 因为可以根据传入key来精准度的寻找特定实现,所以可以实现按需加载而非启动时全部加载.

此外dubbo 还扩展了java spi的功能,例如自适应扩展类(adaptive) 和自动激活扩展类.可以根据注解和传参实现动态生成实现类代码,肥肠灵活. 并大量利用缓存来提高性能,spi用到的类按需加载,且只加载一次.后续都从缓存中获取.

3.1 普通扩展类的加载实现

代码示例

同样的举个栗子.我们复用上面java spi的接口和两个实现类,不同的是接口声明上需要加上@SPI注解,可以暂时理解为一种标记. 此外配置文件需要作一些修改,文件名不变,仍为接口全类名:

spiServiceA=org.xrq.test.spi.SpiServiceA
spiServiceB=org.xrq.test.spi.SpiServiceB

同样的写个测试类来测试下:

 @Test
    public void testSpi() {
        ExtensionLoader<SpiService> serviceLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
        SpiService spiServiceA = serviceLoader.getExtension("spiServiceA");
        SpiService spiServiceB = serviceLoader.getExtension("spiServiceB");
        spiServiceA.hello();
        spiServiceB.hello();
    }

同样,控制台依次输出了 SpiServiceA.Hello和 SpiServiceB.Hello. 这里可以看到,可以通过传入key作为参数来获取某个特定的实现.

原理分析

源码的主入口在com.alibaba.dubbo.common.extension.ExtensionLoader 类中.首先看下该类一些相关的成员变量:

 /**
   * 拓展加载器集合
   *
   * key:拓展接口
   */
  private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
  /**
   * 拓展实现类集合
   *
   * key:拓展实现类
   * value:拓展对象。
   *
   * 例如,key 为 Class<AccessLogFilter>
   *      value 为 AccessLogFilter 对象
   */
  private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();

  // ============================== 对象属性 ==============================

  /**
   * 拓展接口。
   * 例如,Protocol
   */
  private final Class<?> type;
  /**
   * 对象工厂
   *
   * 用于调用 {@link #injectExtension(Object)} 方法,向拓展对象注入依赖的属性。
   *
   * 例如,StubProxyFactoryWrapper 中有 `Protocol protocol` 属性。
   */
  private final ExtensionFactory objectFactory;
  /**
   * 缓存的拓展名与拓展类的映射。
   *
   * 和 {@link #cachedClasses} 的 KV 对调。
   *
   * 通过 {@link #loadExtensionClasses} 加载
   */
  private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
  /**
   * 缓存的拓展实现类集合。
   *
   * 不包含如下两种类型:
   *  1. 自适应拓展实现类。例如 AdaptiveExtensionFactory
   *  2. 带唯一参数为拓展接口的构造方法的实现类,或者说拓展 Wrapper 实现类。例如,ProtocolFilterWrapper 。
   *       拓展 Wrapper 实现类,会添加到 {@link #cachedWrapperClasses} 中
   *
   * 通过 {@link #loadExtensionClasses} 加载
   */
  private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();

 
  /**
   * 缓存的拓展对象集合
   *
   * key:拓展名
   * value:拓展对象
   *
   * 例如,Protocol 拓展
   *          key:dubbo value:DubboProtocol
   *          key:injvm value:InjvmProtocol
   *
   * 通过 {@link #loadExtensionClasses} 加载
   */
  private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
  /**
   * 缓存的默认拓展名
   *
   * 通过 {@link SPI} 注解获得
   */
  private String cachedDefaultName;
  /**
   * 创建 {@link #cachedAdaptiveInstance} 时发生的异常。
   *
   * 发生异常后,不再创建,参见 {@link #createAdaptiveExtension()}
   */
  private volatile Throwable createAdaptiveInstanceError;

3.1.1 getExtensionLoader(Class type)

变量上都已经加了注释… 首先从 ExtensionLoader serviceLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
这段代码中 可以看到ExtensionLoader 这个对象本身是通过getExtensionLoader(Class type) 获取到的 先看下它的实现:

 /**
     * 拓展接口。
     * 例如,Protocol
     */
    private final Class<?> type;
    /**
     * 对象工厂
     *
     * 用于调用 {@link #injectExtension(Object)} 方法,向拓展对象注入依赖的属性。
     *
     * 例如,StubProxyFactoryWrapper 中有 `Protocol protocol` 属性。
     */
    private final ExtensionFactory objectFactory;
    
 public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        // 必须是接口
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        // 必须包含 @SPI 注解
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }

        // 获得接口对应的拓展点加载器
        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;
    }
    
    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

代码逻辑比较简单,主要就是校验传入的是接口以及必须有@SPI注解,然后先从缓存里获取ExtensionLoader对象(对自身也做了缓存),获取不到就构造一个ExtensionLoader并放进缓存中,最终返回.

3.1.2 getExtension(String name)

下面我们分析getExtension(String name)方法:

/**
     * 缓存的拓展对象集合
     *
     * key:拓展名
     * value:拓展对象
     *
     * 例如,Protocol 拓展
     *          key:dubbo value:DubboProtocol
     *          key:injvm value:InjvmProtocol
     *
     * 通过 {@link #loadExtensionClasses} 加载
     */
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
    
    
 public T getExtension(String name) {
        if (name == null || name.length() == 0)
            throw new IllegalArgumentException("Extension name == null");
        // 查找 默认的 拓展对象
        if ("true".equals(name)) {
            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;
    }
    

这里首先仍然是优先检查缓存中有无对应的key,此处的key即为配置文件中配置的key.如果没有就调用createExtension(String name)方法进行创建

 /**
     * 拓展实现类集合
     *
     * key:拓展实现类
     * value:拓展对象。
     *
     * 例如,key 为 Class<AccessLogFilter>
     *      value 为 AccessLogFilter 对象
     */
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
    
 /**
     * 创建拓展名的拓展对象,并缓存。
     *
     * @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);
            }
            // 注入依赖的属性
            injectExtension(instance);
            // 若查找到包装类 则依次创建 Wrapper 拓展对象  装饰器模式  一层套一层
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    //若查找到wrapper类,条件是该类有一个以该接口为参数的构造函数.参考装饰模式
                    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);
        }
    }

这里首先通过getExtensionClasses() 方法获取到一个 Map<String, Class<?>> 这个方法就不展开了 其实就是遍历上面那三个配置文件的内容,拿到配置的key-value键值对,注意这里的value是具体实现类的class对象.key就是配置文件中配置的key.获取到实例对象后,调用injectExtension(instance)方法注入属性:

 /**
     * 注入依赖的属性,类似于Spring的简化IOC实现
     *
     * @param instance 拓展对象
     * @return 拓展对象
     */
    private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) { // setting && public 方法
                        // 获得属性的类型
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            // 获得属性名
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            // 获得属性值 从优先从spi中获取,再从spring上下文中获取,因为对象是用treeSet自然排序存储的.
                            Object object = objectFactory.getExtension(pt, property);
                            // 将上面拿到的属性值 set到对象中
                            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;
    }

这个方法就是遍历该对象中的方法,找到setXXX这种方法,然后从配置文件中拿到对应的属性注入进去.逻辑比较简单

然后回到这段代码

 // 若查找到包装类 则依次创建 Wrapper 拓展对象  装饰器模式  一层套一层
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    //若查找到wrapper类,条件是该类有一个以该接口为参数的构造函数.参考装饰模式
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }

这里举个例子吧 ,参考ProtocolFilterWrapper和

大纲

1.Java spi 机制简介

2.dubbo 重新实现的spi对比原生的有哪些改进

3.dubbo spi 源码流程分析

1.Java spi机制简介

spi是一种扩展加载机制, SPI 是英文Service Provider Interface的缩写 允许将接口的定义和实现解耦.在传统的面向接口编程中,接口的实现需要写死在代码中.对于一些框架来说十分不便.需要更换实现时还需要去修改源码,因此jdk官方提供了一种机制,将接口的实现配置在配置文件中,可以灵活的进行更换,例如jdbc的驱动加载等.
要使用JAVA的spi也十分简单,主要满足3个条件就阔以:

  • 有一个接口,并在工程中的/META-INF/services 文件夹中建一个以该接口全类名为文件名的文件

  • 为该接口编写若干个实现类,并将实现类的全类名配置到上面建的配置文件中

  • 实现类中必须含有一个无参构造方法(显式隐式均可)

    满足以上条件后就可以使用了,下面看一个代码栗子.


package org.xrq.test.spi;

public interface SpiService {

    public void hello();
    
}

两个实现类 分别为SpiServiceA B


package org.xrq.test.spi;

public class SpiServiceA implements SpiService {

    public void hello() {
        System.out.println("SpiServiceA.Hello");
    }
    
}

package org.xrq.test.spi;

public class SpiServiceB implements SpiService {

    public void hello() {
        System.out.println("SpiServiceB.Hello");
    }
    
}

META-INF/services下的配置文件

文件名为接口的全限定名org.xrq.test.spi.SpiService:

org.xrq.test.spi.SpiServiceA
org.xrq.test.spi.SpiServiceB

这样就ojbk了 让我们写个测试类来试试.


public class SpiTest {

    @Test
    public void testSpi() {
        ServiceLoader<SpiService> serviceLoader = ServiceLoader.load(SpiService.class);
        
        Iterator<SpiService> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            SpiService spiService = iterator.next();
            
            spiService.hello();
        }
    }
    
}

结果很简单 控制台依次输出了 SpiServiceA.Hello和 SpiServiceB.Hello

这是一个示例,如果有兴趣分析其内部实现可以看看ServiceLoader类的源码 1.7与1.8略有不同,但原理都是一样的.其实都是通过配置文件拿到全类名,再通过反射的方式去创建类并调用的.由于本文重点是讲dubbo的spi类,因此对java的介绍就到此为止.

2.dubbo 重新实现的spi对比原生的有哪些改进

这一点我直接copy官方文档里的说法如下:

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。这一点看上面的例子就知道,在使用时无法指定加载哪一个,会一次性全部都加载出来
  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
  • 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。这一点在dubbo源码中大量使用,来实现包装类.

在我看来1,2点是对不足的改进 而第3点可以称为功能的增强. 除了这几点,还有最大的区别就是实现了自适应扩展点和自动激活的扩展点,这一点在下面会详细讲解.

3.dubbo spi 源码流程分析

3.1综述

有了上面jdk spi的例子,其实dubbo的spi也是在其基础上增强的.首先配置文件的位置变化了,变为了以下三个地方:

private static final String SERVICES_DIRECTORY = "META-INF/services/";

private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";

private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

其中第一点其实是为了兼容java的spi,而三个配置文件加载顺序是3>2>1 而优先级是1>2>3 因为后面的相同的key会覆盖前面的.

随便打开一个dubbo源码中 META-INF/dubbo/internal/目录 可以发现dubbo spi中配置文件长这样:

adaptive=com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler
jdk=com.alibaba.dubbo.common.compiler.support.JdkCompiler
javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler

对比发现变成了key-value的形式. 因为可以根据传入key来精准度的寻找特定实现,所以可以实现按需加载而非启动时全部加载.

此外dubbo 还扩展了java spi的功能,例如自适应扩展类(adaptive) 和自动激活扩展类.可以根据注解和传参实现动态生成实现类代码,肥肠灵活. 并大量利用缓存来提高性能,spi用到的类按需加载,且只加载一次.后续都从缓存中获取.

3.1 普通扩展类的加载实现

代码示例

同样的举个栗子.我们复用上面java spi的接口和两个实现类,不同的是接口声明上需要加上@SPI注解,可以暂时理解为一种标记. 此外配置文件需要作一些修改,文件名不变,仍为接口全类名:

spiServiceA=org.xrq.test.spi.SpiServiceA
spiServiceB=org.xrq.test.spi.SpiServiceB

同样的写个测试类来测试下:

 @Test
    public void testSpi() {
        ExtensionLoader<SpiService> serviceLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
        SpiService spiServiceA = serviceLoader.getExtension("spiServiceA");
        SpiService spiServiceB = serviceLoader.getExtension("spiServiceB");
        spiServiceA.hello();
        spiServiceB.hello();
    }

同样,控制台依次输出了 SpiServiceA.Hello和 SpiServiceB.Hello. 这里可以看到,可以通过传入key作为参数来获取某个特定的实现.

原理分析

源码的主入口在com.alibaba.dubbo.common.extension.ExtensionLoader 类中.首先看下该类一些相关的成员变量:

 /**
   * 拓展加载器集合
   *
   * key:拓展接口
   */
  private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
  /**
   * 拓展实现类集合
   *
   * key:拓展实现类
   * value:拓展对象。
   *
   * 例如,key 为 Class<AccessLogFilter>
   *      value 为 AccessLogFilter 对象
   */
  private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();

  // ============================== 对象属性 ==============================

  /**
   * 拓展接口。
   * 例如,Protocol
   */
  private final Class<?> type;
  /**
   * 对象工厂
   *
   * 用于调用 {@link #injectExtension(Object)} 方法,向拓展对象注入依赖的属性。
   *
   * 例如,StubProxyFactoryWrapper 中有 `Protocol protocol` 属性。
   */
  private final ExtensionFactory objectFactory;
  /**
   * 缓存的拓展名与拓展类的映射。
   *
   * 和 {@link #cachedClasses} 的 KV 对调。
   *
   * 通过 {@link #loadExtensionClasses} 加载
   */
  private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
  /**
   * 缓存的拓展实现类集合。
   *
   * 不包含如下两种类型:
   *  1. 自适应拓展实现类。例如 AdaptiveExtensionFactory
   *  2. 带唯一参数为拓展接口的构造方法的实现类,或者说拓展 Wrapper 实现类。例如,ProtocolFilterWrapper 。
   *       拓展 Wrapper 实现类,会添加到 {@link #cachedWrapperClasses} 中
   *
   * 通过 {@link #loadExtensionClasses} 加载
   */
  private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();

 
  /**
   * 缓存的拓展对象集合
   *
   * key:拓展名
   * value:拓展对象
   *
   * 例如,Protocol 拓展
   *          key:dubbo value:DubboProtocol
   *          key:injvm value:InjvmProtocol
   *
   * 通过 {@link #loadExtensionClasses} 加载
   */
  private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
  /**
   * 缓存的默认拓展名
   *
   * 通过 {@link SPI} 注解获得
   */
  private String cachedDefaultName;
  /**
   * 创建 {@link #cachedAdaptiveInstance} 时发生的异常。
   *
   * 发生异常后,不再创建,参见 {@link #createAdaptiveExtension()}
   */
  private volatile Throwable createAdaptiveInstanceError;

3.1.1 getExtensionLoader(Class type)

变量上都已经加了注释… 首先从 ExtensionLoader serviceLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
这段代码中 可以看到ExtensionLoader 这个对象本身是通过getExtensionLoader(Class type) 获取到的 先看下它的实现:

 /**
     * 拓展接口。
     * 例如,Protocol
     */
    private final Class<?> type;
    /**
     * 对象工厂
     *
     * 用于调用 {@link #injectExtension(Object)} 方法,向拓展对象注入依赖的属性。
     *
     * 例如,StubProxyFactoryWrapper 中有 `Protocol protocol` 属性。
     */
    private final ExtensionFactory objectFactory;
    
 public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        // 必须是接口
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        // 必须包含 @SPI 注解
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }

        // 获得接口对应的拓展点加载器
        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;
    }
    
    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

代码逻辑比较简单,主要就是校验传入的是接口以及必须有@SPI注解,然后先从缓存里获取ExtensionLoader对象(对自身也做了缓存),获取不到就构造一个ExtensionLoader并放进缓存中,最终返回.

3.1.2 getExtension(String name)

下面我们分析getExtension(String name)方法:

/**
     * 缓存的拓展对象集合
     *
     * key:拓展名
     * value:拓展对象
     *
     * 例如,Protocol 拓展
     *          key:dubbo value:DubboProtocol
     *          key:injvm value:InjvmProtocol
     *
     * 通过 {@link #loadExtensionClasses} 加载
     */
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
    
    
 public T getExtension(String name) {
        if (name == null || name.length() == 0)
            throw new IllegalArgumentException("Extension name == null");
        // 查找 默认的 拓展对象
        if ("true".equals(name)) {
            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;
    }
    

这里首先仍然是优先检查缓存中有无对应的key,此处的key即为配置文件中配置的key.如果没有就调用createExtension(String name)方法进行创建

 /**
     * 拓展实现类集合
     *
     * key:拓展实现类
     * value:拓展对象。
     *
     * 例如,key 为 Class<AccessLogFilter>
     *      value 为 AccessLogFilter 对象
     */
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
    
 /**
     * 创建拓展名的拓展对象,并缓存。
     *
     * @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);
            }
            // 注入依赖的属性
            injectExtension(instance);
            // 若查找到包装类 则依次创建 Wrapper 拓展对象  装饰器模式  一层套一层
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    //若查找到wrapper类,条件是该类有一个以该接口为参数的构造函数.参考装饰模式
                    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);
        }
    }

这里首先通过getExtensionClasses() 方法获取到一个 Map<String, Class<?>> 这个方法就不展开了 其实就是遍历上面那三个配置文件的内容,拿到配置的key-value键值对,注意这里的value是具体实现类的class对象.key就是配置文件中配置的key.获取到实例对象后,调用injectExtension(instance)方法注入属性:

 /**
     * 注入依赖的属性,类似于Spring的简化IOC实现
     *
     * @param instance 拓展对象
     * @return 拓展对象
     */
    private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) { // setting && public 方法
                        // 获得属性的类型
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            // 获得属性名
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            // 获得属性值 从优先从spi中获取,再从spring上下文中获取,因为对象是用treeSet自然排序存储的.
                            Object object = objectFactory.getExtension(pt, property);
                            // 将上面拿到的属性值 set到对象中
                            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;
    }

这个方法就是遍历该对象中的方法,找到setXXX这种方法,然后从配置文件中拿到对应的属性注入进去.逻辑比较简单

然后回到这段代码

 // 若查找到包装类 则依次创建 Wrapper 拓展对象  装饰器模式  一层套一层
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    //若查找到wrapper类,条件是该类有一个以该接口为参数的构造函数.参考装饰模式
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }

这里举个例子吧 ,参考ProtocolFilterWrapper和ProtocolListenerWrapper,在实际使用中,通过包装的方式实现了AOP的功能, 调用的是Protocol的真正实现类,例如DubboProtocol,在这基础上实现了过滤器和监听器功能,是对原接口功能的一种增强.在配置文件中,符合条件的类(,条件是该类有一个以该接口为参数的构造函数)的实现类都会被视作包装类.
在这里插入图片描述


public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolFilterWrapper(Protocol protocol) {
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }
    }

包装完后就return了,这样就得到了一个真正的实例对象(可能经过包装),即可调用所实现的逻辑.十分的灵活…

3.2 自适应扩展类的加载实现

自适应扩展类是dubbo spi的精华,当然也要稍微复杂一些.不过这也是阅读dubbo源码中比较重要的一个环节,因为dubbo在源码中大量使用了这一方式来动态的创建类,并且框架强大的扩展性也是来源于此,使得围绕dubbo的许多组件都能进行二次开发和扩展.

下面还是以一段示例代码来进行说明.

@SPI("spiServiceA")
public interface SpiService {

    @Adaptive("myKey")
     void hello(URL url);
}
//省略B的实现类
public class SpiServiceA implements SpiService {
    @Override
    public void hello(URL url) {
        System.out.println("SpiServiceA.Hello");
    }
}
 @Test
    public void testSpi() {
        ExtensionLoader<SpiService> serviceLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
        SpiService spiService = serviceLoader.getAdaptiveExtension();
        spiService.hello(URL.valueOf("dubbo://localhost:8080?myKey=spiServiceB"));
    }
3.2.1 @Adaptive注解

观察接口 发现在方法上添加了@Adaptive 注解,查看源码

/**
 * Provide helpful information for {@link ExtensionLoader} to inject dependency extension instance.
 *
 * 在 {@link ExtensionLoader} 生成 Extension 的 Adaptive Instance 时,为 {@link ExtensionLoader} 提供信息。
 *
 * `@Adaptive` 可添加类或方法上。这两种方式表现不同:
 *
 * 1. 当在 类 上时,直接使用被注解的类。也因此,一个拓展,只允许最多注解一个类,否则会存在多个会是冲突。
 * 2. 当在方法上时,使用 {@link ExtensionLoader#createAdaptiveExtensionClass()} 方法,创建自适应( Adaptive )拓展类。
 *
 * 如上逻辑,处理的入口方法为 {@link ExtensionLoader#getAdaptiveExtensionClass()}
 *
 * @see ExtensionLoader
 * @see URL
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    /**
     * 从 {@link URL }的 Key 名,对应的 Value 作为要 Adapt 成的 Extension 名。
     * <p>
     * 如果 {@link URL} 这些 Key 都没有 Value ,使用 缺省的扩展(在接口的{@link SPI}中设定的值)。<br>
     * 比如,<code>String[] {"key1", "key2"}</code>,表示
     * <ol>
     *      <li>先在URL上找key1的Value作为要Adapt成的Extension名;
     *      <li>key1没有Value,则使用key2的Value作为要Adapt成的Extension名。
     *      <li>key2没有Value,使用缺省的扩展。
     *      <li>如果没有设定缺省扩展,则方法调用会抛出{@link IllegalStateException}。
     * </ol>
     * <p>
     * 如果不设置则缺省使用Extension接口类名的点分隔小写字串。<br>
     * 即对于Extension接口 {@code com.alibaba.dubbo.xxx.YyyInvokerWrapper} 的缺省值为 <code>String[] {"yyy.invoker.wrapper"}</code>
     *
     * @see SPI#value()
     */
    String[] value() default {};

}

注释写的比较清楚 这个注解可以作用在类上和方法上,
1.当在 类 上时,直接使用被注解的类,跳过代码动态生成和编译环节。也因此,一个拓展,只允许最多注解一个类,否则会存在多个会是冲突。在dubbo中使用得比较少,只有AdaptiveExtensionFactory等少数类使用到类上,表示这个类由人工编码完成.
2.当在方法上时,使用 {@link ExtensionLoader#createAdaptiveExtensionClass()} 方法,创建自适应( Adaptive )拓展类。此外还可以通过传入的URL 中携带的参数值,来动态指定要使用的方法实现类.

3.2.2 getAdaptiveExtension()方法

查看测试类可知,要获取一个自适应类的入口是getAdaptiveExtension()方法,下面开搞:

 /**
     * 获得自适应拓展对象
     *
     * @return 拓展对象
     */
    @SuppressWarnings("unchecked")
    public T getAdaptiveExtension() {
        // 从缓存中,获得自适应拓展对象
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            // 若之前未创建报错,
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            // 创建自适应拓展对象
                            instance = createAdaptiveExtension();
                            // 设置到缓存
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            // 记录异常
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            // 若之前创建报错,则直接抛出异常 IllegalStateException
            } else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }
        return (T) instance;
    }
3.2.3 createAdaptiveExtension()

以上流程比较简单,核心代码在于createAdaptiveExtension()方法,我们来看看如下实现

 /**
     * 创建自适应拓展对象
     *
     * @return 拓展对象
     */
    @SuppressWarnings("unchecked")
    private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

这里injectExtension()方法之前已经分析过,忘记了的小伙伴可以上一节看下,主要就是一个IOC的实现.
这里的重点在于getAdaptiveExtensionClass()方法.

3.2.4 getAdaptiveExtensionClass()
 /**
     * @return 自适应拓展类
     */
    private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

之前说过,对于同一个接口只会生成一个自适应扩展类,因此这里直接看下缓存里有没有,没有的话再进行创建.值得注意的是,如果@Adaptive注解添加在一个类上,那在之前调用loadFile()方法扫描所有配置文件并缓存实现类时候,会将这个类缓存到cachedAdaptiveClass中,这样if()中的判断为true,这里直接返回不往下走了

3.2.5 createAdaptiveExtensionClass()
 /**
     * 自动生成自适应拓展的代码实现,并编译后返回该类。
     *
     * @return 类
     */
    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);
    }

这段代码主要讲通过某些规则去生成自适应类的代码.注意这里是String类型的,再通过一个编译工具类将其编译成一个可以使用的Class文件,为什么不通过反射的方式直接使用呢,这里主要还是考虑到效率的因素,毕竟字节码文件执行的效率肯定是高于反射执行的.

3.2.6 createAdaptiveExtensionClassCode()
  /**
     * 自动生成自适应拓展的代码实现的字符串
     *
     * @return 代码字符串
     */
    private String createAdaptiveExtensionClassCode() {
        StringBuilder codeBuidler = new StringBuilder();
        // 遍历方法数组,判断有 @Adaptive 注解
        Method[] methods = type.getMethods();
        boolean hasAdaptiveAnnotation = false;
        for (Method m : methods) {
            if (m.isAnnotationPresent(Adaptive.class)) {
                hasAdaptiveAnnotation = true;
                break;
            }
        }
        // no need to generate adaptive class since there's no adaptive method found.
        // 完全没有Adaptive方法,则不需要生成Adaptive类
        if (!hasAdaptiveAnnotation)
            throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");

        // 生成代码:package 和 import
        codeBuidler.append("package " + type.getPackage().getName() + ";");
        codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";");
        // 生成代码:类名
        codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adaptive" + " implements " + type.getCanonicalName() + " {");

        // 循环方法
        for (Method method : methods) {
            Class<?> rt = method.getReturnType(); // 返回类型
            Class<?>[] pts = method.getParameterTypes(); // 参数类型数组
            Class<?>[] ets = method.getExceptionTypes(); // 异常类型数组

            Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
            StringBuilder code = new StringBuilder(512); // 方法体的代码
            // 非 @Adaptive 注解,生成代码:生成的方法为直接抛出异常。因为,非自适应的接口不应该被调用。
            if (adaptiveAnnotation == null) {
                code.append("throw new UnsupportedOperationException(\"method ")
                        .append(method.toString()).append(" of interface ")
                        .append(type.getName()).append(" is not adaptive method!\");");
            // @Adaptive 注解,生成方法体的代码
            } else {
                // 寻找 Dubbo URL 参数的位置
                int urlTypeIndex = -1;
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].equals(URL.class)) {
                        urlTypeIndex = i;
                        break;
                    }
                }
                // found parameter in URL type
                // 有类型为URL的参数,生成代码:生成校验 URL 非空的代码
                if (urlTypeIndex != -1) {
                    // Null Point check
                    String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",
                            urlTypeIndex);
                    code.append(s);

                    s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex);
                    code.append(s);
                }
                // did not find parameter in URL type
                // 参数没有URL类型
                else {
                    String attribMethod = null;

                    // find URL getter method
                    // 找到参数的URL属性 。例如,Invoker 有 `#getURL()` 方法。
                    LBL_PTS:
                    for (int i = 0; i < pts.length; ++i) {
                        Method[] ms = pts[i].getMethods();
                        for (Method m : ms) {
                            String name = m.getName();
                            if ((name.startsWith("get") || name.length() > 3)
                                    && Modifier.isPublic(m.getModifiers())
                                    && !Modifier.isStatic(m.getModifiers())
                                    && m.getParameterTypes().length == 0
                                    && m.getReturnType() == URL.class) { // pubic && getting 方法
                                urlTypeIndex = i;
                                attribMethod = name;
                                break LBL_PTS;
                            }
                        }
                    }
                    // 未找到,抛出异常。
                    if (attribMethod == null) {
                        throw new IllegalStateException("fail to create adaptive class for interface " + type.getName()
                                + ": not found url parameter or url attribute in parameters of method " + method.getName());
                    }

                    // 生成代码:校验 URL 非空
                    // Null point check
                    String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
                            urlTypeIndex, pts[urlTypeIndex].getName());
                    code.append(s);
                    s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
                            urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
                    code.append(s);

                    // 生成 `URL url = arg%d.%s();` 的代码
                    s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod);
                    code.append(s);
                }

                String[] value = adaptiveAnnotation.value();
                // value is not set, use the value generated from class name as the key
                // 没有设置Key,则使用“扩展点接口名的点分隔 作为Key
                if (value.length == 0) {
                    char[] charArray = type.getSimpleName().toCharArray();
                    StringBuilder sb = new StringBuilder(128);
                    for (int i = 0; i < charArray.length; i++) {
                        if (Character.isUpperCase(charArray[i])) {
                            if (i != 0) {
                                sb.append(".");
                            }
                            sb.append(Character.toLowerCase(charArray[i]));
                        } else {
                            sb.append(charArray[i]);
                        }
                    }
                    value = new String[]{sb.toString()};
                }

                // 判断是否有 Invocation 参数
                boolean hasInvocation = false;
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
                        // 生成代码:校验 Invocation 非空
                        // Null Point check
                        String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
                        code.append(s);

                        // 生成代码:获得方法名
                        s = String.format("\nString methodName = arg%d.getMethodName();", i);
                        code.append(s);

                        // 标记有 Invocation 参数
                        hasInvocation = true;
                        break;
                    }
                }

                // 默认拓展名
                String defaultExtName = cachedDefaultName;
                // 获得最终拓展名的代码字符串,例如:
                // 【简单】1. url.getParameter("proxy", "javassist")
                // 【复杂】2. url.getParameter(key1, url.getParameter(key2, defaultExtName))
                String getNameCode = null;
                for (int i = value.length - 1; i >= 0; --i) { // 倒序的原因,因为是顺序获取参数,参见【复杂】2. 的例子
                    if (i == value.length - 1) {
                        if (null != defaultExtName) {
                            if (!"protocol".equals(value[i]))
                                if (hasInvocation) // 当【有】 Invocation 参数时,使用 `URL#getMethodParameter()` 方法。
                                    getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                                else // 当【非】 Invocation 参数时,使用 `URL#getParameter()` 方法。
                                    getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
                            else // 当属性名是 "protocol" ,使用 `URL#getProtocl()` 方法获取。
                                getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
                        } else {
                            if (!"protocol".equals(value[i]))
                                if (hasInvocation)
                                    getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); // 此处的 defaultExtName ,可以去掉的。
                                else
                                    getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
                            else
                                getNameCode = "url.getProtocol()";
                        }
                    } else {
                        if (!"protocol".equals(value[i]))
                            if (hasInvocation)
                                getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                            else
                                getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
                        else
                            getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
                    }
                }

                // 生成代码:获取参数的代码。例如:String extName = url.getParameter("proxy", "javassist");
                code.append("\nString extName = ").append(getNameCode).append(";");
                // check extName == null?
                String s = String.format("\nif(extName == null) " +
                                "throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");",
                        type.getName(), Arrays.toString(value));
                code.append(s);

                // 生成代码:拓展对象,调用方法。例如
                // `com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class)
                //                                                                                                           .getExtension(extName);` 。
                s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
                        type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
                code.append(s);

                // return statement
                if (!rt.equals(void.class)) {
                    code.append("\nreturn ");
                }

                s = String.format("extension.%s(", method.getName());
                code.append(s);
                for (int i = 0; i < pts.length; i++) {
                    if (i != 0)
                        code.append(", ");
                    code.append("arg").append(i);
                }
                code.append(");");
            }

            // 生成方法
            codeBuidler.append("\npublic " + rt.getCanonicalName() + " " + method.getName() + "(");
            for (int i = 0; i < pts.length; i++) {
                if (i > 0) {
                    codeBuidler.append(", ");
                }
                codeBuidler.append(pts[i].getCanonicalName());
                codeBuidler.append(" ");
                codeBuidler.append("arg" + i);
            }
            codeBuidler.append(")");
            if (ets.length > 0) {
                codeBuidler.append(" throws ");  // 异常
                for (int i = 0; i < ets.length; i++) {
                    if (i > 0) {
                        codeBuidler.append(", ");
                    }
                    codeBuidler.append(ets[i].getCanonicalName());
                }
            }
            codeBuidler.append(" {");
            codeBuidler.append(code.toString());
            codeBuidler.append("\n}");
        }

        // 生成类末尾的 `}`
        codeBuidler.append("\n}");

        // 调试,打印生成的代码
        if (logger.isDebugEnabled()) {
            logger.debug(codeBuidler.toString());
        }
        return codeBuidler.toString();
    }

这段代码比较长 大家直接看注释吧…提几个要点

  • 动态生成类的时候会检查方法上的@Adaptive注解,仅对加了注解的方法生成实现的代码,对于其他方法的实现都是直接抛异常.表示不能被调用
  • 为了减少import XXX 这种导包的代码,除了必须的工具类ExtensionLoader.其他生成的代码都使用全类名进行声明,
  • 会在方法的参数签名上寻找Url参数类型 ,如果找到会生成一个校验Url非空的代码,
  • 如果上一步没找到会进一步从传参对象的属性里去找Url属性对象,同时通过getXXX方法去获得Url对象,如果还没找到,代码会抛出异常,这说明Url是必须的一个参数.
  • 如果没有设置Key,即@Adaptive的value值, 则使用“扩展点接口名的点分隔 作为Key .
  • 当指定的key 是"protocol"时 直接从url.getProtocal() 方法中获取参数值.

下面贴一个例子,看看生成的类长啥样

package com.alibaba.dubbo.common.extensionloader.ext1;

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

public class SpiService$Adaptive implements com.alibaba.dubbo.common.extensionloader.ext1.SpiService {
    public void hello(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("spi.service", "spiServiceA");
        if (extName == null) {
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.common.extensionloader.ext1.SpiService) name from url(" + url.toString() + ") use keys([spi.service])");
        }
        com.alibaba.dubbo.common.extensionloader.ext1.SpiService extension = (com.alibaba.dubbo.common.extensionloader.ext1.SpiService) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.extensionloader.ext1.SpiService.class).getExtension(extName);
        extension.hello(arg0);
    }
}

看最后两句代码, 其实是通过getExtension(String name) 方法,传入从Url参数获取到的实际值,从而拿到真正的实现类,实际调用的是真正实现类的方法,这也是一种AOP的思想.

3.2.6 代码编译

代码编译这块就不详细讲解了,从以下代码可以

        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();

这里也是使用的自适应类去生成编译工具类,这里默认使用的是JavassistCompiler 进行编译,从Compile类的SPi注解的值可以看出.编译完毕后就得到了Class对象,从而动态的构造出了一个实现类对象.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值