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

参考

相关推荐
计算机毕设指导67 分钟前
基于微信小程序的校园食堂点餐系统【源码文末联系】
java·spring boot·微信小程序·小程序·tomcat·maven·intellij-idea
Mr.Pascal8 分钟前
深度解读一下 springcloud 的 pom.xml 用到的标签
xml·spring boot·spring cloud
Qiuner12 分钟前
Spring Boot AOP(二) 代理机制解析
java·spring boot·后端
invicinble36 分钟前
对于设计IT系统的相关思路
spring boot
nvvas1 小时前
JAVA 关于SpringBoot4新版本阅览
java·spring boot
白宇横流学长1 小时前
基于SpringBoot实现的大创管理系统
java·spring boot·后端
梵得儿SHI1 小时前
SpringCloud 核心组件精讲:OpenFeign 实战指南-服务调用优雅实现方案(含自定义拦截器、超时重试、LoadBalance 整合避坑)
spring boot·spring·spring cloud·负载均衡·openfeign的核心应用·微服务调用·熔断组件
胡玉洋1 小时前
Spring Boot 项目配置文件密码加密解决方案 —— Jasypt 实战指南
java·spring boot·后端·安全·加密·配置文件·jasypt
苹果醋31 小时前
JAVA设计模式之观察者模式
java·运维·spring boot·mysql·nginx
JIngJaneIL1 小时前
基于java+ vue畅游游戏销售管理系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·游戏