解密 Spring Boot 自动配置:原理、流程与核心组件协同
一、SpringBoot简介
Spring Boot 是一款基于 Spring 框架的开源框架,它旨在简化 Spring 应用的初始搭建以及开发过程。在 Spring 框架出现之后,虽然为 Java 开发带来了诸多便利,但在实际应用中,开发者往往需要进行大量的 XML 配置或复杂的注解配置,还需要手动管理各种依赖的版本,这无疑增加了开发的难度和工作量。
而 Spring Boot 凭借其 “约定优于配置” 的理念,极大地改变了这一现状。它内置了多种默认配置,能够自动配置很多常用的组件和功能,开发者无需手动编写繁琐的配置代码,只需引入相应的依赖,就可以快速搭建起一个可运行的应用。同时,Spring Boot 还提供了嵌入式的服务器(如 Tomcat、Jetty 等),使得应用的部署变得更加简单,无需额外部署服务器,直接运行 Jar 包即可。
1、自动配置的重要性与优势
自动配置是 Spring Boot 的核心特性之一,它在 Spring Boot 生态中占据着至关重要的地位。
从重要性来看,自动配置是 Spring Boot 实现 “简化开发” 这一目标的关键所在。它消除了传统 Spring 应用中大量的手动配置工作,让开发者能够将更多的精力集中在业务逻辑的实现上,而不是在框架的配置细节上花费过多时间。如果没有自动配置,Spring Boot 也就失去了其最大的吸引力,与传统的 Spring 框架相比,优势将不再明显。
自动配置的优势主要体现在以下几个方面:
- 提高开发效率:开发者无需手动配置各种组件,如数据源、Web 服务器、ORM 框架等,Spring Boot 会根据项目的依赖和配置自动完成这些工作,大大缩短了项目的开发周期。
- 降低学习成本:对于新手开发者来说,不需要深入了解各种框架的配置细节,只需掌握基本的 Spring Boot 用法和相关依赖的引入方式,就能够快速开发出可用的应用。
- 减少配置错误:手动配置过程中,很容易出现配置项遗漏、配置格式错误等问题,而自动配置基于预设的规则和模板进行配置,能够有效减少这类错误的发生。
- 增强项目的一致性:在团队开发中,自动配置确保了所有开发者使用的配置方式和组件版本保持一致,避免了因个人配置习惯不同而导致的项目混乱。
二、自动配置核心注解剖析
在了解SpringBoot的自动配置前,需要先了解以下几个注解:@Conditional、@Enable
1、@Conditional注解
@Conditional 是 Spring 框架提供的一个条件注解,用于根据特定条件决定是否创建某个 Bean,实现 Bean 的动态注册。它是 Spring 条件化配置的核心,允许开发者根据环境、配置、系统属性等动态控制 Bean 的创建逻辑
使用@Conditional注解的步骤为:
步骤 1:定义条件类(实现 Condition 接口)
条件类需要实现 Condition 接口,并实现 matches() 方法,该方法返回 true 则满足条件,允许 Bean 注册;返回 false 则不注册
步骤 2:使用 @Conditional 标注 Bean
在 @Bean 方法上添加 @Conditional,指定条件类
@Conditional 的参数是 Class<? extends Condition>[],即可以指定多个条件类,所有条件都满足时才会注册 Bean(逻辑 “与” 关系)
@Conditional是一个接口,底下还有多个子接口
常用衍生注解
@ConditionalOnBean:当容器中存在指定 Bean 时才注册
@ConditionalOnClass:当类路径中存在指定类时才注册
@ConditionalOnProperty:当指定配置属性满足条件时才注册
@ConditionalOnResource:当存在指定资源文件时才注册
2、@Enable注解
SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理 是使用@Import注 解导入一些配置类,实现Bean的动态加载
@Import
@Enable底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。 而@Import提供4中用法:导入Bean 、 导入配置类 、 导入 ImportSelector 实现类,一般用于加载配置文件中的类 、导入 ImportBeanDefinitionRegistrar 实现类
导入Bean 、 导入配置类就是直接在启动类中使用@Import(类的class对象或配置类的class对象)
导入 ImportSelector 实现类,一般用于加载配置文件中的类
需要定义一个类实现ImportSelector接口,重写selectImports()方法,此方法的返回值是一个字符串数组(数组中保存了哪些类,将来就会导入哪些类),直接在字符串数组中给出需要导入类的完全限定名,然后再启动类上定义@Import(此实现类的class对象)就可直接导入
导入 ImportBeanDefinitionRegistrar 实现类
需要定义一个类实现ImportBeanDefinitionRegistrar接口,重写方法。如下
//两个参数意思为//导入类的元数据//BeanDefinition 注册器public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {//1. 构建 Figure 类的 BeanDefinitionAbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Figure.class)//指定要注册的类.getBeanDefinition();//获取构建好的 BeanDefinition//2. 向 Spring 容器注册 BeanDefinition//两个参数意思为:Bean 的名称(id),要注册的 BeanDefinitionregistry.registerBeanDefinition("figures", beanDefinition);}
通过@Import(此实现类的class对象)导入后,就会调用重写的registerBeanDefinitions()方法自动注册Figure的Bean
3、 @SpringBootApplication 注解解析
@SpringBootApplication是 Spring Boot 应用的标志性注解,它并非单一功能的注解,而是由三个子注解组成的,它们分别是分别是@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan
3.1、各子注解在自动配置中的作用
@SpringBootConfiguration:是@Configuration的特殊形式,标记一个类为配置类,允许当前类使用@Bean注解定义Bean。在自动配置中,它为应用提供了基础的配置入口,使得 Spring 容器能够识别并加载该类中的配置信息
@EnableAutoConfiguration:这是开启自动配置功能的注解,它会触发SpringBoot的自动配置机制,根据项目的依赖和配置,自动配置所需的Bean和组件
@ComponentScan:负责扫描指定包及其子包下的组件,如被@Component、@Service、@Repository、@Controller等注解标记的类,并将这些组件纳入 Spring 容器管理
4、@EnableAutoConfiguration注解详解
4.1、 自动配置功能的开启原理
@EnableAutoConfiguration注解下两个最重要的子注解,@AutoConfigurationPackage是用于指定自动配置类的包扫描路径,确保 Spring 能够自动发现并注册当前主程序类所在包及其子包下的组件,@Import注解导入AutoConfigurationImportSelector类,该类会根据项目的依赖情况和配置信息,筛选出合适的自动配置类,并将其加载到 Spring 容器中,从而实现自动配置
4.2、与 AutoConfigurationImportSelector 类的关联
AutoConfigurationImportSelector是@EnableAutoConfiguration注解实现自动配置的关键执行者,它实现了ImportSelector接口,重写了selectImports()方法。在该方法中,它会获取所有候选者的自动配置类,并根据一定的规则进行筛选,最终返回所有需要注入的类的完全限定名,并根据@Import注入
这是重写的selectImports()方法
public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}}
我们重点看它是如何导入的,它首先调用自身getAutoConfigurationEntry(annotationMetadata)方法,负责解析并获取有效的自动配置类集合,是 Spring Boot 实现 "自动配置" 功能的关键步骤
进入getAutoConfigurationEntry方法后
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {//检查自动配置是否启用if (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {//获取 @EnableAutoConfiguration注解的属性AnnotationAttributes attributes = this.getAttributes(annotationMetadata);//核心步骤:获取候选自动配置类List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);//去重处理configurations = this.removeDuplicates(configurations);//获取需要排除的配置类Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);this.checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = this.getConfigurationClassFilter().filter(configurations);this.fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);}}
此方法的核心就是getCandidateConfigurations()方法,获取到等待配置的自动配置类
进入此方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {//从spring.factories加载配置类List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));//从AutoConfiguration.imports加载配置类ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);//校验配置类非空Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");//返回候选配置类列表return configurations;}
该方法的核心功能是从两个约定的配置文件中收集所有候选自动配置类,流程可概括为:
- 从传统的META-INF/spring.factories文件加载配置类
- 从新的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件加载配置类
- 确保至少找到一个配置类(否则抛异常)
例如:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
二、自动配置核心流程解析
1、启动加载阶段
1.1、 Spring Boot 应用启动流程概述
Spring Boot 应用的启动始于SpringApplication.run()方法。当调用该方法时,首先会进行初始化工作,包括设置应用的基本信息、加载上下文等。然后,会创建并刷新 Spring 应用上下文,在这个过程中,会触发自动配置等一系列关键操作,最终完成应用的启动。
1.2、 主配置类上 @SpringBootApplication 注解的加载过程
主配置类是标注了@SpringBootApplication注解的类,在应用启动时,SpringApplication会通过@SpringBootApplication注解找到主配置类。然后,Spring 容器会解析该注解,识别出其包含的子注解,并按照各个子注解的功能分别进行处理,如@ComponentScan开始扫描组件,@EnableAutoConfiguration开启自动配置等。
2、自动配置激活阶段
2.1、 @EnableAutoConfiguration 注解如何激活自动配置机制
@EnableAutoConfiguration注解通过导入AutoConfigurationImportSelector类激活自动配置机制。AutoConfigurationImportSelector类会触发对自动配置类的搜索和加载过程,它会根据项目的依赖和配置,确定需要哪些自动配置类,并将这些类导入到 Spring 容器中,从而启动自动配置。
2.2、 相关配置属性对自动配置的影响
配置属性spring.boot.enableautoconfiguration可以控制自动配置的开启与关闭。当该属性设置为true时(默认值),自动配置机制正常启用;当设置为false时,自动配置机制被关闭,此时需要开发者手动配置所有必要的组件。此外,还有一些其他相关配置属性,如spring.autoconfigure.exclude,可以指定需要排除的自动配置类,进一步灵活控制自动配置的范围。
3、 导入选择器阶段
3.1、AutoConfigurationImportSelector 类的作用与原理
AutoConfigurationImportSelector类的主要作用是选择并导入合适的自动配置类。它通过分析项目的依赖、配置信息以及各种条件,从众多候选的自动配置类中筛选出符合当前应用需求的类,并将它们导入到 Spring 容器中。其原理是基于 Spring 的ImportSelector接口,通过重写selectImports方法来实现对自动配置类的动态选择和导入。
3.2、其如何通过 @Import 注解导入自动配置类
@EnableAutoConfiguration注解中使用@Import(AutoConfigurationImportSelector.class)的方式导入AutoConfigurationImportSelector类。当 Spring 容器解析@Import注解时,会实例化AutoConfigurationImportSelector类,并调用其selectImports方法。该方法返回需要导入的自动配置类的全限定名数组,Spring 容器会根据这些全限定名加载并注册相应的自动配置类。
4、加载候选配置阶段
4.1、getAutoConfigurationEntry () 方法的执行逻辑
getAutoConfigurationEntry()方法是AutoConfigurationImportSelector类中的核心方法,它负责获取自动配置项。其执行逻辑大致如下:首先,检查是否有排除的自动配置类,并进行处理;然后,调用getCandidateConfigurations()方法获取所有候选的自动配置类;接着,对候选配置类进行去重和筛选,去除不符合条件的类;最后,返回处理后的自动配置项集合。
4.2、getCandidateConfigurations () 方法如何加载候选配置
getCandidateConfigurations()方法的主要功能是加载所有可能的候选自动配置类。它通过调用SpringFactoriesLoader.loadFactoryNames()方法,从类路径下的META-INF/spring.factories文件中读取以org.springframework.boot.autoconfigure.EnableAutoConfiguration为键的所有值,这些值就是候选的自动配置类的全限定名。该方法将这些全限定名收集起来,作为候选配置返回。
5、扫描配置文件阶段
5.1、SpringFactoriesLoader.loadFactoryNames () 方法的作用
SpringFactoriesLoader.loadFactoryNames()方法是 Spring 框架提供的一个工具方法,用于从类路径下的META-INF/spring.factories文件中加载指定接口或类对应的实现类的名称。在自动配置中,它主要用于加载EnableAutoConfiguration接口对应的自动配置类的名称,为后续的自动配置类筛选和加载提供基础数据。
5.2、如何扫描所有 jar 包中的 META - INF/spring.factories 文件
SpringFactoriesLoader会遍历类路径下的所有资源,包括各种 jar 包。对于每个 jar 包,它会检查是否存在META-INF/spring.factories文件,如果存在,则读取该文件的内容。通过这种方式,它能够扫描到所有 jar 包中的META-INF/spring.factories文件,从而收集到所有可能的自动配置类信息。
6、过滤和实例化阶段
6.1、条件注解(@Conditional 系列)在过滤配置类中的应用
@Conditional系列注解是 Spring 提供的用于条件化配置的注解,包括@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty等。在自动配置中,这些注解被用于过滤配置类。例如,@ConditionalOnClass表示只有当类路径中存在指定的类时,该配置类才会被加载;@ConditionalOnMissingBean表示只有当 Spring 容器中不存在指定的 Bean 时,该配置类才会生效。通过这些条件注解,Spring Boot 能够根据实际情况筛选出合适的配置类。
6.2、符合条件的配置类如何加入 IoC 容器
经过条件注解过滤后,符合条件的配置类会被 Spring 容器处理。Spring 容器会对这些配置类进行解析,将其中定义的 Bean 注册到 IoC 容器中。同时,配置类之间的依赖关系也会被处理,确保 Bean 的创建顺序和依赖注入的正确性,最终使这些配置类及其定义的 Bean 在 IoC 容器中可用。
三、spring.factories 文件的关键作用
1、文件位置与格式
1.1、spring.factories 文件在项目中的位置
spring.factories文件位于项目的META-INF目录下。在开发自定义的自动配置类时,需要将该文件放置在项目的src/main/resources/META-INF目录下,这样在项目打包时,该文件会被包含在 jar 包的META-INF目录中,以便SpringFactoriesLoader能够扫描到。
1.2、文件采用的 Properties 格式介绍
spring.factories文件采用 Properties 格式,其基本格式是键=值,其中键通常是一个接口或抽象类的全限定名,值是该接口或抽象类的实现类的全限定名,多个实现类之间用逗号分隔。例如:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
2、定义自动配置实现类
2.1、如何在 spring.factories 文件中定义各种类型的自动配置实现类
在spring.factories文件中,要定义自动配置实现类,只需以org.springframework.boot.autoconfigure.EnableAutoConfiguration为键,然后在值的位置列出各个自动配置实现类的全限定名即可。这样,当SpringFactoriesLoader加载该文件时,就能获取到这些自动配置实现类的信息。
2.2、示例展示常见自动配置类在文件中的定义方式
以下是spring.factories文件中常见自动配置类的定义示例:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
四、自定义启动器
既然我们已经知道SpringBoot的自动配置原理,那么我们是不是可以根据这个原理,自己手搓一个启动器呢?
那当然是可以的啦!要实现自定义启动器,需遵循其 "约定优于配置" 的设计思想,核心是利用条件注解、SPI 机制和属性绑定实现自动配置。
首先,创建三个SpringBoot项目,springboot_starter作为真正的启动类,datesource_springboot_starter作为中间过渡,daresource_springboot_starter_autoconfig作为配置类
此三者之间的依赖关系是:启动类依赖于中间过渡,中间过渡依赖于配置类
所以在启动类中导入中间过渡的依赖:
<dependency><groupId>com.my</groupId><artifactId>datesource_springboot_starter</artifactId><version>0.0.1-SNAPSHOT</version></dependency>
在中间过渡中导入配置类的依赖:
<dependency><groupId>com.my</groupId><artifactId>datesource_springboot_starter_autoconfig</artifactId><version>0.0.1-SNAPSHOT</version></dependency>
本次我们就以连接数据库为目的,所以在配置类中导入mysql的依赖,再导入数据源(c3p0连接池)
<!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><!--c3p0--><dependency><groupId>c3p0</groupId><artifactId>c3p0</artifactId><version>0.9.1.2</version></dependency>
在配置类的resource下新建一个META-INF文件夹,文件夹下创建名为spring.factories的文件
在配置类项目下定义一个名为DataSourceProperties的类,作为连接数据库的配置文件
@ConfigurationProperties(prefix = "datasource")
public class DataSourceProperties {private String driver="com.mysql.cj.jdbc.Driver";private String url="jdbc:mysql://localhost:3306/weicheng?serverTimezone=GMT";private String uname="root1";private String pwd="yourpwd";public String getDriver() {return driver;}public void setDriver(String driver) {this.driver = driver;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getUname() {return uname;}public void setUname(String uname) {this.uname = uname;}public String getPwd() {return pwd;}public void setPwd(String pwd) {this.pwd = pwd;}
}
再定义一个名为DataSourceAutoConfiguration的类,连接数据库
@Configuration
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {@Beanpublic DataSource dataSource(DataSourceProperties dataSourceProperties){try {ComboPooledDataSource dataSource = new ComboPooledDataSource();dataSource.setDriverClass(dataSourceProperties.getDriver());dataSource.setJdbcUrl(dataSourceProperties.getUrl());dataSource.setUser(dataSourceProperties.getUname());dataSource.setPassword(dataSourceProperties.getPwd());return dataSource;} catch (PropertyVetoException e) {throw new RuntimeException(e);}}
}
然后我们在spring.factories文件下导入我们需要自动导入的类的完全限定名
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com/my/DataSourceAutoConfiguration
因为我们在连接数据库的配置类中给出了这个注解,它是优先按照配置文件中前缀为datasource的属性作为值的,所以我在上文的配置类中故意给出错误的用户名和密码来测试
@ConfigurationProperties(prefix = "datasource")
来到真正的启动类中,
@SpringBootApplication
public class SpringbootStarterApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootStarterApplication.class, args);DataSource bean = run.getBean(DataSource.class);System.out.println(bean);}
}
将我们的数据源做一个输出,从日志结果来看,已经自动配置到这个类了,但是因为用户名不正确抛出异常
我们在启动类的application.yml文件下给出真正的用户名和密码
datasource:uname: rootpwd: yourpwd
再次启动后,通过日志可以看出已经打印出datasource的Bean对象信息,自动装配成功
五、总结
Spring Boot 自动配置流程是一个环环相扣、逻辑严谨的过程,其关键步骤与核心要点可归纳如下:
从启动加载阶段开始,SpringApplication.run()方法触发应用启动,主配置类上的@SpringBootApplication注解被解析,其中@EnableAutoConfiguration注解是开启自动配置的关键。该注解通过导入AutoConfigurationImportSelector类,激活自动配置机制。
在导入选择器阶段,AutoConfigurationImportSelector类发挥重要作用,它借助@Import注解,通过getAutoConfigurationEntry()方法获取自动配置项。此方法先处理排除的配置类,再调用getCandidateConfigurations()方法,该方法利用SpringFactoriesLoader.loadFactoryNames()方法从所有 jar 包的META-INF/spring.factories文件中加载候选自动配置类。
随后进入扫描配置文件阶段,SpringFactoriesLoader遍历类路径下的资源,收集所有相关的spring.factories文件信息。在过滤和实例化阶段,@Conditional系列条件注解对候选配置类进行筛选,符合条件的配置类被解析,其定义的 Bean 被注册到 IoC 容器中,完成自动配置。
核心要点在于@SpringBootApplication及其包含的子注解的协同作用,spring.factories文件作为自动配置类的信息载体,以及@Conditional系列注解实现的条件化配置,三者共同保障了自动配置的灵活性和准确性。