SPI的全称是Service Provider Interface,是JDK内置的动态加载实现扩展点的机制,通过SPI可以动态获取接口的实现类,属于一种设计理念。
系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。如果代码中引用了特定的实现类,那么就违反了可插拔的原则。为了进行实现的替换,需要对代码进行修改。需要一种服务发现机制,以实现在模块装配时无需在程序中动态指定。最常见的使用案例有JDBC、Dubbo和Spring。
笔者在代码中最常用的就是将策略设计模式+SPI进行组合,不直接对接口的实现类进行代码的硬编,可以根据业务的实际使用选择对应的实现类。
SPI由三个组件构成,Service、Service Provider以及ServiceLoader,下面是一个简单例子:
- // 1、使用SPI时先定义好接口
- package com.theone.feather
-
- public interface SendService {
-
- String say();
- }
-
- // 2、创建实现类
- package com.theone.feather;
-
- public class HelloSendService implements SendService {
- @Override
- public String say() {
- return "Hello SPI";
- }
- }
-
- // 3、在classpath下创建META-INF/services目录,添加文件com.theone.feather.SendService
- // 文件名要和接口全路径一样, 里面内容填写实现类的全路径,每个路径各占一行
-
- // 4、通过ServiceLoader调用
- @SpringBootApplication
- public class TheOneApplication implements CommandLineRunner {
-
- public static void main(String[] args) {
- SpringApplication.run(TheOneApplication.class, args);
- }
-
- @Override
- public void run(String... args) throws Exception {
- ServiceLoader
loader = ServiceLoader.load(SendService.class); - for(SendService sendService : loader) {
- System.out.println(sendService.say());
- }
- }
- }
从例子也可以看出,传统的SPI迭代时会遍历所有的实现,需要定制化能够做到根据条件自动加载某个实现。
在聊Dubbo的SPI机制的时候,需要说下开闭原则,开闭原则就是指对扩展开放,修改关闭,尽量通过扩展软件的模块、类、方法,来实现功能的变化,而不是通过修改已有的代码来完成。这样做就可以大大降低因为修改代码而给程序带来的出错率。
对于Dubbo而言,涉及到服务的RPC调用以及服务治理相关的功能需要抽象出来,而对于具体的实现开放出来,由用户去根据自身业务需要随意扩展(例如下图中各个功能的扩展)。

所以在Dubbo中,会经常看到下面的逻辑,分别是自适应扩展点、指定名称的扩展点、激活扩展点
- ExtensionLoader.getExtensionLoader(xxx.class).getAdaptiveExtension();
- ExtensionLoader.getExtensionLoader(xxx.class).getExtension(name);
- ExtensionLoader.getExtensionLoader(xxx.class).getActivateExtension(url, key);
比如在Protocol的获取中,会有
- Protocol protocol = ExtensionLoader
- .getExtensionLoader(Protocol.class)
- .getAdaptiveExtension();
Dubbo根据你的相关配置,找到具体的Protocol实现类,并实例化具体的对象。所以总结来说,Extension就是指某个功能的扩展实现,可能是Dubbo自带的,也可能是业务自己实现的。
正常来说,我们用SPI都是把接口的实现的全路径写在文件里面,解析出来之后再通过反射的方式去实例化这个类,然后将这个类放在集合里面存储起来。Dubbo也是这样的操作,而且它还是实现了IOC和AOP的功能。IOC 就是说如果这个扩展类依赖其他属性,Dubbo 会自动的将这个属性进行注入。这个功能如何实现了?一个常见思路是获取这个扩展类的 setter 方法,调用 setter 方法进行属性注入。AOP 指的是什么了?这个说的是 Dubbo 能够为扩展类注入其包装类。比如 DubboProtocol 是 Protocol 的扩展类,ProtocolListenerWrapper 是 DubboProtocol 的包装类。
下文介绍时,每个功能会用扩展点指代,而对应的实现则是扩展来指代。
在了解Dubbo的SPI机制之前,有几个注解我们要先了解下
@SPI注解用于标记一个扩展点接口,该注解提供了扩展点的默认实现已经作用域。
- @Documented
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.TYPE})
- public @interface SPI {
-
- /**
- * 默认扩展点实现名
- */
- String value() default "";
-
- /**
- * 扩展点的作用域
- */
- ExtensionScope scope() default ExtensionScope.APPLICATION;
- }
Dubbo的除了和java原生的spi一样,文件名是接口名之外,内容上支持两种类型的格式
- // 第一种格式和Java的SPI一样
- com.foo.XxxProtocol
- com.foo.YyyProtocol
-
- // 第二种支持key-value的形式,其中key是扩展点实现的名称
- xxx=com.foo.XxxProtocol
- yyy=com.foo.YyyProtocol
- zzz,default=com.foo.ZzzProtocol
@Activate的作用是用于特定条件下的激活,用户通过group和value配置激活条件,@Activate标记的扩展点实现在满足某个条件时会被会激活并且实例化。也就是自适应扩展类的使用场景,比如我们有需求,在调用某一个方法时,基于参数选择调用到不同的实现类。和工厂方法有些类似,基于不同的参数,构造出不同的实例对象。 在 Dubbo 中实现的思路和这个差不多,不过 Dubbo 的实现更加灵活,它的实现和策略模式有些类似。每一种扩展类相当于一种策略,基于 URL 消息总线,将参数传递给 ExtensionLoader,通过 ExtensionLoader 基于参数加载对应的扩展类,实现运行时动态调用到目标实例上。
扩展访问的最基本类型是ExtensionAccessor,它提供了Extension的默认获取实现(由接口的default方法指定)

- public interface ExtensionAccessor {
-
- // ExtensionDirector继承自ExtensionAccessor, ExtensionAccessor反过来
- // 又从ExtensionDirector获取ExtensionLoader
- ExtensionDirector getExtensionDirector();
-
- default
ExtensionLoader getExtensionLoader(Class type) { - return this.getExtensionDirector().getExtensionLoader(type);
- }
-
- default
T getExtension(Class type, String name) { - ExtensionLoader
extensionLoader = getExtensionLoader(type); - return extensionLoader != null ? extensionLoader.getExtension(name) : null;
- }
-
- default
T getAdaptiveExtension(Class type) { - ExtensionLoader
extensionLoader = getExtensionLoader(type); - return extensionLoader != null ? extensionLoader.getAdaptiveExtension() : null;
- }
-
- default
T getDefaultExtension(Class type) { - ExtensionLoader
extensionLoader = getExtensionLoader(type); - return extensionLoader != null ? extensionLoader.getDefaultExtension() : null;
- }
-
- }
上面的ExtensionAccessor塞入ExtensionDirector的方式,应该是用了代理设计模式提供了默认的实现。ExtensionAccessor提供了接口的同时,也充当着Proxy的角色,default关键字使得这种方式得以实现。
ExtensionAccessor会将请求交给子类ExtensionDirector去处理,ExtensionDirector是一个带有作用域的扩展访问器,这一点从它带有属性ScopeModel可以看出
- public ExtensionDirector(ExtensionDirector parent, ExtensionScope scope, ScopeModel scopeModel) {
- // 这里传入了父ExtensionDirector,类似java的双亲委派机制
- this.parent = parent;
- // 传递作用域ExtensionScope和ScopeModel
- this.scope = scope;
- this.scopeModel = scopeModel;
- }
从ExtensionAccessor调用ExtensionDirector的逻辑可以看到,ExtensionDirector会将扩展交给内部的ExtensionLoader去处理
- public
ExtensionLoader getExtensionLoader(Class type) { - checkDestroyed();
- if (type == null) {
- throw new IllegalArgumentException("Extension type == null");
- }
- if (!type.isInterface()) {
- throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
- }
- if (!withExtensionAnnotation(type)) {
- throw new IllegalArgumentException("Extension type (" + type +
- ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
- }
-
- // 1. find in local cache
- // 根据传递的类型从本地缓存中获取
- ExtensionLoader
loader = (ExtensionLoader) extensionLoadersMap.get(type); -
- ExtensionScope scope = extensionScopeMap.get(type);
- // 如果扩展的作用域在本地缓存为空,则通过注解@SPI尝试去获取
- if (scope == null) {
- SPI annotation = type.getAnnotation(SPI.class);
- scope = annotation.scope();
- extensionScopeMap.put(type, scope);
- }
-
- // 对于第一次获取的类型,可能loader为空,如果作用域是仅限自我访问
- // 这时就需要创建一个针对该类型的extensionLoader
- if (loader == null && scope == ExtensionScope.SELF) {
- // create an instance in self scope
- loader = createExtensionLoader0(type);
- }
-
- // 2. find in parent
- // 这里用到了构造函数时传入进来的ExtensionDirector,如果本地找不到就去parent查找
- if (loader == null) {
- if (this.parent != null) {
- loader = this.parent.getExtensionLoader(type);
- }
- }
-
- // 3. create it
- // 创建针对该类的extensionLoader,注意这里是在上面获取parent之后
- // 所以正常是如果parent不为null的情况,这里是和java的双亲委派机制一样的
- // 有父ExtensionDirector去创建ExtensionLoader并且放在父ExtensionDirector的本地缓存中
- // 查找时先查找自己本地缓存,如果找不到再去父ExtensionDirector去查找
- if (loader == null) {
- loader = createExtensionLoader(type);
- }
-
- return loader;
- }
ExtensionDirector获取ExtensionLoader的方式参考了Java的双亲委派机制,如果本地缓存中查找不到Class对应的ExtensionLoader,则是去父ExtensionDirector中查找。如果ExtensionDirector不为空,且Scope不是SELF,则由父ExtensionDirector去负责创建针对该扩展点类型的ExtensionLoader。
这里也可以看出每一个扩展点对应一个ExtensionLoader。
前面提到了ExtensionDirector是带有作用域ScopeModel的访问器,这点在创建ExtensionLoader中有所体现
- private
ExtensionLoader createExtensionLoader(Class type) { - ExtensionLoader
loader = null; - // 检查扩展点的ScopeModel是否和ExtensionDirector的ScopeMode是否一致
- if (isScopeMatched(type)) {
- // if scope is matched, just create it
- loader = createExtensionLoader0(type);
- }
- return loader;
- }
-
- private
ExtensionLoader createExtensionLoader0(Class type) { - checkDestroyed();
- ExtensionLoader
loader; - extensionLoadersMap.putIfAbsent(type, new ExtensionLoader
(type, this, scopeModel)); - loader = (ExtensionLoader
) extensionLoadersMap.get(type); - return loader;
- }
-
- private boolean isScopeMatched(Class> type) {
- // 通过注解@SPI获取当前扩展点的作用域
- final SPI defaultAnnotation = type.getAnnotation(SPI.class);
- return defaultAnnotation.scope().equals(scope);
- }
上面逻辑的意思是,只有同个作用域的扩展点,才能被ExtensionDirector访问并且创建。但是在getExtensionLoader的时候可以看到,对于本地缓存没有的ExtensionLoader,是可以去父类中查找并返回的,也就是说父ExtensionDirector可以和子ExtensionDirector存在不一样的Scope,甚至比子ExtensionDirector更高的作用域。
类似ClassLoader的作用,整个扩展机制的主要逻辑部分,其提供了配置的加载、缓存扩展类以及对象生成的工作。
ExtensionLoader的主要入口是通过三个方法调用,分别是getExtension,getAdaptiveExtensionClass以及getActivateExtension三个方法。
要记住一点是ExtensionLoader的每个实例只用于一个type,所以在初始化的时候会指定当前ExtensionLoader所能处理的type。
- ExtensionLoader(Class> type, ExtensionDirector extensionDirector, ScopeModel scopeModel) {
- // 每一个ExtensionLoader对应一个type
- this.type = type;
- ...
getExtension是最常用的方法,一般是通过指定name获取文件中对应的实现(name=com.xxx)。
- public T getExtension(String name) {
- T extension = getExtension(name, true);
- if (extension == null) {
- throw new IllegalArgumentException("Not find extension: " + name);
- }
- return extension;
- }
-
- // wrap表示是否要返回一个包装类
- public T getExtension(String name, boolean wrap) {
- checkDestroyed();
- if (StringUtils.isEmpty(name)) {
- throw new IllegalArgumentException("Extension name == null");
- }
- // 如果传递的name为true,则返回默认的扩展点实现
- if ("true".equals(name)) {
- return getDefaultExtension();
- }
- String cacheKey = name;
- if (!wrap) {
- cacheKey += "_origin";
- }
- // ExtensionLoader也会缓存类型的具体实现,类似Spring那样的bean单例模式
- final Holder
- Object instance = holder.get();
- if (instance == null) {
- // 这里用Holder,而不是直接用具体扩展点实现的对象的原因是为了避免这里的锁冲突
- // 一个具体的扩展点实现对应一个Holder,这样不同的实现类在创建的时候,由于
- // holder的不同,synchronized就不是锁同一个对象,这样并发的时候减少锁冲突的作用
- // 这里也还常见的volatile、双重检查创建单例模式的写法
- // 我想想这里为啥用holder,其实holder就是给name对应类型的一个锁,每个name各自有一个
- // 如果不这么做的话,就必须得有外部一个全局的对象去避免创建对象的线程安全问题
- // 这样的话会造成并发创建慢
- synchronized (holder) {
- instance = holder.get();
- if (instance == null) {
- instance = createExtension(name, wrap);
- holder.set(instance);
- }
- }
- }
- return (T) instance;
- }
这里用了双判断法+Holder的方式创建实例,synchronized锁住的是给当前类型的Holder对象。这样做可以避免锁冲突。
Dubbo肯定是懒加载的机制,不会一口气把所有的扩展点实现都实例化,只有用到的时候再去创建实例(当时类还是会加载的)。这里createExtension可以根据具体的name找到并实例化extension。
- private T createExtension(String name, boolean wrap) {
- Class> clazz = getExtensionClasses().get(name);
- if (clazz == null || unacceptableExceptions.contains(name)) {
- throw findException(name);
- }
- try {
- // extensionInstances是缓存的对应类型的实例化对象,这里应该是每个类型一个对象的单例模式
- T instance = (T) extensionInstances.get(clazz);
- if (instance == null) {
- // createExtensionInstance负责初始化对象
- extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
- instance = (T) extensionInstances.get(clazz);、
- // 这里的postBefore和postAfter应该是类似实例前后的埋点操作
- instance = postProcessBeforeInitialization(instance, name);
- // 这里是注入实例的依赖,下文会介绍
- injectExtension(instance);
- instance = postProcessAfterInitialization(instance, name);
- }
-
- // wrap是是否包装的意思,类似AOP
- if (wrap) {
- List
> wrapperClassesList = new ArrayList<>(); - if (cachedWrapperClasses != null) {
- wrapperClassesList.addAll(cachedWrapperClasses);
- wrapperClassesList.sort(WrapperComparator.COMPARATOR);
- Collections.reverse(wrapperClassesList);
- }
-
- if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
- for (Class> wrapperClass : wrapperClassesList) {
- Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
- boolean match = (wrapper == null) || ((ArrayUtils.isEmpty(
- wrapper.matches()) || ArrayUtils.contains(wrapper.matches(),
- name)) && !ArrayUtils.contains(wrapper.mismatches(), name));
- if (match) {
- // 将新生成的实例注入到wrapper中,不过Wrapper类必须构造函数要有可以加入代理的属性
- instance = injectExtension(
- (T) wrapperClass.getConstructor(type).newInstance(instance));
- instance = postProcessAfterInitialization(instance, name);
- }
- }
- }
- }
-
- // Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.
- initExtension(instance);
- return instance;
- } catch (Throwable t) {
- throw new IllegalStateException(
- "Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(),
- t);
- }
- }
第一次实例化的时候,扩展点实现的信息全部为空,这时需要第一次扫描下文件的内容以及缓存到内存中,其中第一步就是获取文件内的扩展点实现的全路径。
- private Map
> getExtensionClasses() { - // cachedClasses指缓存文件中的扩展点实现已经对应的名字name
- Map
> classes = cachedClasses.get(); - if (classes == null) {
- synchronized (cachedClasses) {
- // 再次获取避免等锁的时候有其他线程获取了
- classes = cachedClasses.get();
- if (classes == null) {
- try {
- // 第一次获取时为空,所以遍历文件查询
- classes = loadExtensionClasses();
- } catch (InterruptedException e) {
- logger.error(COMMON_ERROR_LOAD_EXTENSION, "", "",
- "Exception occurred when loading extension class (interface: " + type + ")",
- e);
- throw new IllegalStateException(
- "Exception occurred when loading extension class (interface: " + type + ")",
- e);
- }
- cachedClasses.set(classes);
- }
- }
- }
- return classes;
- }
-
- private Map
> loadExtensionClasses() throws InterruptedException { - checkDestroyed();
- // 缓存默认的扩展点实现,这里@SPI注解可以指定
- cacheDefaultExtensionName();
-
- Map
> extensionClasses = new HashMap<>(); -
- // 分别通过三个不同优先级的扩展点实现获取地方加载
- 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;
- }
-
- private void cacheDefaultExtensionName() {
- // 用于获取接口中的@SPI注解
- final SPI defaultAnnotation = type.getAnnotation(SPI.class);
- if (defaultAnnotation == null) {
- return;
- }
-
- // 这里的value是获取@SPI的value值,表示默认扩展点名
- String value = defaultAnnotation.value();
- if ((value = value.trim()).length() > 0) {
- // 默认的扩展点实现不能有多个
- String[] names = NAME_SEPARATOR.split(value);
- if (names.length > 1) {
- throw new IllegalStateException(
- "More than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(
- names));
- }
- if (names.length == 1) {
- cachedDefaultName = names[0];
- }
- }
- }
Dubbo有三个不同优先级的扩展点实现的加载机制,也就是去哪些目录扫描扩展点实现的地方,通过LoadingStrategy表示。按优先级大小排序的话分别是
DubboInternalLoadingStrategy(META-INF/dubbo/internal/) > DubboLoadingStrategy(META-INF/dubbo/) > ServicesLoadingStrategy(META-INF/services/)
- private void loadDirectory(Map
> extensionClasses, LoadingStrategy strategy, - String type) throws InterruptedException {
- loadDirectoryInternal(extensionClasses, strategy, type);
- try {
- String oldType = type.replace("org.apache", "com.alibaba");
- if (oldType.equals(type)) {
- return;
- }
- //if class not found,skip try to load resources
- ClassUtils.forName(oldType);
- loadDirectoryInternal(extensionClasses, strategy, oldType);
- } catch (ClassNotFoundException classNotFoundException) {
-
- }
- }
-
- private void loadDirectoryInternal(Map
> extensionClasses, - LoadingStrategy loadingStrategy, String type)
- throws InterruptedException {
- // 文件名通常是扩展点接口的全路径,这里不同的策略会有不同的文件夹目录,所以加起来就是全路径
- String fileName = loadingStrategy.directory() + type;
- try {
- List
classLoadersToLoad = new LinkedList<>(); -
- // try to load from ExtensionLoader's ClassLoader first
- // 这里是指是否loadingStrategy的classLoader要和ExtensionLoader的一样
- // 我暂时能想到的是这样加载效率会高一点,这样ExtensionLoader和扩展点对应的ClassLoader是同一个
- // findClass的时候就可以直接找到,我猜的
- if (loadingStrategy.preferExtensionClassLoader()) {
- ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
- if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
- classLoadersToLoad.add(extensionLoaderClassLoader);
- }
- }
-
- if (specialSPILoadingStrategyMap.containsKey(type)) {
- String internalDirectoryType = specialSPILoadingStrategyMap.get(type);
- //skip to load spi when name don't match
- if (!LoadingStrategy.ALL.equals(
- internalDirectoryType) && !internalDirectoryType.equals(
- loadingStrategy.getName())) {
- return;
- }
- classLoadersToLoad.clear();
- classLoadersToLoad.add(ExtensionLoader.class.getClassLoader());
- } else {
- // load from scope model
- Set
classLoaders = scopeModel.getClassLoaders(); -
- if (CollectionUtils.isEmpty(classLoaders)) {
- // 直接从类路径下获取这个文件,注意这里是systemResource,会调用systemClassLoader获取
- // 那这样看LoadStrategy只是指定了优先级和去哪里找的路径
- // 注意这里的ClassLoader是去所有的类路径下寻找fileName,可以能不同的包下会有相同名字的目录和文件名
- // 所以这里会返回多个
- Enumeration
resources = ClassLoader.getSystemResources(fileName); - if (resources != null) {
- while (resources.hasMoreElements()) {
- // 这里就是读取文件的每一行,然后分析=两边的name和实现的全路径,并且加载类到extensionClasses中
- loadResource(extensionClasses, null, resources.nextElement(),
- loadingStrategy.overridden(), loadingStrategy.includedPackages(),
- loadingStrategy.excludedPackages(),
- loadingStrategy.onlyExtensionClassLoaderPackages());
- }
- }
- } else {
- classLoadersToLoad.addAll(classLoaders);
- }
- }
-
- Map
> resources = ClassLoaderResourceLoader.loadResources( - fileName, classLoadersToLoad);
- resources.forEach(((classLoader, urls) -> {
- loadFromClass(extensionClasses, loadingStrategy.overridden(), urls, classLoader,
- loadingStrategy.includedPackages(), loadingStrategy.excludedPackages(),
- loadingStrategy.onlyExtensionClassLoaderPackages());
- }));
- } catch (InterruptedException e) {
- throw e;
- } catch (Throwable t) {
- logger.error(COMMON_ERROR_LOAD_EXTENSION, "", "",
- "Exception occurred when loading extension class (interface: " + type + ", description file: " + fileName + ").",
- t);
- }
- }
上面这里如何去从文件中获取扩展点实现的逻辑还是比较简单的,根据内部设定的LoadStrategy去目录下的查找扩展点的接口全路径的文件,每一行读取获取name和扩展点实现的类全路径。这里获取的时候有检查是否带=的情况,估计是因为老版本的dubbo没有这个name或者是为了兼容java原生的SPI?
这里还有一个点就是扩展点实现的装配问题,首先需要知道有哪些依赖,这些依赖的类型是什么。Dubbo的方案是查找Java标准的setter方法。即方法名以set开始,只有一个参数。如果扩展类中有这样的set方法,Dubbo会对其进行依赖注入,类似于Spring的set方法注入。 但是Dubbo中的依赖注入比Spring要复杂,因为Spring注入的都是Spring bean,都是由Spring容器来管理的。而Dubbo的依赖注入中,需要注入的可能是另一个Dubbo的扩展,也可能是一个Spring Bean,或是Google guice的组件,或其他任何一个框架中的组件。Dubbo需要能够从任何一个场景中加载扩展。在injectExtension方法中。
- private T injectExtension(T instance) {
- if (injector == null) {
- return instance;
- }
-
- try {
- // 获取类中所有的方法
- for (Method method : instance.getClass().getMethods()) {
- // DUBBO通过判断方法是否为set方法判断是否是属性注入
- if (!isSetter(method)) {
- continue;
- }
- /**
- * Check {@link DisableInject} to see if we need auto-injection for this property
- */
- // DisableInject注解可以禁止自动注入
- 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;
- }
- }
-
- // Set方法只有一个参数
- Class> pt = method.getParameterTypes()[0];
- if (ReflectUtils.isPrimitives(pt)) {
- continue;
- }
-
- try {
- // 获取set方法的属性,就是方法名去掉set开头后的字符串
- String property = getSetterProperty(method);
- // 这里是从injector中获取到实例,目前Dubbo支持默认从三个地方根据类型获取实例
- Object object = injector.getInstance(pt, property);
- if (object != null) {
- method.invoke(instance, object);
- }
- } catch (Exception e) {
- logger.error(COMMON_ERROR_LOAD_EXTENSION, "", "",
- "Failed to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(),
- e);
- }
- }
- } catch (Exception e) {
- logger.error(COMMON_ERROR_LOAD_EXTENSION, "", "", e.getMessage(), e);
- }
- return instance;
- }
这里是从injector中获取到对应的属性,注意injector本身也是一个扩展点,在初始化ExtensionLoader的时候创建
- this.injector = (type == ExtensionInjector.class ?null :
- extensionDirector.getExtensionLoader(ExtensionInjector.class).getAdaptiveExtension());
目前Dubbo支持四种ExtensionInjector(老的版本是叫ExtensionFactory),分别是
- SpiExtensionInjector:Dubbo自己的Spi去加载Extension
- SpringExtensionInjector:从Spring容器中去加载Extension
- AdaptiveExtensionInjector: 自适应的AdaptiveExtensionLoader
- ScopeBeanExtensionInjector:从ScopeBeanFactory获取对应的属性
这里getExtension的第二个参数是wrapper,表示是否返回一个包装类。那什么是wrapper类呢,Wrapper类是一个有复制构造函数的类,也是典型的装饰者模式。下面就是一个Wrapper类:
- class A{
- private A a;
- public A(A a){
- this.a = a;
- }
- }
类A有一个构造函数public A(A a),构造函数的参数是A本身。这样的类就可以成为Dubbo扩展机制中的一个Wrapper类。
在Dubbo中Wrapper类也是一个扩展点,和其他的扩展点一样,也是在META-INF文件夹中配置的。例如ProtocolFilterWrapper和ProtocolListenerWrapper就是在路径dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol中配置的:
- filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
- listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
- mock=org.apache.dubbo.rpc.support.MockProtocol
在Dubbo加载扩展配置文件时,也就是前面loadClass的方法中,如果检测到扩展点实现代用@Wrapper注解的话,就会把这个扩展点实现缓存起来
- private void loadClass(ClassLoader classLoader, Map
> extensionClasses, - java.net.URL resourceURL, Class> clazz, String name,
- boolean overridden) {
-
- if (clazz.isAnnotationPresent(Adaptive.class)) {
- cacheAdaptiveClass(clazz, overridden);
- } else if (isWrapperClass(clazz)) {
- cacheWrapperClass(clazz);
- ...
- }
-
- private void cacheWrapperClass(Class> clazz) {
- if (cachedWrapperClasses == null) {
- cachedWrapperClasses = new ConcurrentHashSet<>();
- }
- cachedWrapperClasses.add(clazz);
- }
所以当需要时,ExtensionLoader会将Wrapper类返回而不是直接返回一个原生扩展点的实例。在需要强调的是,Wrapper也是需要和扩展点实现一起放入到文件中,以name=com.xxx.xxxWrapper的形式,ExtensionLoader获取到所有的Wrapper类之后进行缓存,在创建对象的时候会依次进行包装,注意这里因为都是继承了同一个接口,所以是可以依次包装。这但在createExtension也可以发现
- private T createExtension(String name, boolean wrap) {
- ...
- // wrap是是否包装的意思,类似AOP
- if (wrap) {
- List
> wrapperClassesList = new ArrayList<>(); - if (cachedWrapperClasses != null) {
- wrapperClassesList.addAll(cachedWrapperClasses);
- // 这里对Wrapper类进行排序,可以自定义规则
- wrapperClassesList.sort(WrapperComparator.COMPARATOR);
- Collections.reverse(wrapperClassesList);
- }
-
- if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
- // 遍历所有的Wrapper类,并且依次将返回的对象塞入
- for (Class> wrapperClass : wrapperClassesList) {
- Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
- boolean match = (wrapper == null) || ((ArrayUtils.isEmpty(
- wrapper.matches()) || ArrayUtils.contains(wrapper.matches(),
- name)) && !ArrayUtils.contains(wrapper.mismatches(), name));
- if (match) {
- // 将新生成的实例注入到wrapper中,不过Wrapper类必须构造函数要有可以加入代理的属性
- instance = injectExtension(
- (T) wrapperClass.getConstructor(type).newInstance(instance));
- instance = postProcessAfterInitialization(instance, name);
- }
- }
- }
- }
-
-
- }
这里也贴了Dubbo官方介绍的可扩展机制源码分析
官方是这样介绍自适应获取的,在 Dubbo 中,很多拓展都是通过 SPI 机制进行加载的,比如 Protocol、Cluster、LoadBalance 等。有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。
我个人对这段的解释是:Dubbo是基于URL总线的模式,所有的配置和连接信息都可以从URL这个类中获取。所有扩展点参数都作为了URL参数,URL 作为上下文信息贯穿整个扩展点设计体系。那么这里的自适应扩展就是根据传递的参数去创建对象,有点类似工厂模式的意思。那上面的getExtension不能做到吗?
其实getExtension也是可以做到类似的效果,就是根据URL的参数去运行时初始化。只不过自适应扩展有更进一步的优化,自适应扩展可以动态生成关于只包含某个方法的实现。也就是扩展点(用@SPI标记的),可以在方法级别使用@Adaptive,这样程序运行时就会自动生成这个方法内容的实现,接口的其他实现会抛出异常。这个就是自适应扩展的优势。
具体的逻辑实现这里就不介绍了,官方那里+上面的分析写了很多,具体可以跳转到官方bolg看下介绍。
SPI自适应扩展
https://cn.dubbo.apache.org/zh-cn/docsv2.7/dev/source/adaptive-extension/
自适应扩展的注解是@Adaptive,而这里的激活扩展点则是@Activate。主要使用在有多个扩展点实现、需要同时根据不同条件被激活的场景中,如Filter需要多个同时激活,因为每个Filter实现的是不同的功能。
举例来说:在工作中,某种时候存在这样的情形,需要同时启用某个接口的多个实现类,如Filter过滤器。我们希望某种条件下启用这一批实现,而另一种情况下启用那一批实现,比如:希望RPC调用的消费端和服务端,分别启用不同的两批Filter,这该怎么处理呢? —> 这时候dubbo的条件激活注解@Activate,就可以派上用场了。
@Activate的参数有以下
| String[] group() | URL中的分组如果匹配则激活 |
| String[] value() | URL中如果包含该key值,则会激活 |
| String[] before() | 填写扩展点列表,表示哪些扩展点要在本扩展点之前激活 |
| String[] after() | 表示哪些扩展点需要在本扩展点之后激活 |
| int order() | 排序信息 |
- public List
getActivateExtension(URL url, String[] values, String group) { - checkDestroyed();
- // solve the bug of using @SPI's wrapper method to report a null pointer exception.
- Map
, T> activateExtensionsMap = new TreeMap<>(activateComparator); - // 这里的value是从URL中根据Key获取的值列表
- List
names = values == null ? new ArrayList<>(0) : asList(values); - Set
namesSet = new HashSet<>(names); - // 加锁的方式除了只加载一次之外,还有避免线程安全问题
- if (!namesSet.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
- if (cachedActivateGroups.size() == 0) {
- synchronized (cachedActivateGroups) {
- // cache all extensions
- if (cachedActivateGroups.size() == 0) {
- // 获取ExtensionLoader指定的type也就是扩展点的类,这部分可能出现在多个路径中
- // type是接口名
- getExtensionClasses();
- for (Map.Entry
entry : cachedActivates.entrySet()) { - // name是扩展点实现名, value是@Activate类的实现
- String name = entry.getKey();
- Object activate = entry.getValue();
-
- String[] activateGroup, activateValue;
-
- // @Activate中group和value都是激活的条件,其中group表示生产或者消费者
- // value则是自己定义的字符串数组,只有带有对应的字符串的Url才可以加载
- if (activate instanceof Activate) {
- activateGroup = ((Activate) activate).group();
- activateValue = ((Activate) activate).value();
- } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
- activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
- activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
- } else {
- continue;
- }
- cachedActivateGroups.put(name,
- new HashSet<>(Arrays.asList(activateGroup)));
- String[][] keyPairs = new String[activateValue.length][];
- for (int i = 0; i < activateValue.length; i++) {
- // @Activate中value可能包含键值对
- 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];
- }
- }
- cachedActivateValues.put(name, keyPairs);
- }
- }
- }
- }
- // @Activate的一个例子:
- // @Activate(group = CommonConstants.CONSUMER, value = Constants.SERVICE_AUTH)
- // 当URL的key带有Constants.SERVICE_AUTH,该扩展点实现就可以被激活
- //
-
- // traverse all cached extensions
- 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));
- }
- });
- }
-
- // namesSet是URL的Key的值列表
- // 扩展点实现的文件中,DEFAULT_KEY也就是有default=com.xxx的
- if (namesSet.contains(DEFAULT_KEY)) {
- // will affect order
- // `ext1,default,ext2` means ext1 will happens before all of the default extensions while ext2 will after them
- ArrayList
extensionsResult = new ArrayList<>( - activateExtensionsMap.size() + names.size());
- for (String name : names) {
- if (name.startsWith(REMOVE_VALUE_PREFIX) || namesSet.contains(
- REMOVE_VALUE_PREFIX + name)) {
- continue;
- }
- if (DEFAULT_KEY.equals(name)) {
- extensionsResult.addAll(activateExtensionsMap.values());
- continue;
- }
- // 获取扩展点实现的时候,会有name=具体类路径的方式
- // 这里就是检查方法进入时的参数URL带有的key对应的value是否有指定这个name
- // 有的话就选择扩展点实现
- if (containsExtension(name)) {
- extensionsResult.add(getExtension(name));
- }
- }
- return extensionsResult;
- } else {
- // add extensions, will be sorted by its order
- for (String name : names) {
- if (name.startsWith(REMOVE_VALUE_PREFIX) || namesSet.contains(
- REMOVE_VALUE_PREFIX + name)) {
- continue;
- }
- if (DEFAULT_KEY.equals(name)) {
- continue;
- }
- // 当这里的Key指定具体的名字时,而名字对应扩展点实现时初始化?
- if (containsExtension(name)) {
- activateExtensionsMap.put(getExtensionClass(name), getExtension(name));
- }
- }
- return new ArrayList<>(activateExtensionsMap.values());
- }
- }
Dubbo的SPI机制先介绍到这,改天有时间再优化下这篇文章,把代码介绍部分分析好点>_<