Dubbo 的 SPI 机制
参考自:
- github: ExtensionLoader.java
- 敖丙:《Dubbo系列》-Dubbo SPI机制
SPI 是什么
SPI (Service Provider Interface),是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。
简单来说就是用来解耦,实现插件的自由插拔。
使用场景
- JDBC驱动加载案例:利用Java的SPI机制,我们可以根据不同的数据库厂商来引入不同的JDBC驱动包;
- SpringBoot的SPI机制:我们可以在
spring.factories
中加上我们自定义的自动配置类,事件监听器或初始化器等; - Dubbo的SPI机制:Dubbo 更是把 SPI 机制应用的淋漓尽致,Dubbo 基本上自身的每个功能点都提供了扩展点,比如提供了集群扩展,路由扩展和负载均衡扩展等差不多接近 30 个扩展点。如果Dubbo的某个内置实现不符合我们的需求,那么我们只要利用其 SPI 机制将我们的实现替换掉Dubbo的实现即可。
Java SPI 介绍
而 Java SPI 是 JDK 内置的一个服务发现机制,它使得接口和具体实现完全解耦。具体的就是你定义了一个接口,然后在 META-INF/services
目录下放置一个与接口同名的文本文件,文件的内容为接口的实现类,多个实现类用换行符分隔。这样就可以通过配置来决定某个接口具体用哪个实现。
最常见的 Java SPI 的使用就是访问数据库时候用到的
java.sql.Driver
接口。比如使用mysql-connector-java-8.0.17
的模块时,会在META-INF/services
目录下有java.sql.Driver
文件,文件内容只有一行com.mysql.cj.jdbc.Driver
。就是指定java.sql.Driver
接口的实现类为com.mysql.cj.jdbc.Driver
。 Java SPI 的使用:
先在
META-INF/services
目录下,创建以接口的全限定名为文件名的文件,然后每行的内容为接口实现类的全限定名。复制代码1
2
3
4
5
6
7// xxx 指的是接口类 ServiceLoader<xxx> serviceLoader = ServiceLoader.load(xxx.class); Iterator<xxx> iterator = serviceLoader.iterator(); while(iterator.hasNext()) { xxx xx = iterator.next(); }
Java SPI 的局限性
Java SPI 在查找扩展实现类的时候遍历 SPI 的配置文件并且将实现类全部实例化,而无法按需加载实现类,可能会造成资源的浪费。
Spring SPI 介绍
Spring 的功能和JDK的相差无几,最大的区别是所有扩展点写在一个 spring.factories 文件中
Spring 的 SPI 配置文件是一个固定的文件 - META-INF/spring.factories
,功能上和 JDK 的类似,每个接口可以有多个扩展实现,使用起来非常简单:
1
2
3
4//获取所有factories文件中配置的LoggingSystemFactory List<LoggingSystemFactory>> factories = SpringFactoriesLoader.loadFactories(LoggingSystemFactory.class, classLoader);
Spring 的 SPI 主要用在 Spring Boot 中,而 Spring Boot 中的 ClassLoader 会优先加载项目中的文件,而不是依赖包中的文件。所以如果在你的项目中定义个 spring.factories 文件,那么你项目中的文件会被第一个加载,得到的 Factories 中,项目中 spring.factories 里配置的那个实现类也会排在第一个
如果我们要扩展某个接口的话,只需要在你的项目(spring boot)里新建一个META-INF/spring.factories
文件,只添加你要的那个配置,不要完整的复制一遍 Spring Boot 的 spring.factories 文件然后修改
比如我只想添加一个新的 LoggingSystemFactory 实现,那么我只需要新建一个META-INF/spring.factories
文件
Spring SPI,不能像 Dubbo SPI 那样,获取指定的实现类,它只能按顺序获取所有实现。
Spring Boot 中 spring.factories 的配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18# Logging Systems org.springframework.boot.logging.LoggingSystemFactory= org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory, org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory, org.springframework.boot.logging.java.JavaLoggingSystem.Factory # PropertySource Loaders org.springframework.boot.env.PropertySourceLoader= org.springframework.boot.env.PropertiesPropertySourceLoader, org.springframework.boot.env.YamlPropertySourceLoader # ConfigData Location Resolvers org.springframework.boot.context.config.ConfigDataLocationResolver= org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver, org.springframework.boot.context.config.StandardConfigDataLocationResolver ......
Dubbo SPI 介绍
而 Dubbo 就自己实现了一个 SPI,拥有按需加载的功能,具体的使用是给实现类加个“名子”,通过这个名字去文件里面找到对应的实现类全限定名然后加载实例化即可。
1
2
3
4
5
6
7
8
9failover=com.alibaba.dubbo.rpc.cluster.support.FailoverCluster failfast=com.alibaba.dubbo.rpc.cluster.support.FailfastCluster failsafe=com.alibaba.dubbo.rpc.cluster.support.FailsafeCluster failback=com.alibaba.dubbo.rpc.cluster.support.FailbackCluster folking=com.alibaba.dubbo.rpc.cluster.support.FolkingCluster available=com.alibaba.dubbo.rpc.cluster.support.AvailableCluster mergeable=com.alibaba.dubbo.rpc.cluster.support.MergeableCluster broadcast=com.alibaba.dubbo.rpc.cluster.support.BroadcastCluster
并且 Dubbo SPI 除了可以按需加载实现类之外,增加了 IOC 和 AOP 的特性,还有个自适应扩展机制。
Dubbo 依靠 SPI 机制实现了插件化功能,几乎将所有的功能组件做成基于 SPI 实现,并且默认提供了很多可以直接使用的扩展点,实现了面向功能进行拆分的对扩展开放的架构。
Dubbo 对配置文件目录的约定,不同于 Java SPI ,Dubbo 分为了三类目录。
- META-INF/services/ 目录:该目录下的 SPI 配置文件是为了用来兼容 Java SPI 。
- META-INF/dubbo/ 目录:该目录存放用户自定义的 SPI 配置文件。
- META-INF/dubbo/internal/ 目录:该目录存放 Dubbo 内部使用的 SPI 配置文件。
1
2
3// 通过 Dubbo SPI 获取接口的实现类 xxx xx = ExtensionLoader.getExtensionLoader(xxx.class).getExtension("实现类对应的名字");
ExtensionLoader 是类似 Java SPI 中 ServiceLoader 的存在,负责实现类的加载。
大致流程就是先通过接口类找到一个 ExtensionLoader ,然后再通过 ExtensionLoader.getExtension(name) 得到指定名字的实现类实例。
Dubbo SPI 的源码分析 (2.6.5 version)
getExtensionLoader() 获取拓展类加载器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>(); public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { // 省略 type 为空的抛错代码 // 省略 type 不是接口的抛错代码 // 省略 type 没有标注 @SPI 注解的抛错代码 // 从缓存中获取 ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { // 如果缓存中没有的话,则新建一个,并加入缓存中 EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }
getExtension() 获取拓展类
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
31private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>(); public T getExtension(String name) { if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null"); if ("true".equals(name)) { // 获取默认的拓展类 return getDefaultExtension(); } // 从缓存中获取,并持有对象 Holder<Object> holder = cachedInstances.get(name); if (holder == null) { // 如果缓存中没有的话,就新建一个,并加入缓存中 cachedInstances.putIfAbsent(name, new Holder<Object>()); holder = cachedInstances.get(name); } Object instance = holder.get(); // 双重锁检查 if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { // 重点:获取实例化完的实现类 instance = createExtension(name); holder.set(instance); } } } return (T) instance; }
createExtension() 获取实例化完的拓展类
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
32private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>(); private T createExtension(String name) { // 获取实现类的类对象 Class<?> clazz = getExtensionClasses().get(name); if (clazz == null) { throw findException(name); } try { // 从缓存中获取 T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { // 如果缓存中没有的话,就调用反射创建实例 EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } // setter 依赖注入 injectExtension(instance); Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && !wrapperClasses.isEmpty()) { // 如果有包装类,就包装一下 for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type + ") could not be instantiated: " + t.getMessage(), t); } }
getExtensionClasses() 获取拓展类的类对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>(); 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; }
loadExtensionClasses() 加载拓展类的类对象
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
27private static final String SERVICES_DIRECTORY = "META-INF/services/"; private static final String DUBBO_DIRECTORY = "META-INF/dubbo/"; private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/"; private Map<String, Class<?>> loadExtensionClasses() { final SPI defaultAnnotation = type.getAnnotation(SPI.class); if (defaultAnnotation != null) { 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]; } } Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); // 分别从三个目录中查找 loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); loadDirectory(extensionClasses, DUBBO_DIRECTORY); loadDirectory(extensionClasses, SERVICES_DIRECTORY); return extensionClasses; }
loadDirectory() 从配置文件中加载
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) { String fileName = dir + type.getName(); try { Enumeration<java.net.URL> urls; ClassLoader classLoader = findClassLoader(); if (classLoader != null) { urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } if (urls != null) { while (urls.hasMoreElements()) { java.net.URL resourceURL = urls.nextElement(); loadResource(extensionClasses, classLoader, resourceURL); } } } catch (Throwable t) { logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", t); } } private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) { try { BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8")); try { 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) { loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name); } } 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); } } } } finally { reader.close(); } } catch (Throwable t) { logger.error("Exception when load extension class(interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t); } } private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { if (!type.isAssignableFrom(clazz)) { throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface."); } if (clazz.isAnnotationPresent(Adaptive.class)) { // 如果类标注了 @Adaptive 注解,则存一下 if (cachedAdaptiveClass == null) { cachedAdaptiveClass = clazz; } else if (!cachedAdaptiveClass.equals(clazz)) { throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName()); } } else if (isWrapperClass(clazz)) { // 如果是包装类,则存一下 Set<Class<?>> wrappers = cachedWrapperClasses; if (wrappers == null) { cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); wrappers = cachedWrapperClasses; } wrappers.add(clazz); } else { // 普通类 // 没有默认构造器,会抛出错误 clazz.getConstructor(); if (name == null || name.length() == 0) { 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 (names != null && names.length > 0) { Activate activate = clazz.getAnnotation(Activate.class); if (activate != null) { // 如果类标注了 @Adaptive 注解,则存一下 cachedActivates.put(names[0], activate); } for (String n : names) { if (!cachedNames.containsKey(clazz)) { // 记录类到名字的映射 cachedNames.put(clazz, n); } Class<?> c = extensionClasses.get(n); if (c == null) { // 记录名字到类的映射 extensionClasses.put(n, clazz); } else if (c != clazz) { throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName()); } } } } }
Adaptive 注解 - 自适应扩展机制
我们先来看一个场景,首先我们根据配置来进行 SPI 扩展的加载,但是我不想在启动的时候让扩展被加载,我想根据请求时候的参数来动态选择对应的扩展。
Dubbo 通过一个代理机制实现了自适应扩展,简单的说就是为想扩展的接口生成一个代理类,可以通过 JDK 或者 javassist 编译生成的代理类代码,然后通过反射创建实例。
这个实例里面的实现会根据本来方法的请求参数得知需要的扩展类,然后通过 ExtensionLoader.getExtensionLoader(type.class).getExtension("从参数得来的name")
,来获取真正的实例来调用。
@Adaptive 注解就是自适应扩展相关的注解,可以修饰类和方法上,在修饰类的时候不会生成代理类,因为这个类就是代理类,修饰在方法上的时候会生成代理类。
@Adaptive 注解在类上
比如这个 ExtensionFactory
有三个实现类,其中一个实现类 AdaptiveExtensionFactory
就被标注了 @Adaptive 注解。
在 ExtensionLoader 构造的时候就会去通过 getAdaptiveExtension 获取指定的扩展类的 ExtensionFactory。
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@Adaptive public class AdaptiveExtensionFactory implements ExtensionFactory { private final List<ExtensionFactory> factories; public AdaptiveExtensionFactory() { // 构造的时候,就把 ExtensionFactory 所有的实现类加入缓存中 ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class); List<ExtensionFactory> list = new ArrayList<ExtensionFactory>(); for (String name : loader.getSupportedExtensions()) { list.add(loader.getExtension(name)); } factories = Collections.unmodifiableList(list); } @Override public <T> T getExtension(Class<T> type, String name) { for (ExtensionFactory factory : factories) { // 获取实例的时候遍历缓存,只要找到一个就立即返回 T extension = factory.getExtension(type, name); if (extension != null) { return extension; } } return null; } }
ExtensionLoader 中的 getAdaptiveExtension()
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
46public T getAdaptiveExtension() { Object instance = cachedAdaptiveInstance.get(); if (instance == null) { if (createAdaptiveInstanceError == null) { synchronized (cachedAdaptiveInstance) { // 先从缓存中获取 instance = cachedAdaptiveInstance.get(); if (instance == null) { try { // 如果缓存中没有的话,就新建一个,并加入缓存中 instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { createAdaptiveInstanceError = t; throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t); } } } } else { throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } } return (T) instance; } private T createAdaptiveExtension() { try { // 创建一个实例,并且执行依赖注入 return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e); } } private Class<?> getAdaptiveExtensionClass() { // 前面已经分析过了 getExtensionClasses(); if (cachedAdaptiveClass != null) { // 此时说明有 @Adaptive 注解在类上,则直接返回实例,如 AdaptiveExtensionFactory return cachedAdaptiveClass; } // 此时说明没有 @Adaptive 注解在类上,需要动态生成类 return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
Adaptive 注解在方法上
注解在方法上则需要动态拼接代码,然后动态生成类。 主要使用的方法是 createAdaptiveExtensionClass()
,会生成包,也会生成 import 语句,类名就是接口加个$Adaptive,并且实现这接口,没有标记 @Adaptive 注解的方法调用的话直接抛错;标记了 @Adaptive 注解的方法,会根据请求的参数,即 URL 得到具体要调用的实现类名,然后再调用 getExtension()
获取。
WrapperClass - AOP
包装类是因为一个扩展接口可能有多个扩展实现类,而这些扩展实现类会有一个相同的或者公共的逻辑,可以采用 AOP 的思想,用一个包装类进行逻辑的织入,便于维护。
只需要某个扩展类的构造函数只有一个参数,并且是扩展接口类型,就会被判定为包装类,然后记录下来,用来包装别的实现类。
injectExtension - IOC
setter 注入:查找 set 方法,根据参数找到依赖对象则注入。
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
40private T injectExtension(T instance) { try { if (objectFactory != null) { // 遍历所有成员方法 for (Method method : instance.getClass().getMethods()) { // 查找 set 方法,且方法只有一个参数,访问级别为 public if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) { /** * Check {@link DisableInject} to see if we need auto injection for this property */ if (method.getAnnotation(DisableInject.class) != null) { // 不允许直接访问 continue; } // 获取参数类型 Class<?> pt = method.getParameterTypes()[0]; try { // 获取属性名 String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : ""; // 获取依赖对象 Object object = objectFactory.getExtension(pt, property); if (object != null) { // setter 注入 method.invoke(instance, object); } } catch (Exception e) { logger.error("fail to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e); } } } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance; }
Activate 注解
拿 Filter 举例,Filter 有很多实现类,在某些场景下需要其中的几个实现类,而某些场景下需要另外几个,而 Activate 注解就是标记这个用的。
它有三个属性,group 表示修饰在哪个端,是 provider 还是 consumer,value 表示在 URL 参数中出现才会被激活,order 表示实现类的顺序。
最后
以上就是痴情草莓最近收集整理的关于Dubbo 笔记(二) Dubbo 的 SPI 机制的全部内容,更多相关Dubbo内容请搜索靠谱客的其他文章。
发表评论 取消回复