风在路上 风在路上
首页
导航站
  • Java-Se

    • Java基础
  • Java-Se进阶-多线程

    • 多线程
  • Java-Se进阶-java8新特性

    • java8新特性
  • Java-ee

    • JavaWeb
  • Java虚拟机

    • JVM
  • golang基础

    • golang基础
  • golang框架

    • gin
  • SQL 数据库

    • MySQL
  • NoSQL 数据库

    • Redis
    • ElasticSearch
    • MongoDB
  • ORM

    • MyBatis
    • MyBatis-Plus
  • Spring

    • Spring
  • SpringMVC

    • SpringMVC1
    • SpringMVC2
  • SpringCloud

    • SpringCloud
  • 中间件

    • RabbitMQ
    • Dubbo
  • 秒杀项目
  • Git
  • Linux
  • Docker
  • JWT
  • 面试
  • 刷题
开发问题😈
设计模式
关于💕
归档🕛
GitHub (opens new window)

风

摸鱼
首页
导航站
  • Java-Se

    • Java基础
  • Java-Se进阶-多线程

    • 多线程
  • Java-Se进阶-java8新特性

    • java8新特性
  • Java-ee

    • JavaWeb
  • Java虚拟机

    • JVM
  • golang基础

    • golang基础
  • golang框架

    • gin
  • SQL 数据库

    • MySQL
  • NoSQL 数据库

    • Redis
    • ElasticSearch
    • MongoDB
  • ORM

    • MyBatis
    • MyBatis-Plus
  • Spring

    • Spring
  • SpringMVC

    • SpringMVC1
    • SpringMVC2
  • SpringCloud

    • SpringCloud
  • 中间件

    • RabbitMQ
    • Dubbo
  • 秒杀项目
  • Git
  • Linux
  • Docker
  • JWT
  • 面试
  • 刷题
开发问题😈
设计模式
关于💕
归档🕛
GitHub (opens new window)
  • mybatis

  • mybatis-plus

  • Spring

  • SpringMvc

  • RabbitMQ

  • Dubbo

    • Dubbo知识体系
    • Dubbo
    • 服务注册(provider服务暴露)
    • 服务发现(consumer服务引入)
    • 服务调用过程
    • SPI机制
      • JDK的SPI机制-ServiceLoader
        • 概述
        • JDK SPI实现标准
        • 相关代码
        • demo工程地址
        • JDK SPI机制的弊端
      • Spring中的SPI
        • SpringFactoriesLoader
      • Dubbo中的SPI
        • 概述
        • 使用规范
      • Dubbo-SPI特性
        • 1. 自动包装
        • 2. 自动注入
        • 3. 自适应
        • 4. 自动激活
      • 源码分析
        • 1. 默认扩展点
        • 2. 自适应
        • 获取指定名称的扩展点
        • 3. 自动激活
      • 总结
    • 负载均衡机制
    • 服务容错、降级
    • Dubbo的服务异常处理
  • SpringCloud

  • 框架
  • Dubbo
zdk
2022-07-28
目录

SPI机制

Table of Contents generated with DocToc (opens new window)

  • JDK的SPI机制-ServiceLoader
    • 概述
    • JDK SPI实现标准
    • 相关代码
    • demo工程地址
    • JDK SPI机制的弊端
  • Spring中的SPI
    • SpringFactoriesLoader
  • Dubbo中的SPI
    • 概述
    • 使用规范
  • Dubbo-SPI特性
    • 1. 自动包装
    • 2. 自动注入
    • 3. 自适应
    • 4. 自动激活
  • 源码分析
    • 1. 默认扩展点
    • 2. 自适应
      • 获取指定名称的扩展点
    • 3. 自动激活
  • 总结

# JDK的SPI机制-ServiceLoader

# 概述

提示

SPI,是Service Provider Interface的缩写,是JDK内置的一种服务提供发现机制。目前很多框架都用它来做服务的扩展发现,它就是一种动态发现、替换实现类的机制。 SPI的作用就是为这些被扩展的API寻找服务实现。使用SPI机制的优势是实现解耦,使得第三方服务模块的装配控制逻辑与调用者的业务代码分离。 比如我们常见的jdbc,还有SL4j日志门面,都是通过SPI机制来实现对不同数据库连接、不同日志实现的扩展的。

# JDK SPI实现标准

提示

  1. 当服务提供者提供了一个接口的一种具体实现后,在META-INF/services 目录下创建一个以 “接口全限定名” 为命名的文件,内容为实现类的权限定名
  2. 接口实现类所在的jar包,放在主程序的classpath中
  3. 主程序通过 java.util.ServiceLoader 动态加载实现模块,它通过扫描META-INF/services 目录下的配置文件找到实现类的全限定名,把类加载到JVM
  4. SPI的实现类必须要有一个无参构造

笔记

具体代码步骤

  1. 新建api的工程,提供一个接口,在接口项目中不提供实现类

  2. 新建service的工程,引入api工程,创建实现类实现api中的接口

  3. 然后在service工程的resources目录下,创建META-INF/services目录

  4. 在这个目录下,创建一个文件,文件名为接口的全类名,文件内容为接口实现类的全类名,如果有多个实现类,换行继续写即可

  5. 新建测试工程,引入api和service工程的依赖,在测试工程中使用以下方式进行测试

# 相关代码

package com.zdk.api.interfaces;

/**
 * @author zhangdikai
 * @date 2022-07-12 16:28
 */
public interface Eat {
    void eat();
}

1
2
3
4
5
6
7
8
9
10
package com.zdk.cat.service.impl;

import com.zdk.api.interfaces.Eat;

/**
 * @author zhangdikai
 * @date 2022-07-13 9:33
 */
public class CatEat implements Eat {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

// ------------------------------------

package com.zdk.dog.service.impl;

import com.zdk.api.interfaces.Eat;

/**
 * @author zhangdikai
 * @date 2022-07-12 16:29
 */
public class DogEat implements Eat {
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

接口和实现类信息文件 image.png

public class EatService {

    public static void main(String[] args) {
        ServiceLoader<Eat> serviceLoader = ServiceLoader.load(Eat.class);
        for (Eat eat : serviceLoader) {
            eat.eat();
        }
    }
}
1
2
3
4
5
6
7
8
9

输出结果 image.png

# demo工程地址

github (opens new window)

# JDK SPI机制的弊端

  • 只能遍历所有实现(上面的代码中有展示),并将全部实现类都实例化,即使它没有被用到,浪费资源
  • 配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们
  • 没有缓存,实现类会被多次创建
  • 扩展如果依赖其他的扩展,做不到自动注入和装配
  • 扩展很难和其他的框架集成,比如扩展里面依赖了一个Spring Bean,原生的Java SPI不支持(不支持IOC和AOP)
  • 如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位到是这个原因

# Spring中的SPI

# SpringFactoriesLoader

提示

在Spring中,扩展也是依赖spi机制完成的,只不过Spring对于扩展文件约定在Classpath 路径下的 META-INF目录下,所有的文件名都是叫spring.factories,文件里的内容是一个以一个个键值对的方式存储的,键为类的全限定名,值也为类的全限定名,如果有多个值,可以用逗号分割,有一点得注意的是,键和值本身约定并没有类与类之间的依赖关系(当然也可以有,得看使用场景的约定),也就是说键值可以没有任何关联,键仅仅是一种标识,代表一种场景。 最常见的自动装配的注解,@EnableAutoConfiguration,也就是代表自动装配的场景,当你需要你的类被自动装配,就可以用这个注解的全限定名作为键,自己写的类的全限定名为值,这样Spring Boot在进行自动装配的时候,就会拿这个键,找到我们自己写的实现类来完成自动装配。

自动装配的部分源代码: image.png 这里其实就是通过@EnableAutoConfiguration的全限定名从spring.factories中加载这个键对应的所有的实现类的名称,这样就能拿到所有需要自动装配的类的全限定名了。

mybatis整合spring的自动装配功能文件 img 内容: image.png

笔记

mybatis也是按照spring的规则来配置的。可以看看MybatisAutoConfiguration这个实现类,里面有mybatis是如何跟spring整合的内容。 SpringFactoriesLoader的应用场景还有很多,可以去看一下SpringBoot中的启动引导类:SpringApplication,里面多次使用到了这个SpringFactoriesLoader这个类来获取各种实现。

# Dubbo中的SPI

# 概述

Dubbo SPI 定义了一套自己的规范,同时对JDK的SPI存在的问题进行了改进

提示

优点:

  1. 扩展类可以按需加载,节省了资源
  2. SPI文件采用"key=value"的形式,可以根据扩展名灵活获取实现类
  3. 对扩展类的对象进行了缓存,避免重复创建
  4. 扩展类加载失败有详细日志,方便排查
  5. 支持IOC和AOP

# 使用规范

  1. 编写接口,接口必须加@SPI注解,代表它是一个可扩展的接口

  2. 编写实现类

  3. 在classpath下的META-INF/dubbo目录下,创建以接口全限定名命名的文件,文件内容为"key=value"的格式,key是扩展点的名称,value是实现类的全限定名

  4. 通过ExtensionLoader类获取扩展点实现

    提示

Dubbo会默认扫描下面三个目录下的配置

  • META-INF/service:这是为了兼容JDK的SPI
  • META-INF/dubbo:用户自定义的扩展点
  • META-INF/dubbo/internal:dubbo内部自己使用的扩展点

:::

# Dubbo-SPI特性

# 1. 自动包装

笔记

Dubbo SPI 的 AOP 就是利用「自动包装」来实现的。 在扩展类的实现中,可能存在部分逻辑是通用的,应该把它们提取出来,而不是每个实现类都写一份重复的代码。 此时,应该创建一个 Wrapper 包装类,编写通用逻辑,它内部应该持有一个原对象 Origin,个性化的业务逻辑交给 Origin 自己处理,通用逻辑由 Wrapper 统一处理。 自动包装的规范是:Wrapper 类应该提供一个构造函数,该函数只有一个参数:扩展点接口。

public class SayWrapper implements Say {

    private final Say origin;

    public SayWrapper(Say origin) {
        this.origin = origin;
    }

    @Override
    public void say() {
        System.err.println("before...");
        origin.say();
        System.err.println("after...");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
impl=demo.spi.wrapper.SayImpl
wrapper=demo.spi.wrapper.SayWrapper
1
2
// 默认获取的就是包装类
Say say = ExtensionLoader.getExtensionLoader(Say.class).getDefaultExtension();
say.say();

输出:
before...
say...
after...
1
2
3
4
5
6
7
8

# 2. 自动注入

笔记

Dubbo SPI 是支持自动注入的,它类似于 Spring 的 IOC,当扩展类的属性是另一个扩展点类型,且提供了 Setter 方法时,Dubbo 会自动帮我们注入依赖依赖的扩展类成员对象。

假设现在有一个Eat扩展接口

@SPI
public interface Eat {
	@Adaptive("key")
	void eat(URL url);
}

public class EatImpl implements Eat {
	@Override
	public void eat(URL url) {
		System.err.println("eat meat...");
	}
}
1
2
3
4
5
6
7
8
9
10
11
12

SayA 依赖了 Eat 扩展

public class SayA implements Say {
    public Eat eat;

    public void setEat(Eat eat) {
        this.eat = eat;
    }
}
1
2
3
4
5
6
7

当我们获取 SayA 实现时,Dubbo 会自动帮我们注入 Eat 扩展点对象。**Eat 扩展点实现类可能有很多,该注入哪一个呢?**这就和下面要说的「自适应」有关了,其实 Dubbo 注入的始终是一个自适应扩展,它会根据参数中的 URL 去判断具体调用哪个实现。

# 3. 自适应

笔记

SPI扩展点可能存在这种情况:扩展点实现类有很多,无法硬编码指定,需要运行时动态根据参数来确定具体实现类。为了实现该需求,Dubbo SPI实现了自适应调用。

自适应调用需要用到@Adaptive注解,它可以加在类或方法上。

  • 加在类上,该类就是自适应类
  • 加在方法上,会自动生成代理类,通过URL对象里的参数进行匹配,以确定具体实现

笔记

自适应调用的实现原理并不复杂,Dubbo 利用 Javassist 技术给扩展接口动态的生成了自适应代理类,类名的规则是XXX$Adaptive,在代理类中,根据 URL 对象中的参数,去匹配具体的扩展点实现类。 @Adaptive注解可以指定多个值,如果指定的值在URL中没有找到,则以@SPI注解中指定的值作为默认的扩展名称进行返回;如对应的值是{"key1","key2"},如果key1在URL中有对应的值(https://xxxx?key1=xxx这种),则使用key1的值作为扩展名称,如果key1没有,再找key2,有则使用,如果都没有,则使用默认的@SPI注解的中的值进行加载,否则报错 如果没有指定值,则会按接口的名称进行按英文字母进行拼接,用. 进行拼接,如 org.apache.dubbo.xxx.YyyInvokerWrapper,则生成的名称为 yyy.invoker.wrapper

@SPI
public interface Say {
    // 匹配URL中的参数key
    @Adaptive({"key"})
    void say(URL url);
}
1
2
3
4
5
6

假设有Say接口有SayA、SayB两个实现,发生自适应调用如下:

Say say = ExtensionLoader.getExtensionLoader(Say.class).getAdaptiveExtension();
say.say(URL.valueOf("http://127.0.0.1?key=a"));
say.say(URL.valueOf("http://127.0.0.1?key=b"));

输出:
sayA...
sayB...
1
2
3
4
5
6
7

# 4. 自动激活

场景:某个扩展点的多个实现类需要根据规则同时启用,例如 Filter 过滤器 自动激活需要使用@Activate注解,一旦加上该注解,表示该实现类(或方法)需要根据条件自动激活,注解属性含义如下:

属性 说明
group Group 匹配成功则激活
value URL 中存在该 Key 则激活
order 扩展点执行顺序

假设有Filter接口

@SPI
public interface Filter {
    void invoke();
}
1
2
3
4

FilterA 代表在 consumer 组、且 URL 中存在 xxx 参数时自动激活,顺序为 1

@Activate(group = {"consumer"}, value = {"xxx"}, order = 1)
public class FilterA implements Filter {

    @Override
    public void invoke() {
        System.err.println("FilterA...");
    }
}
1
2
3
4
5
6
7
8

FilterB 代表在 provider 组、且 URL 中存在yyy 参数时自动激活,顺序为 2

@Activate(group = {"provider"}, value = {"yyy"}, order = 2)
public class FilterB implements Filter {

    @Override
    public void invoke() {
        System.err.println("FilterB...");
    }
}
1
2
3
4
5
6
7
8

获取激活的扩展点实现类对象集合,如下仅会输出 FilterA,FilterB 的 Group 匹配失败了

ExtensionLoader<Filter> extensionLoader = ExtensionLoader.getExtensionLoader(Filter.class);
URL url = URL.valueOf("http://127.0.0.1?key=xxx,yyy");
List<Filter> filters = extensionLoader.getActivateExtension(url, "key","consumer");
filters.stream().forEach(System.out::println);

输出:
demo.spi.activate.FilterA
1
2
3
4
5
6
7

# 源码分析

Dubbo SPI 的核心类是ExtensionLoader,它的主要职责就是加载扩展点实现类,以及根据各种条件获取扩展点实例。 属性说明如下:

public class ExtensionLoader<T> {

    private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
    // 多个扩展点用逗号分割
    private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
    // 扩展点实例缓存
    private final ConcurrentMap<Class<?>, Object> extensionInstances = new ConcurrentHashMap<>(64);
    // 接口
    private final Class<?> type;
    // 扩展依赖注入器
    private final ExtensionInjector injector;
    // 扩展类名称缓存
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
    // 扩展类缓存
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
    // 自动激活扩展实例缓存
    private final Map<String, Object> cachedActivates = Collections.synchronizedMap(new LinkedHashMap<>());
    // 扩展类激活的Group缓存
    private final Map<String, Set<String>> cachedActivateGroups = Collections.synchronizedMap(new LinkedHashMap<>());
    // 扩展类激活的Value缓存
    private final Map<String, String[]> cachedActivateValues = Collections.synchronizedMap(new LinkedHashMap<>());
    // 扩展实例缓存
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
    // 动态生成的自适应实例缓存
    private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
    // 动态生成的自适应类
    private volatile Class<?> cachedAdaptiveClass = null;
    // 默认扩展名称
    private String cachedDefaultName;
    // 动态创建自适应实例发生的异常
    private volatile Throwable createAdaptiveInstanceError;
    // 包装类缓存
    private Set<Class<?>> cachedWrapperClasses;
    // 异常缓存
    private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<>();
    /**
     * 扩展类Class加载策略:默认从三个路径加载
     * 1. META-INF/dubbo/internal/
     * 2. META-INF/dubbo/
     * 3. META-INF/services/
     */
    private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
    /**
     * Record all unacceptable exceptions when using SPI
     * 记录加载扩展点时出现的异常
     */
    private Set<String> unacceptableExceptions = new ConcurrentHashSet<>();
    //
    private ExtensionDirector extensionDirector;
    // 扩展点后置处理
    private List<ExtensionPostProcessor> extensionPostProcessors;
    // 扩展类实例化策略
    private InstantiationStrategy instantiationStrategy;
    private Environment environment;
    // 自动激活扩展点排序
    private ActivateComparator activateComparator;
    private ScopeModel scopeModel;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

ExtensionLoader 是和接口绑定的,一个接口对应一个 ExtensionLoader 实例,获取接口对应的实例也很简单:

ExtensionLoader<T> extensionLoader = ExtensionLoader.getExtensionLoader(T.class);
1

ExtensionLoader 有三个常用方法,下面分别分析:

方法名 备注
getDefaultExtension() 获取默认扩展点实现类实例
getAdaptiveExtension() 获取自适应实例
getActivateExtension() 获取自动激活实例集合

# 1. 默认扩展点

以getDefaultExtension() 方法为入口获取默认的扩展点实现类实例,默认情况下,如果有 Wrapper 类,会进行自动包装。

public T getDefaultExtension() {
    // 加载实现类
    getExtensionClasses();
    if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
        return null;
    }
    // 获取默认扩展点实现类实例
    return getExtension(cachedDefaultName);
}
1
2
3
4
5
6
7
8
9

getExtensionClasses() 方法会获取扩展点下的所有实现类,只会加载一次,加载完会放入缓存中

private Map<String, Class<?>> getExtensionClasses() {
    // 优先从缓存中取
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                // 没有缓存,从指定路径下加载类
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

loadExtensionClasses() 方法默认会从三个路径去加载 Class,不同的加载路径被定义成一个加载策略,对应的类是 LoadingStrategy。

private Map<String, Class<?>> loadExtensionClasses() {
    // 缓存@SPI注解指定的默认扩展名
    cacheDefaultExtensionName();

    Map<String, Class<?>> extensionClasses = new HashMap<>();

    /**
     * 依次从不同目录下加载
     * 1. META-INF/dubbo/internal/
     * 2. META-INF/dubbo/
     * 3. META-INF/services/
     */
    for (LoadingStrategy strategy : strategies) {
        // 从指定目录加载Class
        loadDirectory(extensionClasses, strategy, type.getName());

        // compatible with old ExtensionFactory
        if (this.type == ExtensionInjector.class) {
            loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());
        }
    }

    return extensionClasses;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

扩展类加载完毕后,就可以根据默认的扩展名去创建实例了,默认是会进行自动包装的

private T createExtension(String name, boolean wrap) {
    // 获取扩展名对应的Class
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null || unacceptableExceptions.contains(name)) {
        // Class实例创建失败过,抛出异常
        throw findException(name);
    }
    try {
        T instance = (T) extensionInstances.get(clazz);
        if (instance == null) {
            // 创建实例并缓存
            extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
            instance = (T) extensionInstances.get(clazz);
            // 前置处理
            instance = postProcessBeforeInitialization(instance, name);
            // Setter方法注入
            injectExtension(instance);
            // 后置处理
            instance = postProcessAfterInitialization(instance, name);
        }

        if (wrap) {// 自动包装
            List<Class<?>> 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);
                    // @Wrapper注解匹配,判断是否需要包装
                    if (wrapper == null
                        || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
                        // 反射创建包装类实例
                        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                        // 包装类的后置处理
                        instance = postProcessAfterInitialization(instance, name);
                    }
                }
            }
        }

        initExtension(instance);
        return instance;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

injectExtension() 方法会进行依赖注入,它会查找 Class 的 Setter 方法,然后判断它的参数是否也是扩展点,如果是就会从 ExtensionAccessor 中获取扩展点对应的自适应实例,然后反射赋值。注:Dubbo SPI 只能注入 Adaptive 实例,因此必须保证注入的扩展点是自适应的

# 2. 自适应

时序图2.png

笔记

getAdaptiveExtension()方法用来获取扩展点的自适应实例,它的原理并不复杂,无非就是生成扩展点的代理类,然后解析参数中 URL 的属性和@Adaptive 注解的值做匹配,再去调用指定的扩展点实现。 自适应类会在程序运行时动态生成,可以用 JDK 的动态代理,也可以用类似 CGLIB 等字节码技术生成,Dubbo 默认用的是 Javassist。 自适应对象也有缓存,只会创建一次,对应的属性是cachedAdaptiveInstance,创建自适应对象的方法是createAdaptiveExtension()。

public T getAdaptiveExtension() {
    //确认是否有缓存
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
        if (createAdaptiveInstanceError != null) {
            throw new IllegalStateException("Failed to create adaptive instance: " +
                    createAdaptiveInstanceError.toString(),
                    createAdaptiveInstanceError);
        }
        synchronized (cachedAdaptiveInstance) {
            instance = cachedAdaptiveInstance.get();
            if (instance == null) {
                try {
                    // 没有就创建一个适配类
                  instance = createAdaptiveExtension();
                    cachedAdaptiveInstance.set(instance);
              } catch (Throwable t) {
                    createAdaptiveInstanceError = t;
                  throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                }
            }
        }
    }
    return (T) instance;
}
  

private T createAdaptiveExtension() {
      try {
          // 获取适配类并注入依赖
          return injectExtension((T) getAdaptiveExtensionClass().newInstance());
      } catch (Exception e) {
        throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
      }
  }
private Class<?> getAdaptiveExtensionClass() {
	// 从jar包中获取相应的SPI实现的类,这块会在下面进行分析
	getExtensionClasses();
	// 如果cachedAdaptiveClass不为空,则这个类是@Adaptive 直接标注的类作为扩展实现类
	if (cachedAdaptiveClass != null) {
		return cachedAdaptiveClass;
	}
	return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

以下来看下 getExtensionClasses的实现:

//先从缓存获取,缓存没有就加载并存入缓存 
private Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // 缓存中如果没有,从jar包中加载
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
	   return classes;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private Map<String, Class<?>> loadExtensionClasses() {
    // 对应的需要适配的接口,缓存默认的扩展点名称,是 @SPI指定的值
    cacheDefaultExtensionName();
      Map<String, Class<?>> extensionClasses = new HashMap<>();
      // 从加载策略中获取,主要有三种 
      // DubboInternalLoadingStrategy从META-INF/dubbo/internal/目录下搜索
      // ServicesLoadingStrategy从META-INF/services/目录搜索
      // DubboLoadingStrategy从META-INF/dubbo/目录搜索
      for (LoadingStrategy strategy : strategies) {
          loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
          loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
      }
      return extensionClasses;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                         boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
    String fileName = dir + type;
    try {
        Enumeration<java.net.URL> urls = null;
        ClassLoader classLoader = findClassLoader();
        // try to load from ExtensionLoader's ClassLoader first
        if (extensionLoaderClassLoaderFirst) {
            ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
            if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
                urls = extensionLoaderClassLoader.getResources(fileName);
            }
        }
        if (urls == null || !urls.hasMoreElements()) {
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
        }
        if (urls != null) {
            while (urls.hasMoreElements()) {
                java.net.URL resourceURL = urls.nextElement();
                // 获取到所有包含这些目录里的相关文件,并缓存对应的Class信息
                loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
            }
        }
    } catch (Throwable t) {
        logger.error("Exception occurred when loading extension class (interface: " +
                type + ", description file: " + fileName + ").", t);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
                            java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
        try {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    final int ci = line.indexOf('#');
                    if (ci >= 0) {
                        line = line.substring(0, ci);
                    }
                    line = line.trim();
                    if (line.length() > 0) {
                        try {
                            String name = null;
                            int i = line.indexOf('=');
                            if (i > 0) {
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
                                // 加载类信息
                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);
                            }
                        } catch (Throwable t) {
                            IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                            exceptions.put(line, e);
                        }
                    }
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
                              boolean overridden) throws NoSuchMethodException {
         if (!type.isAssignableFrom(clazz)) {
               throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                     type + ", class line: " + clazz.getName() + "), class "
                       + clazz.getName() + " is not subtype of interface.");
           }
           // 判断当前类是否有指定 @Adaptive注解,如果有,则缓存Adaptive类
           if (clazz.isAnnotationPresent(Adaptive.class)) {
               cacheAdaptiveClass(clazz, overridden);
               // 如果是当前类的包装类(Wrapper),有以当前接口为唯一参数的构造函数
               // 在获取扩展类时,会使用扩展Wrapper类进行封装当前扩展类
               // 也可以使用 @Wrapper 注解来标识,具体的类需要使用哪个 Wrapper类
           } else if (isWrapperClass(clazz)) {
               cacheWrapperClass(clazz);
           } else {
               clazz.getConstructor();
               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);
                 }
               }
   
               String[] names = NAME_SEPARATOR.split(name);
               if (ArrayUtils.isNotEmpty(names)) {
                   cacheActivateClass(clazz, names[0]);
                   for (String n : names) {
                       //缓存名称
                       cacheName(clazz, n);
                       // 加入到extensionClasses中进行缓存
                       saveInExtensionClass(extensionClasses, clazz, n, overridden);
                   }
               }
           }
       }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

到此,getExtensionClasses方法结束 ,相关的Class信息已经加载完成 再来看 createAdaptiveExtensionClass

private Class<?> createAdaptiveExtensionClass() {
       // 动态创建对应类的适配类,创建类的内容,生成的类名如 Protocol$Adaptive
       // Dubbo 主要是以URL作为参数传递,动态获取扩展类也是依赖于URL对象,对于增加了@Adaptive 注解的方法,必须有URL作为参数,或是参数必须有URL的成员
       String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
       ClassLoader classLoader = findClassLoader();
       // 动态编译
       org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
       // 返回动态创建的适配类
       return compiler.compile(code, classLoader);
}
1
2
3
4
5
6
7
8
9
10

生成的动态适配类:

  package org.apache.dubbo.rpc;
  
  import org.apache.dubbo.common.extension.ExtensionLoader;
  // 生成的适配类
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
      public void destroy() {
        throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
   }

      public int getDefaultPort() {
          throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
      }
  
      public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
          if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
          if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
          //依据参数的中的对应参数,获取真实的Protocol实现类
          org.apache.dubbo.common.URL url = arg0.getUrl();
          String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
          if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
          org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
          return extension.export(arg0);
      }
  
      public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
          if (arg1 == null) throw new IllegalArgumentException("url == null");
          org.apache.dubbo.common.URL url = arg1;
          String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
          if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
          org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
          return extension.refer(arg0, arg1);
      }
  
      public java.util.List getServers() {
          throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }
  }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

从源码中,上面有讲到 cachedAdaptiveClass变量,这个是当一个接口的实现类有 加 @Adaptive时,会作为默认的适配类,不需要动态再创建了 如 上面的Compiler 的实现类

@Adaptive
public class AdaptiveCompiler implements Compiler {

    private static volatile String DEFAULT_COMPILER;

    public static void setDefaultCompiler(String compiler) {
        DEFAULT_COMPILER = compiler;
    }

    @Override
    public Class<?> compile(String code, ClassLoader classLoader) {
        Compiler compiler;
        ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
        String name = DEFAULT_COMPILER; // copy reference
        if (name != null && name.length() > 0) {
            compiler = loader.getExtension(name);
        } else {
            // 获取默认的方式
            compiler = loader.getDefaultExtension();
        }
        return compiler.compile(code, classLoader);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 获取指定名称的扩展点

注意

这种方式和获取适配类的方式,大致加载逻辑类似,但这里是明确了具体的实现类,是通过名称进行获取的,比较特殊的点如下:

 private T createExtension(String name, boolean wrap) {
        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);

            // 如果wrap为true时,则会从cachedWrapperClasses已经缓存的包装类进行
            // 如获取名为registry的 Protocol 接口的实例,会返回一个 ProtocolFilterWrapper
            if (wrap) {

                List<Class<?>> 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);
                        if (wrapper == null
                                || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
                            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                        }
                    }
                }
            }
            // 注入依赖后,初始化实例,调用 lifecycle.initlialize()
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 3. 自动激活

时序图3.png

笔记

**getActivateExtension()**方法用来获取自动激活的扩展点实例集合,如果希望某个扩展点实现自动激活,只需要在类上加@Activate 注解即可,还可以配置 注解的group 和 value 属性来设置自动激活的条件。例如某些扩展点只会在 Provider 端激活,而有些只会在 Consumer 端激活。

public @interface Activate {
    // 自动激活时匹配的Group
	String[] group() default {};

    // 自动激活时匹配的Value
    String[] value() default {};

    // 扩展点顺序
    int order() default 0;
}
1
2
3
4
5
6
7
8
9
10

首先,需要从 URL 中解析出 Key 对应的 Value,多个扩展点名称用逗号分割

public List<T> getActivateExtension(URL url, String key, String group) {
    // 获取Key对应的Value
    String value = url.getParameter(key);
    // Value使用,拆分
    return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
}
1
2
3
4
5
6

激活的扩展点实例使用 TreeMap 存储,Key 会按照注解里的 order 属性进行排序

Map<Class<?>, T> activateExtensionsMap = new TreeMap<>(activateComparator);
List<String> names = values == null ? new ArrayList<>(0) : asList(values);
Set<String> namesSet = new HashSet<>(names);
1
2
3

Value 如果配置了-default,则会排除默认的自动激活类,反之会先加载默认的激活类,此时并不会加载 Value 指定的扩展类。

 public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> activateExtensions = new ArrayList<>();
            List<String> names = values == null ? new ArrayList<>(0) : asList(values);
            if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
			// 在加载对应的接口的具体实现类时,会判断是否有加 `@Activate`注解进行标识,
			//如果有会加入到cachedActivates进行缓存	
            getExtensionClasses();
            for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
                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 (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;
				}
                // 是否符合
                // 也会排除类似 -dubbo
                if (isMatchGroup(group, activateGroup)
                        && !names.contains(name)
                        && !names.contains(REMOVE_VALUE_PREFIX + name)
                        && isActive(activateValue, url)) {
                    activateExtensions.add(getExtension(name));
                }
            }
                // 进行排序
            activateExtensions.sort(ActivateComparator.COMPARATOR);
        }
        List<T> loadedExtensions = new ArrayList<>();
        for (int i = 0; i < names.size(); i++) {
            String name = names.get(i);
            if (!name.startsWith(REMOVE_VALUE_PREFIX)
                    && !names.contains(REMOVE_VALUE_PREFIX + name)) {
                if (DEFAULT_KEY.equals(name)) {
                    if (!loadedExtensions.isEmpty()) {
                        activateExtensions.addAll(0, loadedExtensions);
                        loadedExtensions.clear();
                    }
                } else {
                    loadedExtensions.add(getExtension(name));
                }
            }
        }
        if (!loadedExtensions.isEmpty()) {
            activateExtensions.addAll(loadedExtensions);
        }
        return activateExtensions;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

如果 Value 指定了-default,会影响扩展点的顺序,default 内的扩展点依然是有序的,但是 default 前后的扩展点将不会根据 order 排序,例如:

`extA,default,extB`
extA的顺序将在所有默认扩展点之前,extB的顺序将在所有默认扩展点之后
1
2

代码如下:

if (namesSet.contains(DEFAULT_KEY)) {
	ArrayList<T> extensionsResult = new ArrayList<>(activateExtensionsMap.size() + names.size());
	for (int i = 0; i < names.size(); i++) {
		String name = names.get(i);
		//如果 Value 没有指定-default,那么所有扩展点实例将全部存储在 TreeMap 中,
		//全部都是有序的
		if (!name.startsWith(REMOVE_VALUE_PREFIX)
			&& !namesSet.contains(REMOVE_VALUE_PREFIX + name)) {
			if (!DEFAULT_KEY.equals(name)) {
				if (containsExtension(name)) {
					extensionsResult.add(getExtension(name));
				}
			} else {
				extensionsResult.addAll(activateExtensionsMap.values());
			}
		}
	}
	return extensionsResult;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 总结

笔记

SPI 机制使用了策略模式,一个接口多种实现,开发者面向接口编程,具体实现并不在程序中硬编码指定,而是通过配置文件的方式在外部指定。 Java 内置了 SPI 机制,但是存在一些缺陷,例如:不支持按需加载,浪费资源,排查困难等等,因此 Dubbo 自己定义了一套规范,开发了自己的 SPI 功能。 Dubbo SPI 进行了大量的优化和功能增强,它支持按需加载,并且对扩展对象做了缓存,不会重复创建对象。获取扩展对象的方式更加灵活,还增加了诸如自动包装、IOC 和 AOP、自动激活、自适应调用等多重高级特性。

  1. 在Dubbo中使用SPI实现动态扩展类,并通过@Adaptive 实现在运行时动态选择扩展类
  2. 在Dubbo中,自适应适配类依赖于 URL对象,在参数或是参数的成员中需要有URL对象
  3. 当 @Adaptive标注在类上,则会是作为自适应扩展类,不会动态生成相应的代理类
  4. 当 @Adaptive标注在方法上,则会生成动态的自适应扩展类,如果没有参数会以@SPI的值作为默认值;有参数就会以URL中对应的key的值作为扩展的key
在 GitHub 上编辑此页 (opens new window)
#Dubbo#SPI
最后更新: 2022/10/04, 16:10:00
服务调用过程
负载均衡机制

← 服务调用过程 负载均衡机制→

Theme by Vdoing | Copyright © 2022-2025 zdk | notes
湘ICP备2022001117号-1
川公网安备 51142102511562号
本网站由 提供CDN加速/云存储服务
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式