Spring中的SPI机制

Why

  在面向对象的世界里,我们一般都主张模块之间基于接口编程。这是因为一旦涉及到具体的实现类,就违反了开闭原则,此时如果需要替换一种实现,就需要修改代码。依赖接口化了之后,就需要一种服务发现机制,帮助模块在装配时定位到具体的实现,java.util.ServiceLoader就是一个现实世界的例子。

What

1
2
Service provider interface (SPI) is an API intended to be implemented or extended by a third party. 
It can be used to enable framework extension and replaceable components.

  以上是维基百科对SPI的释义,java.util.ServiceLoader的文档则描述得更加细节一点儿。

1
2
3
A service is a well-known set of interfaces and (usually abstract) classes.  A service provider is a 
specific implementation of a service. The classes in a provider typically implement the interfaces
and subclass the classes defined in the service itself.

SpringFactoriesLoader

  SpringFactoriesLoaderSpring提供的一种服务发现机制的实现,区别于java.util.ServiceLoader,它加载位于META-INF下的spring.factoriesspring.factories的文件格式和properties配置文件相同,其中key是全限定的接口或抽象类的名称,valueCSV格式的实现类的名称,比如:

1
example.MyService=example.MyServiceImpl1,example.MyServiceImpl2

How

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
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 先读缓存
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}

try {
// 获取位于 META-INF 下的 spring.factories
// 注意这个文件可以来自多个 jar
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 读取 spring.factories 的内容
// 读取逻辑基于 spring 自身的 io 抽象
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// 按照约定,key 是接口或抽象类的名称,value 是 CSV 格式的实现类的名称
for (Map.Entry<?, ?> entry : properties.entrySet()) {
// 将文件内容转换成 Map<String, List<String>>
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
// 加入缓存
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}

  SpringFactoriesLoader的核心逻辑不过以上二三十行,大体步骤如下:

  1. 读取所有的META-INF/spring.factories文件
  2. 按照proeprties文件的格式进行解析
  3. 将解析的结果转换成Map<String, List<String>> ,其中key是全限定的接口或抽象类的名称,value是具体的实现类的名称列表
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
/**
* 查找并实例化 factoryClass 的实现类
*/
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryClass, "'factoryClass' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 查找所有实现了 T 的类
List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
}
List<T> result = new ArrayList<>(factoryNames.size());
for (String factoryName : factoryNames) {
// 反射生成实例
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
// @Order、Ordered 排序
AnnotationAwareOrderComparator.sort(result);
return result;
}

/**
* 查找 factoryClass 的实现类的名称集合
*/
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

两个公开方法#loadFactories(...)#loadFactoryNames(...)的实现比较简单,就不多叨叨了。

Use

  SpringBoot的自动装配便是使用此机制来加载外部的配置类。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!