前言

上一篇文章Dubbo系列-SPI扩展(上),通过对比JDK、Spring、Dubbo对SPI的不同实现,对它们的区别有一个全局的了解,其中Dubbo SPI实现最为复杂,其在整个框架设计与实现上发挥着极大的作用。

本篇会继续深入Dubbo SPI的其它特性,包括Dubbo3引入的作用域模型,及Dubbo SPI扩展点的四大特性(自动包装、自动加载、自适应、自动激活)。

作用域模型

定义

在介绍Dubbo的扩展点时,原来的获取扩展点的静态方法已经标记废弃,如下所示,取而代之的是ApplicationModel的东西进行获取。

@Deprecated
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    return ApplicationModel.defaultModel().getDefaultModule().getExtensionLoader(type);
}

其中ApplicationModel是Dubbo3引入的,其对应着作用域ExtensionScope#APPLICATION,除此之外,还有ExtensionScope#FRAMEWORK、ExtensionScope#MODULE、ExtensionScope#SELF。

SPI扩展点作用域包括

  • ExtensionScope#FRAMEWORK:框架级别作用域,对所有的application和module可见;对应着域模型 FrameworkModel

  • ExtensionScope#APPLICATION:应用级别作用域,对所有module可见;对应着域模型 ApplicationModel

  • ExtensionScope#MODULE:模块级别作用域;对应着域模型 ModuleModel

  • ExtensionScope#SELF:自给自足,为每个作用域创建一个实例,用于特殊的SPI扩展,如ExtensionInjector

每个作用域模型缓存着对应作用域的扩展点的相关信息。可以类比ClassLoader理解。

关于作用域有个可见性,在查找作用域对应的扩展加载器ExtensionLoader时就如ClassLoader的双亲委派,上篇文章有提到,会递归查询上一级的作用域。

例如@SPI注解为ExtensionScope.Module,则获取ExtensionLoader过程ExtensionScope.Module -> Application -> Framework

1)、先ModuleScope缓存中查找,找到返回,找不到则进入2)

2)、再Application缓存中找,找到返回,找不到则匹配注解作用域,匹配则创建ExtensionLoader实例,不匹配进入3)

3)、再Framework缓存中找,找到返回,找不到则匹配注解作用域,匹配则创建ExtensionLoader实例,不匹配进入4)

4)、最后回到了ModuleScope匹配注解作用域,匹配则创建ExtensionLoader实例,不匹配不创建,返回null

对应流程图:


作用

为什么会在Dubbo3的新版本中加入这个作用域模型呢?

1、让Dubbo支持多应用的部署,可能大企业才有诉求,即一个JVM启动两个Dubbo应用

可以参考github issue有关场景

2、从架构设计上,之前都是一个作用域,通过静态类进行属性共享,引入域模型解决了静态属性资源共享、清理的问题

3、分层模型可以将应用的管理和模块的管理进行分开管理


设计与实现

  • ExtentsionAccessor作为扩展的统一访问接口

    1. 用于获取扩展管理器ExtensionDirector

    2. 获取扩展加载器ExtensionLoader

    3. 获取扩展对象(名字、自适应、默认)

  • ExtensionDirector扩展管理器

    1. Class到扩展加载器ExtensionLoader的缓存

    2. Class到作用域ExtensionScope的缓存

    3. 父扩展管理器ExtensionDirector

    4. 所属的模型实例对象scopeModel

    5. 扩展点处理器List<ExtensionPostProcessors>

  • ScopeModel作用域模型抽象父类

    1. 表示内部层次结构的内部id

    2. 父模型ScopeModel

    3. 当前作用域ExtensionScope

    4. 当前作用域模型对应的扩展管理器ExtensionDirector

    5. 当前作用域模型对应的Bean工厂管理ScopeBeanFactory

    6. 当前是否是内部作用域internalScope:boolean

  • 三个不同的作用域模型实现

    1. ModuleModel的构造器需要传入ApplicationModel实例

    2. ApplicationModel构造器需要传入FrameworkModel实例

    3. ApplicationModel和ModuleModel构造器可见性protected;FrameworkModel可见性public


举例

1、Codec2编解码器,一种Codec2编解码器在一个FrameworkModel中只有一个实例;还有如Protocol等。

2、统计服务MetricsService,一个ApplicationModel只有一个实例;FrameworkModel作用域获取不到。

3、Filter过滤器在一个ModuleModel中有一个实例;在ApplicationModel和FrameworkModel作用域获取不到。

SPI四大特性

Dubbo扩展点的四大特性包括:

  • 自动包装

  • 自动加载

  • 自适应

  • 自动激活

不管哪种特性,都是获取SPI接口的扩展点,都需要加载配置文件。

自动包装

在ExtensionLoader加载扩展时,如果发现这个扩展类包含其它的扩展点作为构造函数的参数,则这个扩展类会被当成Wrapper类。

例如ProtocolFilterWrapper,实现了Protocol,同时使用Protocol作为构造器的入参。这是一种装饰器模式,对逻辑进行封装或对子类的增强。

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

一个示例

// 接口
@SPI(value = DubboSimpleSpiImpl1.NAME, scope = ExtensionScope.APPLICATION)
public interface DubboSimpleSpi {
    void sayHello();
}

// 实现1 略

// 实现2
public class DubboSimpleSpiImpl2 implements DubboSimpleSpi {

    public static final String NAME = "dubboSimpleSpiImpl2";

    @Override
    public void sayHello() {
        System.out.println("DubboSimpleSpiImpl2:" + this + " say hello");
    }
}

// 包装类
@Wrapper
public class DubboSimpleSpiWrapper implements DubboSimpleSpi {

    // 依赖注入
    private DubboSimpleSpi dubboSimpleSpi;

    public DubboSimpleSpiWrapper(DubboSimpleSpi dubboSimpleSpi) {
        this.dubboSimpleSpi = dubboSimpleSpi;
    }

    @Override
    public void sayHello() {
        System.out.println("DubboSimpleSpiWrapper sayHello");
        dubboSimpleSpi.sayHello();
    }
}

// 演示类
public class DubboSpiWrapperDemo {

    public static void main(String[] args) {
        // 获取DubboSimpleSpiImpl2.NAME对应的实现类
        DubboSimpleSpi dubboSimpleSpi = ApplicationModel.defaultModel().getExtensionLoader(DubboSimpleSpi.class)
                .getExtension(DubboSimpleSpiImpl2.NAME);
        dubboSimpleSpi.sayHello();
    }

}

配置文件META-INF/dubbo/top.xudj.spi.dubbo.DubboSimpleSpi:

dubboSimpleSpiImpl1=top.xudj.spi.dubbo.DubboSimpleSpiImpl1
dubboSimpleSpiImpl2=top.xudj.spi.dubbo.DubboSimpleSpiImpl2
dubboSimpleSpiWrapper=top.xudj.spi.dubbo.wrapper.DubboSimpleSpiWrapper

注意,也需要配置包装类,它是一个扩展点,只不过是特殊的扩展点:dubboSimpleSpiWrapper=top.xudj.spi.dubbo.wrapper.DubboSimpleSpiWrapper

运行演示类,输出包装类的日志,再调用原扩展点对象的日志:

DubboSimpleSpiWrapper sayHello
DubboSimpleSpiImpl2:top.xudj.spi.dubbo.DubboSimpleSpiImpl2@7920ba90 say hello

原理分析

分析原理之前,先看@Wrapper注解

@Retention(RetentionPolicy.RUNTIME)
public @interface Wrapper {
    String[] matches() default {};
    String[] mismatches() default {};
    int order() default 0;
}

该注解用于决定被标记的类是否要作为某个扩展点的包装类。

先说结论,下面再分析原因:默认可不加注解或加注解无配置,matches和mismatches属性为扩展点的名称(配置文件内容的key),决定哪些扩展点名称会被当作包装类。

下面看原理分析,获取扩展点实现的方法:getExtension(String),即上篇文章分析的根据名称获取扩展,默认有个参数wrap为true:

// org.apache.dubbo.common.extension.ExtensionLoader#getExtension(java.lang.String, boolean)
public T getExtension(String name, boolean wrap) {
	// ...
	instance = createExtension(name, wrap);
	// ...
}

继续看创建扩展:

// org.apache.dubbo.common.extension.ExtensionLoader#createExtension
private T createExtension(String name, boolean wrap) {
    // 获取接口的全部扩展类实现class,然后根据name获取对应的class
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null || unacceptableExceptions.contains(name)) {
        throw findException(name);
    }
    try {
        // 从缓存中获取扩展实例,extensionInstances:ConcurrentMap<Class<?>, Object>
        T instance = (T) extensionInstances.get(clazz);
        if (instance == null) {
            // 核心:调用createExtensionInstance方法创建扩展实例(构造器反射),并放入缓存
            extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
			instance = (T) extensionInstances.get(clazz);
			// ... 对instance进行一系列的处理
        }

        // 是否需要包装,默认为true
        if (wrap) {
            List<Class<?>> wrapperClassesList = new ArrayList<>();
            // 存在缓存的包装类,在getExtensionClasses()文件加载的时候会存入cachedWrapperClasses
            if (cachedWrapperClasses != null) {
                wrapperClassesList.addAll(cachedWrapperClasses);
                // 排序 并 反转
                wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                Collections.reverse(wrapperClassesList);
            }
            if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                // AOP思想的体现
                for (Class<?> wrapperClass : wrapperClassesList) {
                    Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                    // match两种情况为true:1)没有Wrapper注解,2)有注解,matches为空或者matches包含name,且mismatches不包含name
                    boolean match = (wrapper == null)
                            || ((ArrayUtils.isEmpty(wrapper.matches())
                                            || ArrayUtils.contains(wrapper.matches(), name))
                                    && !ArrayUtils.contains(wrapper.mismatches(), name));
                    if (match) {
                        // 包装扩展实例,实例化包装类,并完成"包装类"的DI,类似Spring的Autowired
                        instance = injectExtension(
                                (T) wrapperClass.getConstructor(type).newInstance(instance));
                        // 包装类的后置处理,类似Spring的BeanPostProcessor#postProcessAfterInitialization
                        // 默认会注入实现了ScopeModelAware接口的方法
                        instance = postProcessAfterInitialization(instance, name);
                    }
                }
            }
        }
		// ...
        // 返回扩展实例
        return instance;
    } catch (Throwable t) {
		// ... throw
    }
}

代码省略了部分内容,核心看 if(wrap){},在进入包装扩展处理前,对应的instance已经创建完成了。

首先获取cachedWrapperClasses,这是一个关键成员变量,类型Set<Class<?>>,缓存wrapper的Class,它从何而来?下面再详细介绍。

对示例来说就是top.xudj.spi.dubbo.wrapper.DubboSimpleSpiWrapper对应的Class,如果存在多个,进行排序(正序),根据顺序进行多重包装。这就体现了AOP的思想

最终会不会进行包装,还要看包装类上的注解@Wrapper,上面提到过,两种情况会进行包装

  • 类上没有@Wrapper注解;

  • 类上有@Wrapper注解,matches为空或者matches包含name,且mismatches不包含name。这个name是扩展点名称

一旦进行包装,也会对包装类的Setter方法进行DI注入(Inject spi extension 和 scope bean),体现了IoC的思想;以及后置处理。

划重点cachedWrapperClasses缓存从何而来?

回到createExtension方法的入口有个getExtensionClasses(),该方法会对配置文件进行解析,跟进getExtensionClasses()方法:

private Map<String, Class<?>> getExtensionClasses() {
    // cachedClasses扩展点实现的Class缓存,不包括标记@Adaptive注解和Wrapper扩展类
    Map<String, Class<?>> classes = cachedClasses.get();
	// ... 此处有cachedClasses双检锁
    // 获取当前接口的全部的扩展类Class
    classes = loadExtensionClasses();
    // ... 加入cachedClasses中
    return classes;
}

继续看loadExtensionClasses();

private Map<String, Class<?>> loadExtensionClasses() throws InterruptedException {
    checkDestroyed();
    // @SPI注解的value值,作为默认的扩展名,存入变量cachedDefaultName:String中
    cacheDefaultExtensionName();

    Map<String, Class<?>> extensionClasses = new HashMap<>();
    // 加载扩展类的策略,有三种:META-INF/dubbo/internal、META-INF/dubbo、META-INF/services
    for (LoadingStrategy strategy : strategies) {
        loadDirectory(extensionClasses, strategy, type.getName());
        // compatible with old ExtensionFactory
        if (this.type == ExtensionInjector.class) {
            loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());
        }
    }
    return extensionClasses;
}

一步步下去,按ClassLoader加载接口命名的配置文件(可能多个),最后调用到:

// org.apache.dubbo.common.extension.ExtensionLoader#loadClass
private void loadClass(
        ClassLoader classLoader,
        Map<String, Class<?>> extensionClasses,
        java.net.URL resourceURL,
        Class<?> clazz,
        String name,
        boolean overridden) {
	// 当前class实现了接口type
    if (!type.isAssignableFrom(clazz)) {
        // ... throw 
    }

    // clazz是否是激活的,不激活则直接返回,由@Activate的onClass属性决定
    boolean isActive = loadClassIfActive(classLoader, clazz);
    if (!isActive) {
        return;
    }

    if (clazz.isAnnotationPresent(Adaptive.class)) {
        // clazz是标有Adaptive注解,将clazz指向缓存 cachedAdaptiveClass:Class<?>
        // 几种策略都是overridden=true,可覆盖
        cacheAdaptiveClass(clazz, overridden);
    } else if (isWrapperClass(clazz)) {
        // isWrapperClass:满足构造器的要求(扩展点作为构造器的入参),将class加入缓存 cachedWrapperClasses:Set<Class<?>>
        cacheWrapperClass(clazz);
    } else {
        // 此时,==》clazz不是Adaptive,也不是Wrapper,是普通的扩展类
        if (StringUtils.isEmpty(name)) {
            // 使用类名作为扩展名
            name = findAnnotationName(clazz);
            if (name.length() == 0) {
                throw new IllegalStateException("No such extension name for the class " + clazz.getName()
                        + " in the config " + resourceURL);
            }
        }
        // 逗号分割name,正常就一个
        String[] names = NAME_SEPARATOR.split(name);
        if (ArrayUtils.isNotEmpty(names)) {
            // 存在@Activate注解时,缓存第一个name(一般也就一个名字)到cachedActivates:Map<String, Object>
            cacheActivateClass(clazz, names[0]);
            for (String n : names) {
                // 缓存name到cachedNames:ConcurrentMap<Class<?>, String>
                cacheName(clazz, n);
                // 重要,将class放入extensionClasses
                saveInExtensionClass(extensionClasses, clazz, n, overridden);
            }
        }
    }
}

该方法是重点,处理的缓存信息关系着SPI的四大特性。

该方法核心分成2大步骤:

  1. 首先判断class是否是激活的,激活表示可以获取到该扩展。当使用@Activate且配置了onClass属性且配置的onClass存在className不能ClassLoader加载,则不是激活的,否则就是激活的。

  2. 分三种情况依次处理:

    1. 如果类是有@Adaptive注解,将Class存入缓存cachedAdaptiveClass:Class<?>中,多个会进行覆盖。

    2. 如果有个仅接口类型入参的构造器,被认定为包装类,将Class缓存到cachedWrapperClasses:Set<Class<?>>中

    3. 普通的扩展类处理,将扩展点名称到Class的映射放入缓存cachedClasses:Holder<Map<String, Class<?>>>中,名称相同会覆盖;同时标记@Activate的类会额外进行缓存,将扩展点名称到注解的信息缓存到cachedActivates:Map<String, Object>中,

补充说明下,只有在cachedClasses缓存中的Class,才能通过getExtension(String name) 进行获取到扩展。具体可以看上面createExtension方法。

至此,分析了配置文件的加载与解析,得到了包装类信息,存入缓存cachedWrapperClasses中,在getExtension(String name) 获取扩展时,完成对原扩展的包装。


自动加载

上面的自动包装特性是通过构造函数完成的,还有一种是setter方法进行注入,获取类型是参数类型,名称通过方法名截取,Dubbo SPI会自动注入对应的实例,它是一种IoC思想。接下来先看一个示例。

一个示例

// 接口
@SPI
public interface DubboSpiInject {
    void echo(String msg);
    void setDubboAdaptiveSpi(DubboAdaptiveSpi dubboAdaptiveSpi);
}

// 实现
public class DubboSpiInjectImpl implements DubboSpiInject {

    public static final String NAME = "dubboSpiInject";
    @Override
    public void echo(String msg) {
        System.out.println("DubboSpiInjectImpl echo:" + msg);
    }
    /**
     * 自动注入:先查询ScopeBeanFactory,再查询ExtensionLoader,查询ExtensionLoader时,查找自适应扩展实例
	 * DubboAdaptiveSpi:可以通过SPI获取到的接口类型
     */
    @Override
    public void setDubboAdaptiveSpi(DubboAdaptiveSpi dubboAdaptiveSpi) {
        System.out.println("DubboSpiInjectImpl setDubboAdaptiveSpi:" + dubboAdaptiveSpi);
    }
}

// 演示
public class DubboSpiInjectDemo {
    public static void main(String[] args) {
        ExtensionLoader<DubboSpiInject> extensionLoader = ApplicationModel.defaultModel().getExtensionLoader(DubboSpiInject.class);
        DubboSpiInject dubboSpiInject = extensionLoader.getExtension(DubboSpiInjectImpl.NAME);
        dubboSpiInject.echo("hello");
    }
}

配置文件,META-INF/dubbo/top.xudj.spi.dubbo.inject.DubboSpiInject,内容:

dubboSpiInject=top.xudj.spi.dubbo.inject.DubboSpiInjectImpl

运行演示类,输出:

DubboSpiInjectImpl setDubboAdaptiveSpi:top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi$Adaptive@293a5bf6
DubboSpiInjectImpl echo:hello

通过输出日志可以看出,setDubboAdaptiveSpi方法成功完成了对象注入。

原理分析

演示类中获取扩展点方法仍是extensionLoader.getExtension(String name);得到,在创建对应的扩展点时:

private T createExtension(String name, boolean wrap) {
    // 获取接口的全部扩展类实现class,然后根据name获取对应的class
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null || unacceptableExceptions.contains(name)) {
        throw findException(name);
    }
    try {
        // 从缓存中获取扩展实例,extensionInstances:ConcurrentMap<Class<?>, Object>
        T instance = (T) extensionInstances.get(clazz);
        if (instance == null) {
            // 核心:调用createExtensionInstance方法创建扩展实例(构造器反射),并放入缓存
            extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
			// ...
            // DI,注入扩展实例的依赖,类似Spring的Autowired,只不过它是"setter注入"(Inject spi extension 和 scope bean)
            injectExtension(instance);
            // ...
        }

		// ...
        // 返回扩展实例
        return instance;
    } catch (Throwable t) {
        // ... throw 
    }
}

重点在injectExtension(instance);方法:

private T injectExtension(T instance) {
    if (injector == null) {
        return instance;
    }
    try {
        for (Method method : instance.getClass().getMethods()) {
            // 1、查找方法
			if (!isSetter(method)) {
                continue;
            }
			// 2、过滤不需要注入的情况
            if (method.isAnnotationPresent(DisableInject.class)) {
                continue;
            }
            // When spiXXX implements ScopeModelAware, ExtensionAccessorAware,
            // the setXXX of ScopeModelAware and ExtensionAccessorAware does not need to be injected
            if (method.getDeclaringClass() == ScopeModelAware.class) {
                continue;
            }
            if (instance instanceof ScopeModelAware || instance instanceof ExtensionAccessorAware) {
                if (ignoredInjectMethodsDesc.contains(ReflectUtils.getDesc(method))) {
                    continue;
                }
            }
            Class<?> pt = method.getParameterTypes()[0];
            if (ReflectUtils.isPrimitives(pt)) {
                continue;
            }

            try {
                String property = getSetterProperty(method);
                // 3、进行注入
                Object object = injector.getInstance(pt, property);
                if (object != null) {
                    method.invoke(instance, object);
                }
            } catch (Exception e) {
                // ... log
            }
        }
    } catch (Exception e) {
        logger.error(COMMON_ERROR_LOAD_EXTENSION, "", "", e.getMessage(), e);
    }
    return instance;
}

方法主要包括3步:

  1. 查找所有setter方法;

  2. 对于部分情况不会进行注入,包括方法有@DisableInject注解、类实现了ScopeModelAware, ExtensionAccessorAware继承下来的方法、参数类型属于基础类型或String等都会跳过;

  3. 进行注入,依赖于injector:ExtensionInjector对象,在构造ExtensionLoader时创建,最终会依次调用如下2个实现:ScopeBeanExtensionInjector、SpiExtensionInjector的实现方法:org.apache.dubbo.common.extension.ExtensionInjector#getInstance

  • 对于ScopeBeanExtensionInjector:

依赖于beanFactory:ScopeBeanFactory变量,维护着当前作用域下的bean,通过ScopeModel作用域模型获取所得。在ScopeModel的initialize方法中初始化 beanFactory = new ScopeBeanFactory(parent != null ? parent.getBeanFactory() : null, extensionDirector);。类似于Spring的BeanFactory,也存在parent,有registerBean及getBean等api。

  • 对于SpiExtensionInjector

依赖于ExtensionLoader,如下,通过type得到ExtensionLoader,调用getAdaptiveExtension()获取到自适应扩展。

@Override
public <T> T getInstance(final Class<T> type, final String name) {
    if (!type.isInterface() || !type.isAnnotationPresent(SPI.class)) {
        return null;
    }
    ExtensionLoader<T> loader = extensionAccessor.getExtensionLoader(type);
    if (loader == null) {
        return null;
    }
    // 这里注意,即使getSupportedExtensions有,但可能自适应类扩展没有(一是没有@Adaptive注解的类,二是接口没有@Adaptive注解的方法)
    if (!loader.getSupportedExtensions().isEmpty()) {
        return loader.getAdaptiveExtension();
    }
    return null;
}

至此,完成了扩展实例的setter方法依赖注入DI。最后提到的自适应扩展是什么呢?往下看自适应特性。


自适应

在SPI扩展中,有个注解@Adaptive,用来标记扩展点类或者接口方法,来实现动态的通过URL确定使用哪个具体的实现类。仍然先通过一个示例看看这个注解怎么用。

一个示例

// 接口
@SPI(DubboAdaptiveSpiImpl1.NAME)
public interface DubboAdaptiveSpi {
    @Adaptive(value = {"adaptive", "adaptiveBack"})
    String echo(URL url, String s);

    default void defaultPlay(String s) {
        System.out.println("DubboAdaptiveSpi play");
    }
    void play(String s);
}

// 实现1
public class DubboAdaptiveSpiImpl1 implements DubboAdaptiveSpi {
    public static final String NAME = "impl1";

    @Override
    public String echo(URL url, String s) {
        System.out.println("DubboAdaptiveSpiImpl1 echo url:" + url.toFullString() + ", s:" + s);
        return this.getClass().getSimpleName();
    }
    @Override
    public void play(String s) {
        System.out.println("DubboAdaptiveSpiImpl1 play s:" + s);
    }
}

// 实现2
public class DubboAdaptiveSpiImpl2 implements DubboAdaptiveSpi {
    public static final String NAME = "impl2";

    @Override
    public String echo(URL url, String s) {
        System.out.println("DubboAdaptiveSpiImpl2 echo url:" + url.toFullString() + ", s:" + s);
        return this.getClass().getSimpleName();
    }
    @Override
    public void play(String s) {
        System.out.println("DubboAdaptiveSpiImpl2 play s:" + s);
    }
}

// 演示类
public class DubboAdaptiveSpiDemo {
    public static void main(String[] args) {
        ExtensionLoader<DubboAdaptiveSpi> extensionLoader = ApplicationModel.defaultModel()
                .getExtensionLoader(DubboAdaptiveSpi.class);
        DubboAdaptiveSpi adaptiveExtension = extensionLoader.getAdaptiveExtension();

        // 构造参数
        Map<String, String> map = new HashMap<>();
		map.put("adaptive", "impl2"); // 使用DubboAdaptiveSpiImpl2,url:dubbo://1.2.3.4:1010/path1?adaptive=impl2
        URL url = new ServiceConfigURL("dubbo", "1.2.3.4", 1010, "path1", map);
        adaptiveExtension.echo(url, "hello");
    }

}

配置文件:META-INF/dubbo/top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi,内容:

impl1=top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpiImpl1
impl2=top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpiImpl2

运行演示类,输出:DubboAdaptiveSpiImpl2 echo url:dubbo://1.2.3.4:1010/path1?adaptive=impl2, s:hello

先说结论:@Adaptive注解方式:

  • 方法上面,会动态生成并编译一个Class类,内部的逻辑往下看;extensionLoader.getAdaptiveExtension()得到的便是生成的类。

  • 类上面,该类作为自适应类,内部逻辑可以自行定义;extensionLoader.getAdaptiveExtension()得到的便是该类。

对于注解在方法上的情况,生成的类的方法内部逻辑如下:通过url获取参数得到扩展的名称,通过扩展名称来决定使用哪个扩展实现类,所以叫自适应

内部方法实现的逻辑分为2步:

1、获取扩展点name = url.getParameter(${key}, ${default})

${key}:首先是@Adaptive的value值作为key(多个依次为key),如果没配置则使用接口名转化(DubboAdaptiveSpi -> dubbo.adaptive.spi作为key),url通过key得到得value值作为对应的扩展点名称。如果key没有对应的value,则使用${default},${default}为@SPI注解的value值

2、根据扩展点name,调用getExtension(String name)得到扩展点实现,并调用对应的实现方法。

生成的自适应类是什么样子呢?示例生成的类如下:

package top.xudj.spi.dubbo.adaptive;
import org.apache.dubbo.rpc.model.ScopeModel;
import org.apache.dubbo.rpc.model.ScopeModelUtil;

public class DubboAdaptiveSpi$Adaptive implements top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi {

public void defaultPlay(java.lang.String arg0)  {
	throw new UnsupportedOperationException("The method public default void top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi.defaultPlay(java.lang.String) of interface top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi is not adaptive method!");
}

public void play(java.lang.String arg0)  {
	throw new UnsupportedOperationException("The method public abstract void top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi.play(java.lang.String) of interface top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi is not adaptive method!");
}

public java.lang.String echo(org.apache.dubbo.common.URL arg0, java.lang.String arg1)  {
	if (arg0 == null) 
		throw new IllegalArgumentException("url == null");
	org.apache.dubbo.common.URL url = arg0;
	String extName = url.getParameter("adaptive", url.getParameter("adaptiveBack", "impl1"));
	if(extName == null) 
		throw new IllegalStateException("Failed to get extension (top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi) name from url (" + url.toString() + ") use keys([adaptive, adaptiveBack])");
	ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi.class);
	
	top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi extension = (top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi)scopeModel.getExtensionLoade(top.xudj.spi.dubbo.adaptive.DubboAdaptiveSpi.class)
		.getExtension(extName);
	return extension.echo(arg0, arg1);
}

可以看出:

  • 该类实现了对应的接口,类名为:接口名 + $Adaptive

  • @Adaptive注解在echo方法,只会处理echo方法,其它方法均会抛出异常

原理分析

先看下@Adaptive注解,比较简单就一个value数组,存放url用于匹配的参数key。

public @interface Adaptive {
    String[] value() default {};
}

生成的类的情况上面分析差不多了,这里看下,extensionLoader.getAdaptiveExtension();具体做了什么?

public T getAdaptiveExtension() {
    checkDestroyed();
    // 先从缓存中取
    Object instance = cachedAdaptiveInstance.get();
    // ... 双重检查锁
	// 重要方法创建自适应扩展点实现
	instance = createAdaptiveExtension();
	// ... 设置到缓存中
	cachedAdaptiveInstance.set(instance);
    return (T) instance;
}

这里可以看出,该方法无参,所以每个扩展点接口只有一个对应的自适应实现类

继续看createAdaptiveExtension()创建:

private T createAdaptiveExtension() {
    try {
        // 创建自适应扩展类
        T instance = (T) getAdaptiveExtensionClass().newInstance();
        // 前置处理
        instance = postProcessBeforeInitialization(instance, null);
        // DI,setter方法注入
        injectExtension(instance);
        // 后置处理,默认处理ScopeModelAware接口的set方法
        instance = postProcessAfterInitialization(instance, null);
        // 如果是Lifecycle接口的实现类,调用初始化方法
        initExtension(instance);
        return instance;
    } catch (Exception e) {
        throw new IllegalStateException(
                "Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
    }
}

主要看T instance = (T) getAdaptiveExtensionClass().newInstance();,其它都见过。

private Class<?> getAdaptiveExtensionClass() {
    // 加载配置文件,几种情况都一样
    getExtensionClasses();
    // 如果cachedAdaptiveClass不为空,直接返回,cachedAdaptiveClass是@Adaptive注解在了实现类上
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    // 创建自适应类
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

调用getExtensionClasses加载配置文件,在自动包装特性原理分析时已经讲述过。SPI都离不开这个操作。

这里有个判断cachedAdaptiveClass也很重要,cachedAdaptiveClass(解析文件时处理的)如果不为null,说明存在类上有@Adaptive注解(多个的话默认进行覆盖),直接使用该Class即可。没有的话最后调用createAdaptiveExtensionClass()创建自适应Class:

private Class<?> createAdaptiveExtensionClass() {
	// ...
    // 生成自适应类的代码,然后通过Compiler编译成Class
    String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
    org.apache.dubbo.common.compiler.Compiler compiler = extensionDirector
            .getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class)
            .getAdaptiveExtension();
    return compiler.compile(type, code, classLoader);
}

通过AdaptiveClassCodeGenerator完成代码生成,再通过Compiler的扩展点实现完成compile编译成Class。

这里注意下:AdaptiveClassCodeGenerator的generate()里有个判断,需要当前的扩展点类type中有方法标记了@Adaptive注解,否则抛出异常。

至此,自适应的扩展实现完成。


自动激活

自适应特性是动态寻找实现类的方式,比较灵活,但只能激活一个具体的实现,上面三大特性都是激活一个具体的实现。如果像Filter一样,需要多个类同时激活呢,就需要用到SPI的最后一个特性:自动激活。

对于自动激活的注解@Activate,因为这个注解相对复杂,先看注解:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
    String[] group() default {};
	// URL parameter keys
    String[] value() default {};
    @Deprecated
    String[] before() default {};
    @Deprecated
    String[] after() default {};
	// 正序排序,值越小越靠前
    int order() default 0;
    String[] onClass() default {};
}
  • group:可以通过group进行分组获取,具体看ExtensionLoader的入参

  • value:配置url参数,支持多个key或配置”key:value“形式

  • before和after在dubbo2.7.0版本之后废弃,用于排序

  • order:排序,默认0,值越小越靠前

  • onClass:决定Class是否是激活的,默认是,具体看上文有提到。

一个示例

// 接口
@SPI
public interface DubboActivateSpi {
    void echo(URL url, String msg);
}

// 实现1
@Activate(group = "group1", value = {"key1:impl1", "key"})
public class DubboActivateSpiImpl1 implements DubboActivateSpi {
    public static final String NAME = "impl1";

    @Override
    public void echo(URL url, String msg) {
        System.out.println("DubboActivateSpiImpl1 echo url: " + url + ", msg: " + msg);
    }
}

// 实现2
@Activate(group = "group1", value = {"key2:impl2", "key"})
public class DubboActivateSpiImpl2 implements DubboActivateSpi {
    public static final String NAME = "impl2";

    @Override
    public void echo(URL url, String msg) {
        System.out.println("DubboActivateSpiImpl2 echo url: " + url + ", msg: " + msg);
    }
}

// 演示类
public class DubboActivateSpiDemo {

    public static void main(String[] args) {
        ExtensionLoader<DubboActivateSpi> extensionLoader = ApplicationModel.defaultModel().getExtensionLoader(DubboActivateSpi.class);
        // 激活扩展实例
        List<DubboActivateSpi> activateExtensions = new ArrayList<>();
        // activateExtensions = extensionLoader.getActivateExtensions(); // 获取全部

        // 获取特定的激活扩展,根据group和key1
        Map<String, String> map = new HashMap<>();
        // map.put("key1", "impl1");     // DubboActivateSpiImpl1作为非默认的激活扩展
        // map.put("key1", "impl2");       // DubboActivateSpiImpl2作为非默认的激活扩展
        // map.put("key1", "impl1,impl2");   // DubboActivateSpiImpl1和DubboActivateSpiImpl2作为非默认的激活扩展
        // map.put("key1", "xxx");         // 空,无非默认,无法匹配默认
        URL url = new ServiceConfigURL("dubbo", "1.2.3.4", 1010, "path1", map);
        activateExtensions = extensionLoader.getActivateExtension(url, "key1", "group1");

        // map.put("key", "xxx");   // DubboActivateSpiImpl1和DubboActivateSpiImpl2作为默认的激活扩展
        // map.put("key", "xxx,-default");   // 空,无非默认
        // map.put("key", "");     // 空,没有激活扩展,无法匹配默认,也无法匹配非默认
        // activateExtensions = extensionLoader.getActivateExtension(url, "key", "group1");
        activateExtensions.forEach(activateExtension -> activateExtension.echo(url, "hello"));
    }
}

配置文件:META-INF/dubbo/top.xudj.spi.dubbo.activate.DubboActivateSpi,内容

impl1=top.xudj.spi.dubbo.activate.DubboActivateSpiImpl1
impl2=top.xudj.spi.dubbo.activate.DubboActivateSpiImpl2

运行演示类,输出:DubboActivateSpiImpl1 echo url: dubbo://1.2.3.4:1010/path1?key1=impl1, msg: hello

原理分析

通过extensionLoader.getActivateExtension(url, "key1", "group1");看:

public List<T> getActivateExtension(URL url, String key, String group) {
    String value = url.getParameter(key);
    return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
}

调用重载方法,将key转成了url对应的value值,多个用逗号分隔:

public List<T> getActivateExtension(URL url, String[] values, String group) {
    checkDestroyed();
    // solve the bug of using @SPI's wrapper method to report a null pointer exception.
    // 用于排序的map,临时存储
    Map<Class<?>, T> activateExtensionsMap = new TreeMap<>(activateComparator);
    List<String> names = values == null
            ? new ArrayList<>(0)
            : Arrays.stream(values).map(StringUtils::trim).collect(Collectors.toList());
    // value值集合,为啥要name命名,应该是因为这个值是扩展点的名字
    Set<String> namesSet = new HashSet<>(names);

    // 1、value中不包括 -default 的值情况,这一步拿到所有default的扩展点(不在namesSet中的其它的),后面用
    if (!namesSet.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
        if (cachedActivateGroups.size() == 0) {
            synchronized (cachedActivateGroups) {
                // cache all extensions
                if (cachedActivateGroups.size() == 0) {
                    // 加载配置文件
                    getExtensionClasses();

                    // 遍历所有的标有@Activate注解的信息(每个类的@Activate注解)
                    for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
                        // name为扩展点的名字,activate为扩展点的注解信息
                        String name = entry.getKey();
                        Object activate = entry.getValue();

                        // 注解配置的分组和值
                        String[] activateGroup, activateValue;

                        if (activate instanceof Activate) {
                            activateGroup = ((Activate) activate).group();
                            activateValue = ((Activate) activate).value();
                        } else if (Dubbo2CompactUtils.isEnabled()
                                && Dubbo2ActivateUtils.isActivateLoaded()
                                && Dubbo2ActivateUtils.getActivateClass().isAssignableFrom(activate.getClass())) {
                            activateGroup = Dubbo2ActivateUtils.getGroup((Annotation) activate);
                            activateValue = Dubbo2ActivateUtils.getValue((Annotation) activate);
                        } else {
                            continue;
                        }
                        // 扩展点的名字对应的group配置
                        cachedActivateGroups.put(name, new HashSet<>(Arrays.asList(activateGroup)));

                        // 根据注解配置的value内容,创建不规则的(或称为“锯齿形”)二维数组
                        String[][] keyPairs = new String[activateValue.length][];
                        for (int i = 0; i < activateValue.length; i++) {
                            if (activateValue[i].contains(":")) {
                                keyPairs[i] = new String[2];
                                String[] arr = activateValue[i].split(":");
                                keyPairs[i][0] = arr[0];
                                keyPairs[i][1] = arr[1];
                            } else {
                                keyPairs[i] = new String[1];
                                keyPairs[i][0] = activateValue[i];
                            }
                        }
                        // 扩展点的名字对应的value配置
                        cachedActivateValues.put(name, keyPairs);
                    }
                }
            }
        }

        // traverse all cached extensions
        cachedActivateGroups.forEach((name, activateGroup) -> {
            // 如果入参group为空,或者注解group不为空且包含入参group 且 name 和 -name 不在入参value中 且 注解配置的value是否和url匹配(存在)
            // so,作为默认的扩展点的重要条件:1)、group匹配上、2)、扩展点的名称不在入参values中;3)、注解的value值(key或key-value)和url能匹配上
            if (isMatchGroup(group, activateGroup)
                    && !namesSet.contains(name)
                    && !namesSet.contains(REMOVE_VALUE_PREFIX + name)
                    && isActive(cachedActivateValues.get(name), url)) {
                // 获取对应扩展点,和普通的获取方式一样。
                activateExtensionsMap.put(getExtensionClass(name), getExtension(name));
            }
        });
    }

    // 2、包括 default 的值,注意顺序
    if (namesSet.contains(DEFAULT_KEY)) {
        // default的位置影响排序
        // ArrayList有序集合
        ArrayList<T> extensionsResult = new ArrayList<>(activateExtensionsMap.size() + names.size());
        for (String name : names) {
            // 扩展name 以-开头。这里的namesSet判断有点冗余了。。。
            if (name.startsWith(REMOVE_VALUE_PREFIX) || namesSet.contains(REMOVE_VALUE_PREFIX + name)) {
                continue;
            }
            // name为default,直接添加所有的扩展点
            if (DEFAULT_KEY.equals(name)) {
                extensionsResult.addAll(activateExtensionsMap.values());
                continue;
            }
            // 存在扩展点,添加
            if (containsExtension(name)) {
                extensionsResult.add(getExtension(name));
            }
        }
        return extensionsResult;
    } else {
        // 3、不包括 default 的值,使用activateExtensionsMap对应的顺序
        // add extensions, will be sorted by its order
        for (String name : names) {
            // 扩展name 以-开头,跳过
            if (name.startsWith(REMOVE_VALUE_PREFIX) || namesSet.contains(REMOVE_VALUE_PREFIX + name)) {
                continue;
            }
            // 进不来
            if (DEFAULT_KEY.equals(name)) {
                continue;
            }
            // 存在扩展点,添加
            if (containsExtension(name)) {
                activateExtensionsMap.put(getExtensionClass(name), getExtension(name));
            }
        }
        return new ArrayList<>(activateExtensionsMap.values());
    }
}

该方法为获取激活扩展实现的核心且复杂方法,可以分成3大步骤看:

1、vlaues中没有“-default”,那么处理defalut的激活类,所谓default是排除values之外的扩展点名称;

2、values中有“default”,根据default的位置来排序所有激活点实例,例如ext1,default,ext2。而default的激活类来自步骤1;

3、values中没有“default”,则在default的基础上,添加values中所有的扩展点,通过getExtension(String name)获取。

关于第1步稍微复杂,哪些会作为defalut的激活扩展类呢?对应逻辑:

// ...	
cachedActivateGroups.forEach((name, activateGroup) -> {
    if (isMatchGroup(group, activateGroup)
            && !namesSet.contains(name)
            && !namesSet.contains(REMOVE_VALUE_PREFIX + name)
            && isActive(cachedActivateValues.get(name), url)) {
        // 获取对应扩展点,和普通的获取方式一样。
        activateExtensionsMap.put(getExtensionClass(name), getExtension(name));
    }
});

这个if判断,决定作为默认的扩展点的重要逻辑包括:1)、group匹配上、2)、扩展点的名称不在入参values中;3)、注解的value值(key或key-value)和url能匹配上。

所以,@Activate注解的group和value决定着是否作为default自动激活扩展类的关键点。

关于上述代码中keyPairs变量,作为一个二维数组,它可能是一个不规则的(或称为“锯齿形”)二维数组,如对于注解@Activate(group = "group1", value = {"key1:impl1", "key"});keyPairs=[[key1][value2]],[[key]]

至此,获取自动激活的扩展类分析完,它允许通过getActivateExtension方法获取多个满足条件的扩展类

四大特性小结

四大特性对比:

四大特性

对应获取api

注解

是否得到多个

是否需要定义额外的扩展类

自动包装

getExtension

@SPI @Wrapper

1

需要定义包装类,并加入配置文件中

自动加载

getExtension

getAdaptiveExtension

getActivateExtension

@SPI

-

-

自适应

getAdaptiveExtension

@SPI @Adaptive

1

如果注解类上,这个类就是定义的自适应类,并加入配置文件中

自动激活

getActivateExtension

@SPI @Activate

不需要

SPI四大特性底层都依赖于getExtension(String name)获取对应的扩展点实例,因为其实都是在获取扩展点实现

ExtensionLoader的10大缓存:

变量名

类型

赋值时机

使用时机

说明

cachedNames

ConcurrentMap

<Class<?>, String>

解析配置文件时

getExtensionName(Class)

可以通过Class得到扩展点名称

cachedClasses

Holder<Map<String, Class<?>>>

解析配置文件时

getExtensionClasses(),加载接口的扩展类时,所有获取扩展点的方法都用得到

扩展点名称到Class的映射,注意:不包括标记@Adaptive注解和被判断为Wrapper扩展类

cachedActivates

Map<String, Object>

解析配置文件时

getActivateExtension

标记@Activate注解的扩展缓存。扩展点名称到Activate注解信息的映射

cachedActivateGroups

Map<String, Set<String>>

getActivateExtension

getActivateExtension

扩展点名称到@Activate注解的group的映射

cachedActivateValues

Map<String,

String[][]>

getActivateExtension

getActivateExtension

扩展点名称到@Activate注解的value的映射。二维数组的第一维长度对应value的大小

cachedInstances

ConcurrentMap

<String,

Holder<Object>>

getExtension

getExtension

扩展点名称到扩展点实例的映射

cachedAdaptiveInstance

Holder<Object>

getAdaptiveExtension

getAdaptiveExtension

接口的自适应扩展点实例的映射

cachedAdaptiveClass

Class<?>

解析配置文件时

getAdaptiveExtension

如果实现类上为@Adaptive标记,那么会加入该缓存,该实现类会作为自适应类

cachedDefaultName

String

进入解析配置文件之前,loadExtensionClasses

方法

getDefaultExtension

默认扩展点名称,@SPI注解的value值

cachedWrapperClasses

Set<Class<?>>

解析配置文件时

getExtension

缓存包装类,就是扩展点实现了接口同时构造器入参也是该接口类型,装饰器模式

总结

本文介绍了Dubbo3引入的作用域模型;然后介绍了SPI的四大特性;这两块内容在整个Dubbo框架中发挥着至关重要的作用。Dubbo是微内核+扩展的设计,框架内部的一些实现本身就是基于SPI扩展进行实现的。所以了解Dubbo的扩展对于理解和学习Dubbo都是前提。