- SpringFactoriesLoader 作用介绍
- 源码解析
- SpringFactoriesLoader的流程图
其源码注释如下
General purpose factory loading mechanism for internal use within the framework. SpringFactoriesLoader loads and instantiates factories of a given type from "META-INF/spring.factories" files which may be present in multiple JAR files in the classpath. The spring.factories file must be in Properties format, where the key is the fully qualified name of the interface or abstract class, and the value is a comma-separated list of implementation class names. For example: example.MyService=example.MyServiceImpl1,example.MyServiceImpl2 where example.MyService is the name of the interface, and MyServiceImpl1 and MyServiceImpl2 are two implementations.
翻译成中文就是如下四点特性
- 框架内部使用的通用工厂加载机制
- classpath下多个jar包META-INF/spring.factories中读取文件并初始化类
- 文件的内容必须是kv形式, 即必须满足Properties 格式
- 文件的内容key是全限定名(接口或抽象类), value是接口或抽象类的实现, 多个实现用逗号进行分割.
SpringBoot启动分为框架运行, 框架初始化, 框架启动, 自动化装配. SpringFactoriesLoader工厂加载是在如下图的框架初始化的ApplicationContextInitializer中进行的. SpringBoot启动 框架初始化即SpringApplication的构造方法如下, 系统初始化调用了setInitializers方法. 并调用了
getSpringFactoriesInstances
方法,
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
getSpringFactoriesInstances
方法调用了如下的getSpringFactoriesInstances
同名方法, 方法的重载
private Collection getSpringFactoriesInstances(Class type, Class[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
上面的方法中, 调用了SpringFactoriesLoader.loadFactoryNames
方法, 该方法如下:
public static List loadFactoryNames(Class factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
该方法调用了loadSpringFactories
方法如下
private static Map loadSpringFactories(@Nullable ClassLoader classLoader) {
// 先从缓存中去拿数据, 如果拿到了不为null ,那么直接返回.
MultiValueMap result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration 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) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
上面的方法中, 有如下的这行代码.
Enumeration urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
其中FACTORIES_RESOURCE_LOCATION
的值为如下 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
即classLoader类加载器会去从META-INF/spring.factories
中获取要加载的类资源
通过如下的断点可以看到, 加载到了META-INF/spring.factories
的绝对路径为file:/D:/mycode/my-springboot2-study/target/classes/META-INF/spring.factories
并且构建了Properties
对象, 其对象的内容为spring.factories
文件中写的键值对. 逐个遍历spring.factories文件中的key value. value值如果是用逗号分隔的, 那么就Spring提供的工具类
StringUtils.commaDelimitedListToStringArray
去进行分隔, 分隔完成后, 把接口或抽象类作为key, 所有的实现类作为value, 放入result 缓存中去, 下次拿的时候, 直接从缓存中获取. 再次进行遍历 , 可以看到会从jar包中
jar:file:/D:/developsoft/jarslib/org/springframework/boot/spring-boot/2.3.12.RELEASE/spring-boot-2.3.12.RELEASE.jar!/META-INF/spring.factories
去 获取spring.factories
加载资源. 从jar包中读取如下的类资源
最终的result的值如下 , 可以看到
org.springframework.context.ApplicationContextInitializer
的实现类中有我们在配置文件中写的com.thc.myspringboot2study.initializer.FirstInitializer
上一步return了result之后, 调用如下的这行方法
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
其中getOrDefault
方法为map接口的方法, 该方法的含义是第一个参数为key, 第二个参数为默认值. 如果该key在map中存在, 则返回该key对应的value, 如果不存在, 则返回一个空集合. 断点获取如下的接口, 即org.springframework.context.ApplicationContextInitializer
的key是存在的, 即它有如下的这些实现类. 返回到
getSpringFactoriesInstances
, 可以看到获取的names, 就是接口实现的类名. 接着在
getSpringFactoriesInstances
方法中调用了下面一行 createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names)
方法, 该方法是通过反射去创建实例对象. createSpringFactoriesInstances 方法如下:
- 创建实现子类大小容量的集合
- 循环遍历这些实现类, 依次通过反射去创建实例
- 把创建的实例, 加入到集合中去.
private List createSpringFactoriesInstances(Class type, Class[] parameterTypes,
ClassLoader classLoader, Object[] args, Set names) {
// 创建实现子类大小容量的集合
List instances = new ArrayList(names.size());
// 循环遍历这些实现类, 依次通过反射去创建实例
for (String name : names) {
try {
Class instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor constructor = instanceClass.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
// 把创建的实例, 加入到集合中去.
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
创建完实例之后, 调用AnnotationAwareOrderComparator.sort(instances)
方法去进行排序. 可以看到它是基于
OrderComparator
去排序的. 经历完上面的步骤之后, 再回到SpringApplication的构造方法, 调用
setInitializers
方法
setInitializers
方法 如下, 把ApplicationContextInitializer的实现类 加入到了spring容器当中.
- SpringBoot框架中从类路径jar包(各个jar包)中读取特定文件 spring.factories实现扩展类的载入
loadSpringFactories
方法如下
private static Map loadSpringFactories(@Nullable ClassLoader classLoader) {
// 先从缓存中去拿数据, 如果拿到了不为null ,那么直接返回.
MultiValueMap result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration 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) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
其流程图如下: 图片在线浏览 https://www.processon.com/view/link/60c5e1ba0e3e7468f4cf7d6f