Spring Boot自动配置原理

概述

面试中频率非常高的一个问题: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,请看后文。

此注解有两个属性excludeexcludeName,功能定位几乎相同,都是用于排除自动配置生效类,具体代码看getExclusions方法。

此外,还有个字段,ENABLED_OVERRIDE_PROPERTYAutoConfigurationImportSelector.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 Boot系列之条件注解

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

问题

项目依赖复杂的情况下,因为一些不必要的自动配置加载而导致应用启动失败。

解决方法:

  1. 在Maven的pom.xml文件中exclusions排除调不必要的依赖;
  2. 禁用指定的自动化配置来避免加载不必要的自动化配置
    • 注解方式:如@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
      • 当使用@SpringBootApplication时,也可使用它的exclude属性来指定;
      • 当使用@SpringCloudApplication时,由于它没有exclude属性,需要@EnableAutoConfiguration注解配合使用。
    • 配置文件:如增加配置spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

参考

相关推荐
用户908324602732 天前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
用户8307196840823 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
Java水解3 天前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解3 天前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
洋洋技术笔记3 天前
Spring Boot Web MVC配置详解
spring boot·后端
初次攀爬者4 天前
Kafka 基础介绍
spring boot·kafka·消息队列
用户8307196840824 天前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
Java水解4 天前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端
初次攀爬者5 天前
RocketMQ在Spring Boot上的基础使用
java·spring boot·rocketmq
花花无缺5 天前
搞懂@Autowired 与@Resuorce
java·spring boot·后端