概述
面试中频率非常高的一个问题:Spring Boot如何实现自动配置?或其原理是什么?
如何简洁而又条理地抓住重点来回答这个问题,其实有点难。
必须要提到的几个要点:
- 约定优于配置:Convention over Configuration,通过扫描类路径上的依赖和现有的 Bean 定义,动态地为应用提供合理的默认配置;
- @EnableAutoConfiguration注解:通过启动类上的@SpringBootApplication引入;
AutoConfigurationImportSelector.selectImports()
:动态加载配置类;- 条件注解:根据类路径依赖、现有Bean、属性配置等条件决定是否启用某些配置;
spring.factories
文件:基于SPI机制;AutoConfiguration.imports
文件:Spring Boot 3.0版本后推荐的方式。
原理
分析自动配置原理的入口是启动类,启动类使用@SpringBootApplication,这是一个组合注解,包括@EnableAutoConfiguration。
另外,自动配置源码在spring-boot-autoconfigure-3.2.4.jar
,即,本文基于Spring Boot 3.2.4版本。
EnableAutoConfiguration
该注解的源码:
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
面对一个注解类,对其进行分析,要从两个地方入手:
- 添加的注解:如@Target、@AutoConfigurationPackage等。
- 属性:如exclude;
@Target等4个注解可略过,不清楚可参考Java基础之注解。
@AutoConfigurationPackage,作用是将添加该注解的类所在的package作为自动配置package进行管理。
@Import,作用是将所有符合自动配置条件的Bean定义加载到Spring IoC容器。看注解时,还得看其参数,此处为AutoConfigurationImportSelector.class
,请看后文。
此注解有两个属性exclude
和excludeName
,功能定位几乎相同,都是用于排除自动配置生效类,具体代码看getExclusions方法。
此外,还有个字段,ENABLED_OVERRIDE_PROPERTY
在AutoConfigurationImportSelector.selectImports()
方法里用于判断自动配置是否生效:
java
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
return true;
}
AutoConfigurationImportSelector
看一下源码:
java
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
// 关闭自动配置时返回空Entry
private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();
// 关闭自动配置时返回空数组
private static final String[] NO_IMPORTS = {};
// 用于排除自动配置类
private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
//
private ConfigurableListableBeanFactory beanFactory;
//
private Environment environment;
// 类加载器
private ClassLoader beanClassLoader;
// 资源加载器
private ResourceLoader resourceLoader;
// 配置类过滤器
private ConfigurationClassFilter configurationClassFilter;
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
几个属性见上面的注释。
核心方法selectImports
是选择自动配置的主入口,返回一个包含许多自动配置类信息的字符串数组,主要代码就是调用getAutoConfigurationEntry
方法,源码如下:
java
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 核心方法:返回自动配置类列表
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重
configurations = removeDuplicates(configurations);
// 排除
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查
checkExcludedClasses(configurations, exclusions);
// 移除被排除的配置类
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
// 核心方法:加载自动配置类
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
看看核心方法getCandidateConfigurations
源码:
java
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 核心代码行,注意load第一个参数
List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).getCandidates();
// 有省略
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.");
return configurations;
}
从上面的Assert语句可知,解析META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件获取自动配置类。
不信的话,可继续看看ImportCandidates.load
方法:
java
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
Assert.notNull(annotation, "'annotation' must not be null");
ClassLoader classLoaderToUse = decideClassloader(classLoader);
String location = String.format("META-INF/spring/%s.imports", annotation.getName());
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
List<String> importCandidates = new ArrayList();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
importCandidates.addAll(readCandidateConfigurations(url));
}
return new ImportCandidates(importCandidates);
}
在目录META-INF/spring/
下寻找以imports
为后缀(格式)的文件。严格来说,此处使用String.format()
来拼接文件名,即寻找以AutoConfiguration的完整包名为文件名的.imports
文件,即org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件:
此文件是一个文本文件,有若干行,一行代表一个自动配置类,遵循严格的命名规范org.springframework.boot.autoconfigure.**.**.*AutoConfiguration
,截止Spring Boot-3.2.4版本,共有152个自动配置类。
SpringFactoriesLoader
上面提到getAutoConfigurationEntry
方法里,还有个核心方法fireAutoConfigurationImportEvents
,看看其源码:
java
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
// 重点看
List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
if (!listeners.isEmpty()) {
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
for (AutoConfigurationImportListener listener : listeners) {
invokeAwareMethods(listener);
listener.onAutoConfigurationImportEvent(event);
}
}
}
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
}
此处,使用Spring 4提供的的SpringFactoriesLoader工具类。通过SpringFactoriesLoader.loadFactoryNames()
读取ClassPath下面的META-INF/spring.factories
文件。看看SpringFactoriesLoader
类相关方法:
java
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
return forDefaultResourceLocation(classLoader).load(factoryType);
}
public static SpringFactoriesLoader forDefaultResourceLocation(@Nullable ClassLoader classLoader) {
return forResourceLocation(FACTORIES_RESOURCE_LOCATION, classLoader);
}
forDefaultResourceLocation
方法用于加载配置文件,再看看load相关方法:
java
public <T> List<T> load(Class<T> factoryType) {
return load(factoryType, null, null);
}
public <T> List<T> load(Class<T> factoryType, @Nullable ArgumentResolver argumentResolver, @Nullable FailureHandler failureHandler) {
Assert.notNull(factoryType, "'factoryType' must not be null");
List<String> implementationNames = loadFactoryNames(factoryType);
logger.trace(LogMessage.format("Loaded [%s] names: %s", factoryType.getName(), implementationNames));
List<T> result = new ArrayList<>(implementationNames.size());
FailureHandler failureHandlerToUse = (failureHandler != null) ? failureHandler : THROWING_FAILURE_HANDLER;
for (String implementationName : implementationNames) {
T factory = instantiateFactory(implementationName, factoryType, argumentResolver, failureHandlerToUse);
if (factory != null) {
result.add(factory);
}
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
load方法用于加载Spring Bean。
回到spring.factories
文件,典型的Java配置文件,配置格式为Key=Value
:
# ApplicationContext Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
参考Java学习之SPI、SpringFactoriesLoader。
案例
以org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件里的第一个类SpringApplicationAdminJmxAutoConfiguration为例,其源码:
java
@AutoConfiguration(after = JmxAutoConfiguration.class)
@ConditionalOnProperty(prefix = "spring.application.admin", value = "enabled", havingValue = "true", matchIfMissing = false)
public class SpringApplicationAdminJmxAutoConfiguration {
//
private static final String JMX_NAME_PROPERTY = "spring.application.admin.jmx-name";
private static final String DEFAULT_JMX_NAME = "org.springframework.boot:type=Admin,name=SpringApplication";
@Bean
@ConditionalOnMissingBean
public SpringApplicationAdminMXBeanRegistrar springApplicationAdminRegistrar(
ObjectProvider<MBeanExporter> mbeanExporters, Environment environment) throws MalformedObjectNameException {
String jmxName = environment.getProperty(JMX_NAME_PROPERTY, DEFAULT_JMX_NAME);
if (mbeanExporters != null) { // Make sure to not register that MBean twice
for (MBeanExporter mbeanExporter : mbeanExporters) {
mbeanExporter.addExcludedBean(jmxName);
}
}
return new SpringApplicationAdminMXBeanRegistrar(jmxName);
}
}
解读:
@AutoConfiguration(after = JmxAutoConfiguration.class)
:SpringApplicationAdminJmxAutoConfiguration基于JmxAutoConfiguration的自动配置生效才能生效;- 检查配置文件,如果有
spring.application.admin=true
,则自动配置会生效,引入条件注解这一话题。 - 如果没有自定义SpringApplicationAdminMXBeanRegistrar这个Bean,Spring会帮开发者自动注入。
条件注解
自动配置充分的利用Spring 4.0的条件化注解特性,能够自动配置特定的Spring Bean,用来启动某项特性。
spring.factories和AutoConfiguration.imports
在Spring Boot早期版本里,自动配置过程只有一个spring.factories
文件;Spring Boot 3.*版本后又新增一个AutoConfiguration.imports
文件。
早期Spring Boot(低于<3.0)版本,在启动时,通过SpringFactoriesLoader
读取 spring.factories
文件中EnableAutoConfiguration
声明的类,这些类被注册为候选配置类并应用于上下文初始化。
局限性:
- 文件格式固定且较为笼统,不支持更细粒度的分层控制;
- 加载所有配置类后再过滤可能影响性能。
Spring Boot 3.0后,直接从AutoConfiguration.imports
文件读取配置类,该文件没有键值对,结构更简洁。只处理与自动配置相关的内容,减少不必要的解析步骤,提高启动效率。
其他
其他在自动配置过程中比较重要的类:
@AutoConfiguration
@AutoConfigureAfter
@AutoConfigureBefore
问题
项目依赖复杂的情况下,因为一些不必要的自动配置加载而导致应用启动失败。
解决方法:
- 在Maven的
pom.xml
文件中exclusions排除调不必要的依赖; - 禁用指定的自动化配置来避免加载不必要的自动化配置
- 注解方式:如
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
- 当使用@SpringBootApplication时,也可使用它的exclude属性来指定;
- 当使用@SpringCloudApplication时,由于它没有exclude属性,需要@EnableAutoConfiguration注解配合使用。
- 配置文件:如增加配置
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- 注解方式:如