- 发布日期
Spring Boot原理:配置优先级、Bean管理与自动配置
- 作者
- 姓名
- 缘
- 社交账号
文章目录
Spring Boot 帮我们省掉了什么
使用 Spring Boot 创建 Web 项目时,只要引入一个依赖、写少量配置,就能启动内嵌服务器、处理请求、注入常用组件。它并不是“没有配置”,而是把大量常见配置封装成约定和自动装配逻辑。
理解 Spring Boot 原理,可以围绕三个问题展开:
- 同一个配置出现多份时,最终使用哪一份?
- 一个对象为什么会成为 Bean,又应该何时创建?
- 引入 Starter 后,为什么很多 Bean 不用手写也能直接注入?
配置文件与配置优先级
Spring Boot 常见的配置文件格式有三种:
application.properties
application.yml
application.yaml
它们可以表达同一个配置。例如端口号:
# application.properties
server.port=8081
# application.yml 或 application.yaml
server:
port: 8081
项目开发中建议统一选择一种格式,YAML 因层级清晰而很常见。不要在同一个项目里为同一项配置维护三份来源,否则排查“为什么配置不生效”会很困难。
除了配置文件,启动时还能通过 Java 系统属性和命令行参数覆盖配置:
java -Dserver.port=9000 -jar app.jar
java -jar app.jar --server.port=10010
针对同一个配置项,课程中这几种来源的优先级从低到高为:
application.yaml
↓
application.yml
↓
application.properties
↓
Java 系统属性:-Dkey=value
↓
命令行参数:--key=value
也就是说,命令行参数会覆盖 -D 系统属性,二者又会覆盖文件中的同名配置。这非常适合部署时临时调整端口、环境地址等参数,而无需改动打包后的文件。
要把 Spring Boot 项目打成可运行 Jar,Maven 通常需要 Spring Boot 的打包插件;从官方骨架创建的项目一般已自动包含:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
执行 mvn package 后,再用 java -jar 运行即可。实际项目还可能有 profile、环境变量、外部配置目录等来源;当配置冲突难以判断时,应查看启动日志或 Actuator 环境端点,而不是靠猜测。
Bean 作用域:一个对象会创建几次
Bean 是由 Spring IOC 容器创建和管理的对象。默认情况下,Bean 的作用域是 singleton:容器中同一个 Bean 名称只保留一个实例。
Spring 常见作用域如下:
| 作用域 | 含义 | 使用场景 |
|---|---|---|
singleton | IOC 容器中只有一个实例,默认值 | Service、Mapper、Controller 等无状态组件 |
prototype | 每次获取 Bean 时新建一个实例 | 有短暂内部状态、不能共享的对象 |
request | 每个 HTTP 请求一个实例 | Web 环境中的请求级对象 |
session | 每个会话一个实例 | Web 环境中的会话级对象 |
application | 每个 ServletContext 一个实例 | Web 应用范围共享对象 |
可以通过 @Scope 指定作用域:
@Component
@Scope("prototype")
public class TaskContext {
}
绝大多数 Controller、Service、Mapper 都应该保持单例。单例并不等于“永远线程安全”:如果把一次请求的可变数据放到成员变量中,多线程访问仍会有问题。一般把状态放在方法局部变量、数据库或专门的上下文中。
默认单例 Bean 会在容器启动时创建。若某个 Bean 初始化昂贵、且并非每次启动都要用,可以加 @Lazy 延迟到第一次使用时再创建:
@Lazy
@Service
public class ReportService {
}
延迟初始化会把一部分启动成本推迟到首次请求;是否使用应根据实际启动时间和访问频率判断。
自定义 Bean 与第三方 Bean
项目中自己编写的类,可以使用组件注解交给 Spring 管理:
@Component
@Controller
@Service
@Repository
这些注解标记的类必须位于组件扫描范围内。@SpringBootApplication 默认扫描启动类所在包及其子包,因此启动类通常放在项目根包。
来自第三方依赖的类不能直接加 @Component,这时使用 @Bean 方法创建并注册对象。建议把这类配置集中放进 @Configuration 类:
@Configuration
public class OssConfig {
@Bean
public AliyunOSSOperator aliyunOSSOperator(
AliyunOSSProperties ossProperties) {
return new AliyunOSSOperator(ossProperties);
}
}
@Bean 默认以方法名作为 Bean 名称;方法参数则由容器按类型自动注入。上例中,AliyunOSSProperties 已经是容器中的 Bean 时,Spring 会把它传入 aliyunOSSOperator()。
可以用一句话区分两者:能修改源码的自定义类,通常用 @Component 及其派生注解;来自第三方库、无法加注解的类,通常用 @Bean。
起步依赖:一次引入一组常用能力
传统 Java 项目常需要手动找齐很多相互关联的依赖。Spring Boot 把一类场景所需的常用依赖组织成 Starter(起步依赖),例如:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
引入 spring-boot-starter-web 后,Maven 会通过依赖传递引入 Web 开发所需的一组组件。类似地,还有 AOP、测试、数据访问等不同 Starter。
Starter 解决的是“依赖怎么带进来”的问题,但它还不是全部答案。仅仅让类出现在 classpath 上,并不会自动把所有对象变成 Bean;真正让 Spring Boot 根据当前依赖和配置创建组件的是自动配置。
自动配置:按条件把合适的 Bean 放进容器
自动配置可以理解为:应用启动后,Spring Boot 导入一批候选配置类;每个配置类再根据当前环境、依赖和配置决定是否注册 Bean。
启动类上的 @SpringBootApplication 是理解入口。它主要组合了三部分能力:
@SpringBootConfiguration
@ComponentScan
@EnableAutoConfiguration
public @interface SpringBootApplication {
}
| 注解 | 作用 |
|---|---|
@SpringBootConfiguration | 表明启动类也是配置类,本质上与 @Configuration 相关 |
@ComponentScan | 扫描当前包及其子包中的组件 |
@EnableAutoConfiguration | 导入 Spring Boot 提供的自动配置逻辑 |
第三方组件不能被默认组件扫描发现时,有两种常见做法:
- 使用
@ComponentScan扩大扫描范围。 - 使用
@Import显式导入。
@Import 可以导入普通类、配置类、ImportSelector 的实现类,也经常被 @EnableXxx 这类启用注解封装。它比把第三方大包全部纳入组件扫描更精确,也避免无意义的扫描开销。
自动配置并非“把候选类全部注册”。它依赖 @Conditional 系列条件注解,只在条件满足时装配 Bean:
@Configuration
@ConditionalOnClass(AliyunOSSOperator.class)
public class OssAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public AliyunOSSOperator aliyunOSSOperator() {
return new AliyunOSSOperator();
}
}
常见条件包括:
| 注解 | 判断条件 |
|---|---|
@ConditionalOnClass | classpath 中存在指定类 |
@ConditionalOnMissingBean | 容器中不存在指定类型或名称的 Bean |
@ConditionalOnProperty | 配置中存在指定属性且值匹配 |
这带来两个重要结果:只有需要的功能才会生效;应用也可以通过自己声明 Bean 覆盖默认实现。阅读自动配置时,最值得先看的就是它的条件注解和 @Bean 方法。
自动配置类从哪里被发现
Spring Boot 需要先找到候选自动配置类,再交由条件注解筛选。
课程中提到:Spring Boot 2.7.0 以前,自动配置类主要通过 META-INF/spring.factories 声明。现代 Spring Boot 则使用更明确的文件:
META-INF/spring/
org.springframework.boot.autoconfigure.AutoConfiguration.imports
该文件每行列出一个自动配置类的全限定名。Spring Boot 导入候选配置后,再由 @Conditional 决定哪些 Bean 真正进入 IOC 容器。
因此,“Starter 一引入,所有配置都会生效”是不准确的。更准确的过程是:
引入 Starter
↓
相关类和自动配置声明进入 classpath
↓
Spring Boot 找到候选自动配置类
↓
@Conditional 根据依赖、配置和已有 Bean 筛选
↓
符合条件的 Bean 注册到 IOC 容器
自定义 Starter:把公共组件做成开箱即用的依赖
多个项目都要使用同一个组件时,可以把它封装成自己的 Starter。例如把阿里云 OSS 操作工具、配置绑定和自动装配逻辑封装起来,业务项目只需引入依赖并注入工具类。
常见结构分为两个模块:
aliyun-oss-spring-boot-starter
└─ 负责依赖管理,依赖 autoconfigure 模块
aliyun-oss-spring-boot-autoconfigure
├─ AliyunOssAutoConfiguration
├─ @ConfigurationProperties 配置类
└─ META-INF/spring/...AutoConfiguration.imports
自动配置模块中定义配置属性和 Bean:
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliyunOssProperties {
private String endpoint;
private String bucketName;
// getter、setter 省略
}
@Configuration
@EnableConfigurationProperties(AliyunOssProperties.class)
@ConditionalOnClass(AliyunOSSOperator.class)
public class AliyunOssAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public AliyunOSSOperator aliyunOSSOperator(
AliyunOssProperties properties) {
return new AliyunOSSOperator(properties);
}
}
业务项目配置好 aliyun.oss 前缀的属性后,直接注入 AliyunOSSOperator 即可。这样,依赖版本、默认配置和装配规则都集中在 Starter 中,业务项目不必复制一套 @Configuration。
自定义 Starter 应保持聚焦:一个 Starter 服务于一个清晰能力,并让默认行为合理、配置项可发现、用户自定义 Bean 可以覆盖默认 Bean。
小结:按这条链路排查问题
当 Spring Boot 中出现“端口没有按预期变化”“Bean 注入失败”或“引入依赖后功能没生效”时,可以按下面的顺序排查:
- 配置是否被更高优先级的来源覆盖。
- 类是自定义组件还是第三方组件,应使用组件扫描还是
@Bean。 - Bean 的包是否落在启动类的扫描范围内。
- Starter 是否真的被引入,传递依赖是否存在。
- 自动配置的
@Conditional是否满足,是否已有 Bean 覆盖了默认配置。 - 当前 Spring Boot 版本使用的是哪一种自动配置声明文件。
这条链路把“配置、Bean、依赖、自动配置”连接在一起。理解它之后,Spring Boot 的便利不再像黑盒,而是可以被观察、验证和定制的运行机制。