Spring Boot 原理
1. 配置优先级
在 Spring Boot 中,常见的属性配置来源有 5 种(从低到高):
application.yaml(很少使用,可忽略)application.ymlapplication.properties- Java 系统属性(
-Dxxx=xxx) - 命令行参数(
--xxx=xxx)
当同一个配置键在多种位置都出现时,优先级更高的来源会覆盖优先级更低的配置。
例如同时存在:
application.yml中配置server.port: 8080- 命令行参数
--server.port=10010
最终生效的端口是 10010(命令行参数优先级最高)。
虽然 Spring Boot 支持多种格式的配置文件,但在实际项目开发中,推荐统一使用一种配置文件格式,以便团队协作和维护;目前主流做法是统一使用
application.yml。
2. 外部化配置:Java 系统属性与命令行参数
除了项目内部的 3 种配置文件外,Spring Boot 为了增强扩展性,还支持两种常见的外部配置方式:
- Java 系统属性:
-Dkey=value(如-Dserver.port=9000) - 命令行参数:
--key=value(如--server.port=10010)
即使项目已经打成 JAR 包上线,我们仍然可以在启动命令中通过这两种方式覆盖配置文件中的值:
# 同时设置 Java 系统属性 和 命令行参数
java -Dserver.port=9000 -jar app.jar --server.port=10010在这种情况下:
application.yml/application.properties中的server.port会被 Java 系统属性 覆盖;- Java 系统属性中的
server.port=9000又会被 命令行参数--server.port=10010覆盖,最终端口为 10010。
注意:Spring Boot 项目打包成可执行 JAR 时,需要在
pom.xml中引入(或保留)spring-boot-maven-plugin插件;使用官网骨架创建项目时会自动添加该插件。
3. Bean 的作用域
在前面讲 IOC 容器时提到:默认情况下,Spring 中的 Bean 是单例的(同名 Bean 在容器中只有一个实例)。
实际上,Spring 支持 五种作用域,其中后三种只在 Web 环境 中生效:
| 作用域 | 说明 |
|---|---|
singleton | 容器内同名称的 Bean 只有一个实例(单例),默认作用域。 |
prototype | 每次获取该 Bean 时都会创建一个新的实例(非单例)。 |
request | 每个 HTTP 请求范围内创建新的实例(Web 环境中,了解即可)。 |
session | 每个会话范围内创建新的实例(Web 环境中,了解即可)。 |
application | 每个应用范围(ServletContext)内创建新的实例(Web 环境中,了解)。 |
3.1 使用 @Scope 指定作用域
如果我们希望修改某个 Bean 的作用域,可以借助 Spring 提供的 @Scope 注解 进行配置,例如将某个 Controller 配置为原型(prototype):

等价代码示例:
@Scope("prototype") // 每次注入或获取时都创建新的 DeptController 实例
@RequestMapping("/depts")
@RestController
public class DeptController {
}实际项目中,大多数 Bean(尤其是 Service / Repository)仍建议使用默认的
singleton;只有在确有需求时,才调整为prototype或 Web 相关作用域。
3.2 @Lazy:延迟初始化单例 Bean
默认情况下,singleton 作用域的 Bean 会在容器启动时就被创建(饿汉式加载)。
如果某些 Bean 比较“重”(初始化成本高)或并不是每次启动都一定会用到,可以使用 @Lazy 注解 将其改为延迟初始化 —— 第一次真正被使用时才创建实例。
@Lazy // 延迟到第一次注入 / 调用时再创建该 Bean
@Service
public class HeavyService {
// ...
}小结:
@Scope("singleton")+ 无@Lazy→ 容器启动时立即创建;
加上@Lazy→ 延迟到首次使用时再创建,有助于缩短启动时间、按需加载资源。
4. 面试常问:Bean 单例与线程安全
4.1 Spring 容器中的 Bean 是单例还是多例?单例 Bean 什么时候实例化?
- 默认是单例(
singleton):同名 Bean 在容器中只有一个实例。 - 默认情况下,单例 Bean 会在容器启动时就完成实例化(可以通过
@Lazy改为延迟初始化)。
4.2 Spring 容器中的 Bean 是线程安全的吗?
Bean 的线程安全与是否有状态、以及作用域有关:
- 若是 无状态的 Bean(内部不保存任何与请求相关的可变状态,例如只依赖方法参数,不持有可变成员字段),即使是单例,一般也是线程安全的。
- 若是 有状态的 Bean(内部保存可变状态信息,如把用户数据、计数器等放在成员变量里),在多线程同时访问 / 修改时,可能出现数据不一致等问题,这样的单例 Bean 就是线程不安全的。
实战建议:
- Service / Repository 等 Bean 通常设计为无状态单例,每次请求的数据通过方法参数传入。
- 如确实需要保存会话级 / 请求级状态,应考虑使用请求参数、
ThreadLocal或 Web 作用域 Bean,而不是把状态直接放在单例 Bean 的成员变量中。
5. 第三方 Bean 的配置(@Bean)
前面我们声明的 Bean(Controller / Service / DAO 等)都是自己项目中编写的类,可以直接在类上使用 @Component 及其衍生注解(@Controller、@Service、@Repository)让 Spring 扫描并注册。
但在实际开发中,还有一类 “第三方 Bean”:
- 这些类来自引入的第三方依赖(JAR 包),不是我们自己写的;
- 无法在源码上添加
@Component、@Service等注解。
这时就需要使用 @Bean 注解来声明 Bean。
小结:自定义类优先用
@Component/@Service等;第三方类无法改源码时,用@Bean声明到容器中。
5.1 演示 1:在启动类中直接声明 @Bean
最简单的方式是在 启动类 中直接声明第三方 Bean,例如将阿里云 OSS 操作工具类注册为 Bean:
import com.itheima.utils.AliyunOSSOperator;
import com.itheima.utils.AliyunOSSProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
@ServletComponentScan
@EnableScheduling
@SpringBootApplication
public class TliasWebManagementApplication {
public static void main(String[] args) {
SpringApplication.run(TliasWebManagementApplication.class, args);
}
@Bean
public AliyunOSSOperator aliyunOSSOperator(AliyunOSSProperties ossProperties) {
// 如果第三方 Bean 需要依赖其他 Bean,可直接通过方法形参注入(按类型自动装配)
return new AliyunOSSOperator(ossProperties);
}
}使用方式示例(单元测试中注入使用):
@SpringBootTest
class TliasWebManagementApplicationTests {
@Autowired
private AliyunOSSOperator aliyunOSSOperator;
@Test
void testUploadFiles() throws Exception {
byte[] content = FileUtil.readBytes(new File("C:\\\\Users\\\\deng\\\\Pictures\\\\6.jpg"));
String url = aliyunOSSOperator.upload(content, "6.jpg");
System.out.println(url);
}
@Test
void testListFiles() throws Exception {
List<String> objectNameList = aliyunOSSOperator.listFiles();
objectNameList.forEach(System.out::println);
}
@Test
void testDelFiles() throws Exception {
aliyunOSSOperator.deleteFile("2024/06/43b4....84.jpg");
}
}5.2 演示 2:使用配置类集中管理第三方 Bean(推荐)
如果项目中有多种第三方 Bean,建议通过 @Configuration 配置类进行集中管理,结构更清晰、职责更单一:
package com.itheima.config;
import com.itheima.utils.AliyunOSSOperator;
import com.itheima.utils.AliyunOSSProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OSSConfig {
@Bean
public AliyunOSSOperator aliyunOSSOperator(AliyunOSSProperties ossProperties) {
return new AliyunOSSOperator(ossProperties);
}
}5.3 @Bean 的一些细节
- Bean 名称:
- 默认情况下,
@Bean方法名就是 Bean 的名称,例如上例中的aliyunOSSOperator。 - 也可以通过
@Bean(name = "ossOperator")或@Bean("ossOperator")显式指定 Bean 名称。
- 默认情况下,
- 依赖注入:
- 第三方 Bean 若依赖其他 Bean,只需在
@Bean方法的形参中声明相应类型,Spring 会按类型自动注入(类似构造器注入)。
- 第三方 Bean 若依赖其他 Bean,只需在
- 适用场景:
- 只要是无法直接加
@Component的类(第三方库、工厂方法返回的对象等),都可以通过@Bean注解把它们交给 Spring 容器管理。
- 只要是无法直接加