本文介绍Spring中关于自动装配META-INF/spring.factories文件的使用
- 使用方式
- 实现原理
- 内容延伸
- 总结
使用方式
在SpringBoot中,有一种自动装配的过程,是通过在文件classpath:/META-INF/spring.factories
中添加配置org.springframework.boot.autoconfigure.EnableAutoConfiguration
的方式来配置Bean,下面配置自定义Bean com.ethan.diveinspringboot.bean.HelloWorld
类,
package com.ethan.diveinspringboot.bean;
public class HelloWorld {
public void test() {
System.out.println("hello world");
}
}
在resources
资源文件夹下新建文件META-INF/spring.factories
文件,内容如下:
# 自动装配
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ethan.diveinspringboot.bean.HelloWorld
添加测试类,并在配置类上添加注解@EnableAutoConfiguration
@EnableAutoConfiguration
public class EnableAutoConfigurationBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableAutoConfigurationBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
HelloWorld helloWorld = context.getBean(HelloWorld.class);
helloWorld.test();
// 关闭上下文
context.close();
}
}
测试结论得出com.ethan.diveinspringboot.bean.HelloWorld
类被成功注册成为Bean。
实现原理
关于自动装配META-INF/spring.factories
配置文件的实现原理,可以从两个方面入手
META-INF/spring.factories
目录@EnableAutoConfiguration
的使用
META-INF/spring.factories文件
先看META-INF/spring.factories
文件是从哪里开始使用的,通过查找,发现在类org.springframework.core.io.support.SpringFactoriesLoader#FACTORIES_RESOURCE_LOCATION
中定义
/**
* General purpose factory loading mechanism for internal use within the framework.
*
* <p>{@code SpringFactoriesLoader} {@linkplain #loadFactories loads} and instantiates
* factories of a given type from {@value #FACTORIES_RESOURCE_LOCATION} files which
* may be present in multiple JAR files in the classpath. The {@code spring.factories}
* file must be in {@link 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:
*
* <pre class="code">example.MyService=example.MyServiceImpl1,example.MyServiceImpl2</pre>
*
* where {@code example.MyService} is the name of the interface, and {@code MyServiceImpl1}
* and {@code MyServiceImpl2} are two implementations.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.2
*/
public abstract class SpringFactoriesLoader {
/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
}
以上是SpringFactoriesLoader
抽象类的部分代码,首先定义资源文件META-INF/spring.factories
,最后一个属性cache
类型为Map<ClassLoader, MultiValueMap<String, String>>
,ClassLoader
代表当前加载类的加载器,MultiValueMap<String, String>>
表示这个ClassLoader
加载器加载的类的集合。而MultiValueMap
集合的value
可以存储多个值,而非一个值,所以cache
的存储的结构应该为:
Map<类加载器,多值集合<键,多值集合>>, 这样的结构
cache
属性在方法org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
中被初始化
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 从缓存中获取类加载加载的键值集合
MultiValueMap<String, String> result = cache.get(classLoader);
// 若缓存cache中存在,则直接返回
if (result != null) {
return result;
}
try {
// 获取所有资源文件META-INF/spring.factories中
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);
// 将资源文件META-INF/spring.factories 文件读取成为Properties类
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// 读取Properties实例,将相同键的集合,放入result中
for (Map.Entry<?, ?> entry : properties.entrySet()) {
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
// 将读取的文件,放入到cache中,并返回结果
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
根据注释内容,完整读取了META-INF/spring.factories
中的内容。
EnableAutoConfiguration
注解的使用
在Spring Enable注解装配中,我们曾分析过@Enable*
的实现原理,那么我们直接看EnableAutoConfiguration
功能实现的类org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
, AutoConfigurationImportSelector
依赖于方法AutoConfigurationImportSelector#selectImports
实现,方法如下:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取候选配置类,将准备加载成为Bean的类添加到configurations集合中
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
// 移除重复的类
configurations = removeDuplicates(configurations);
// 根据类的配置,获取需要排除的类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 根据AutoConfigurationImportFilter 过滤类,过滤掉无需加载的类
configurations = filter(configurations, autoConfigurationMetadata);
// 触发自动导入事件
fireAutoConfigurationImportEvents(configurations, exclusions);
// 返回待加载的Bean的字符串数组
return StringUtils.toStringArray(configurations);
}
从第9行代码,获取配置类字符串集合,进一步分析getCandidateConfigurations
方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
这里看第3行代码,可以关联到上小结提到的SpringFactoriesLoader
类,类SpringFactoriesLoader#loadFactoryNames
方法定义如下:
/**
* Load the fully qualified class names of factory implementations of the
* given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
* class loader.
* @param factoryClass the interface or abstract class representing the factory
* @param classLoader the ClassLoader to use for loading resources; can be
* {@code null} to use the default
* @see #loadFactories
* @throws IllegalArgumentException if an error occurs while loading factory names
*/
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
此方法将获取所有spring.factories
文件中配置的键为factoryClass.getName
的集合。
回到AutoConfigurationImportSelector#getCandidateConfigurations
方法,可以看出,这个方法将从spring.factories
文件中获取所有的键为org.springframework.boot.autoconfigure.EnableAutoConfiguration
的值集合,然后通过后期的判断,加载成为Spring容器的Bean。
小结:META-INF/spring.factories
文件在SpringFactoriesLoader
类中定义,可以加载所有spring.factories
文件的键为org.springframework.boot.autoconfigure.EnableAutoConfiguration
的值为Spring容器管理的Bean。这种方式,模仿了Java SPI技术。
内容延伸
在spring-boot-autoconfigure-x.x.x.RELEASE.jar
中的spring.factories
中有各种key的使用。
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
.......
# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer
# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider
这些Key,都在不同的场景中使用,比如org.springframework.context.ApplicationContextInitializer
键的所有值,必须为接口org.springframework.context.ApplicationContextInitializer
的实现,并且在SpringApplication
初始化时,调用ApplicationContextInitializer#initialize(C applicationContext)
方法。这种方式,模仿了Java SPI技术。
总结
- 可以通过在META-INF/spring.factories文件中添加
org.springframework.boot.autoconfigure.EnableAutoConfiguration
键,添加多个类的全名,然后在启动类上添加@EnableAutoConfiguration
注解来注册Bean类。 META-INF/spring.factories
文件在SpringFactoriesLoader
中定义,内容被加载到cache
属性中。@EnableAutoConfiguration
注解的实现,调用SpringFactoriesLoader#loadSpringFactories
方法读取键为org.springframework.boot.autoconfigure.EnableAutoConfiguration
的所有值。SpringFactoriesLoader
类原理,仿照JAVA SPI技术实现。