SPI机制
Table of Contents generated with DocToc (opens new window)
# JDK的SPI机制-ServiceLoader
# 概述
提示
SPI,是Service Provider Interface的缩写,是JDK内置的一种服务提供发现机制。目前很多框架都用它来做服务的扩展发现,它就是一种动态发现、替换实现类的机制。 SPI的作用就是为这些被扩展的API寻找服务实现。使用SPI机制的优势是实现解耦,使得第三方服务模块的装配控制逻辑与调用者的业务代码分离。 比如我们常见的jdbc,还有SL4j日志门面,都是通过SPI机制来实现对不同数据库连接、不同日志实现的扩展的。
# JDK SPI实现标准
提示
- 当服务提供者提供了一个接口的一种具体实现后,在META-INF/services 目录下创建一个以 “接口全限定名” 为命名的文件,内容为实现类的权限定名
- 接口实现类所在的jar包,放在主程序的classpath中
- 主程序通过 java.util.ServiceLoader 动态加载实现模块,它通过扫描META-INF/services 目录下的配置文件找到实现类的全限定名,把类加载到JVM
- SPI的实现类必须要有一个无参构造
笔记
具体代码步骤
新建api的工程,提供一个接口,在接口项目中不提供实现类
新建service的工程,引入api工程,创建实现类实现api中的接口
然后在service工程的resources目录下,创建META-INF/services目录
在这个目录下,创建一个文件,文件名为接口的全类名,文件内容为接口实现类的全类名,如果有多个实现类,换行继续写即可
新建测试工程,引入api和service工程的依赖,在测试工程中使用以下方式进行测试
# 相关代码
package com.zdk.api.interfaces;
/**
* @author zhangdikai
* @date 2022-07-12 16:28
*/
public interface Eat {
void eat();
}
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("狗吃骨头");
}
}
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
接口和实现类信息文件
public class EatService {
public static void main(String[] args) {
ServiceLoader<Eat> serviceLoader = ServiceLoader.load(Eat.class);
for (Eat eat : serviceLoader) {
eat.eat();
}
}
}
2
3
4
5
6
7
8
9
输出结果
# demo工程地址
# JDK SPI机制的弊端
- 只能遍历所有实现(上面的代码中有展示),并将全部实现类都实例化,即使它没有被用到,浪费资源
- 配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们
- 没有缓存,实现类会被多次创建
- 扩展如果依赖其他的扩展,做不到自动注入和装配
- 扩展很难和其他的框架集成,比如扩展里面依赖了一个Spring Bean,原生的Java SPI不支持(不支持IOC和AOP)
- 如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位到是这个原因
# Spring中的SPI
# SpringFactoriesLoader
提示
在Spring中,扩展也是依赖spi机制完成的,只不过Spring对于扩展文件约定在Classpath 路径下的 META-INF目录下
,所有的文件名都是叫spring.factories
,文件里的内容是一个以一个个键值对的方式存储的,键为类的全限定名,值也为类的全限定名,如果有多个值,可以用逗号分割,有一点得注意的是,键和值本身约定并没有类与类之间的依赖关系(当然也可以有,得看使用场景的约定),也就是说键值可以没有任何关联,键仅仅是一种标识,代表一种场景。
最常见的自动装配的注解,@EnableAutoConfiguration,也就是代表自动装配的场景,当你需要你的类被自动装配,就可以用这个注解的全限定名作为键,自己写的类的全限定名为值,这样Spring Boot在进行自动装配的时候,就会拿这个键,找到我们自己写的实现类来完成自动装配。
自动装配的部分源代码: 这里其实就是通过@EnableAutoConfiguration的全限定名从spring.factories中加载这个键对应的所有的实现类的名称,这样就能拿到所有需要自动装配的类的全限定名了。
mybatis整合spring的自动装配功能文件 内容:
笔记
mybatis也是按照spring的规则来配置的。可以看看MybatisAutoConfiguration这个实现类,里面有mybatis是如何跟spring整合的内容。 SpringFactoriesLoader的应用场景还有很多,可以去看一下SpringBoot中的启动引导类:SpringApplication,里面多次使用到了这个SpringFactoriesLoader这个类来获取各种实现。
# Dubbo中的SPI
# 概述
Dubbo SPI 定义了一套自己的规范,同时对JDK的SPI存在的问题进行了改进
提示
优点:
- 扩展类可以按需加载,节省了资源
- SPI文件采用
"key=value"
的形式,可以根据扩展名灵活获取实现类 - 对扩展类的对象进行了缓存,避免重复创建
- 扩展类加载失败有详细日志,方便排查
- 支持IOC和AOP
# 使用规范
编写接口,接口必须加@SPI注解,代表它是一个可扩展的接口
编写实现类
在classpath下的
META-INF/dubbo
目录下,创建以接口全限定名命名的文件,文件内容为"key=value"
的格式,key是扩展点的名称,value是实现类的全限定名
通过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...");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
impl=demo.spi.wrapper.SayImpl
wrapper=demo.spi.wrapper.SayWrapper
2
// 默认获取的就是包装类
Say say = ExtensionLoader.getExtensionLoader(Say.class).getDefaultExtension();
say.say();
输出:
before...
say...
after...
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...");
}
}
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;
}
}
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);
}
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...
2
3
4
5
6
7
# 4. 自动激活
场景:某个扩展点的多个实现类需要根据规则同时启用,例如 Filter 过滤器
自动激活需要使用@Activate
注解,一旦加上该注解,表示该实现类(或方法)需要根据条件自动激活,注解属性含义如下:
属性 | 说明 |
---|---|
group | Group 匹配成功则激活 |
value | URL 中存在该 Key 则激活 |
order | 扩展点执行顺序 |
假设有Filter接口
@SPI
public interface Filter {
void invoke();
}
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...");
}
}
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...");
}
}
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
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;
}
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);
ExtensionLoader 有三个常用方法,下面分别分析:
方法名 | 备注 |
---|---|
getDefaultExtension() | 获取默认扩展点实现类实例 |
getAdaptiveExtension() | 获取自适应实例 |
getActivateExtension() | 获取自动激活实例集合 |
# 1. 默认扩展点
以getDefaultExtension() 方法为入口获取默认的扩展点实现类实例,默认情况下,如果有 Wrapper 类,会进行自动包装。
public T getDefaultExtension() {
// 加载实现类
getExtensionClasses();
if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
return null;
}
// 获取默认扩展点实现类实例
return getExtension(cachedDefaultName);
}
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;
}
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;
}
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;
}
}
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. 自适应
笔记
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();
}
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;
}
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;
}
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);
}
}
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);
}
}
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);
}
}
}
}
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);
}
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!");
}
}
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);
}
}
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);
}
}
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. 自动激活
笔记
**getActivateExtension()**方法用来获取自动激活的扩展点实例集合,如果希望某个扩展点实现自动激活,只需要在类上加@Activate
注解即可,还可以配置 注解的group
和 value
属性来设置自动激活的条件。例如某些扩展点只会在 Provider
端激活,而有些只会在 Consumer
端激活。
public @interface Activate {
// 自动激活时匹配的Group
String[] group() default {};
// 自动激活时匹配的Value
String[] value() default {};
// 扩展点顺序
int order() default 0;
}
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);
}
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);
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;
}
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的顺序将在所有默认扩展点之后
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;
}
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、自动激活、自适应调用等多重高级特性。
- 在Dubbo中使用SPI实现动态扩展类,并通过
@Adaptive
实现在运行时动态选择扩展类 - 在Dubbo中,自适应适配类依赖于 URL对象,在参数或是参数的成员中需要有URL对象
- 当
@Adaptive
标注在类上,则会是作为自适应扩展类,不会动态生成相应的代理类 - 当
@Adaptive
标注在方法上,则会生成动态的自适应扩展类,如果没有参数会以@SPI的值
作为默认值;有参数就会以URL中对应的key的值作为扩展的key