本文将介绍spring-core
中的SpringFactoriesLoader
和spring.factories
。
1 引言
在Spring Web MVC配置方式汇总(二)和Spring Web MVC配置方式汇总(三)中我们介绍了反射、SPI和SCI机制。这些技术框架或名词看似毫不相关,其实本质上处理的是一样的问题,都是将程序或应用的部分控制逻辑暴露出去,便于配置和调整。
控制反转是IoC容器的标准特性,我们曾经在文章中也说过:Spring的IoC容器的设计思想便是将对象管理、对象与对象间的依赖关系从系统中抽离出来,交由外部控制。
但无论框架怎么改变,架构怎么设计,也不管对象管理、对象与对象间的依赖关系交由谁来负责,它总在那儿,无法抹去。变化的只是形式,但信息却不能丢失。
Spring框架提供了大量的注解来支持IoC容器,管理Bean对象的创建、使用和销毁。另外,Spring中也提供了一些工具类,这些工具类非常适合在上述场景下使用。
SpringFactoriesLoader
就是其中的一个,这篇文章会对其机制进行介绍。
2 例子
我们先用一个例子解释SpringFactoriesLoader
是如何使用的。
首先需要在pom.xml中增加必要的配置和依赖,主要是需要引入spring-core
包。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring-core</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
</dependencies>
</project>
设计一个Application
类,编写main
方法并创建一个实现了Converter
接口的名为NoOpConverter
的内部类。
public class Application {
public static void main(String[] args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
List<Converter> converters = SpringFactoriesLoader.loadFactories(Converter.class, classLoader);
System.out.println(converters);
}
static class NoOpConverter implements Converter {
public Object convert(Object o, Class aClass, Object o1) {
return null;
}
}
}
在resources
目录下增加子文件夹META-INF
,在META-INF
中增加一个名为spring.factories
的文件,文件内容为:
org.springframework.cglib.core.Converter=org.example.app.Application$NoOpConverter
运行程序,得到的结果如下图所示:
SpringFactoriesLoader运行结果示例
这样的运行结果和SPI以及SCI的运行结果非常相似。只不过在SPI和SCI中,定义文件需要放在META-INF/services
目录下,文件名称需要定义为接口名称。在这里,定义文件是META-INF/spring.factories
,文件内容为接口名称=实现类
。
3 源码解释
下图是SpringFactoriesLoader.loadFactories(...)
方法的javadoc,其中指出了该方法的用途。该方法从META-INF/spring.factories
文件中加载指定类型的对象的实现并返回给调用者。
SpringFactoriesLoader.loadFactories的javadoc
SpringFactoriesLoader.loadFactories(...)
的代码比较简单,主要就是调用loadSpringFactories(...)
方法拿到META-INF/spring.factories
文件中定义的所有对象的清单列表,然后通过传入的对象类型进行过滤后返回。
loadSpringFactories(...)
方法首先从cache
中尝试获取数据,如果没有获取到则开始遍历classpath,拿到所有META-INF/spring.factories
文件。读取这些文件的内容后,加载到cache
中再返回。参考下面展示的代码段:
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) return result;
try {
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();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException ex) {
...
}
}
4 总结
本文主要介绍SpringFactoriesLoader
的功能和实现机制,该类具有如下特性:
-
读取所有
META-INF/spring.factories
文件的配置内容,它为使用了它的应用框架提供了一个稳定的扩展点; -
无需实例化,核心方法是静态方法,因此不需要通过容器来管理。也就是说,
SpringFactoriesLoader
提供的加载配置信息的功能可以在应用还未创建ApplicationContext
之前使用;
SpringFactoriesLoader
可以作为IoC容器(即各种不同类型的ApplicationContext
)启动的入口。实际上很多框架也确实在spring.factories
中定义了和其相关的各种接口实现类,以帮助整个框架完成初始化。