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

参考

相关推荐
爱上语文1 小时前
请求响应:常见参数接收及封装(数组集合参数及日期参数)
java·开发语言·spring boot·后端
浪 子1 小时前
SpringBoot mq快速上手
java·spring boot·spring
我爱李星璇2 小时前
Spring Boot项目的创建
java·数据库·spring boot
全栈开发帅帅3 小时前
基于springboot+vue实现的创新创业学分管理系统 (源码+L文+ppt)4-111
spring boot·后端·oracle
宋冠巡8 小时前
Spring Boot Validation 封装自定义校验注解和校验器(validation-spring-boot-starter)
spring boot·参数校验·validation
( •̀∀•́ )92012 小时前
Spring Boot 启动流程详解
java·spring boot·后端
paterWang12 小时前
小程序-基于java+SpringBoot+Vue的音乐播放器小程序设计与实现
java·spring boot·小程序
K丶one13 小时前
Spring boot + Vue2 整合人脸识别登录
java·spring boot·spring
baozhengw13 小时前
IntelliJ+SpringBoot项目实战(23)--整合RabbitMQ
spring boot·rabbitmq·java-rabbitmq·消息中间件
晚风 -13 小时前
SprinBoot整合KafKa的使用(详解)
spring boot·分布式·kafka