Spring Boot 在 Spring 的基础上提供自动配置、依赖管理与应用启动约定。它减少常见服务的装配工作,但不会替你决定业务模块、数据库事务和权限边界。本页配套工程固定使用 Java 21、Spring Boot 3.5.16;这是一组可复现依赖,不表示必须把既有项目全部升级到它。
一次请求经过什么
Servlet 容器接收 HTTP,请求先经过 Filter,Spring Security 也在这一层参与;DispatcherServlet 查找 Controller,转换并校验请求,调用业务服务,最后把结果序列化。ControllerAdvice 能处理 MVC 范围内的异常,但不应假设它自动接管所有安全 Filter 错误。
Controller 负责协议转换,Service 负责用例和事务,Repository/JDBC 负责数据访问。边界的价值是让错误、测试和依赖可理解,不需要为每个类都机械增加一个没有其他实现的接口。
@RestController
class GreetingController {
private final GreetingService service;
GreetingController(GreetingService service) { this.service = service; }
@GetMapping("/greeting")
Map<String, String> greeting(@RequestParam String name) {
return Map.of("message", service.greet(name));
}
}
这是展示构造注入的片段;可直接构建的完整项目见预约工程。构造注入让必需依赖在创建时显式提供,也方便不启动整个 Spring 的单元测试。不要在 Controller 里手动 new 一个本应受容器代理的业务服务。
自动配置不是任意配置都生效
Starter 汇集相关依赖;自动配置依据 classpath、属性和已存在 Bean 等条件生效。用户自定义 Bean 可能使默认配置退让,也可能与现有配置冲突。遇到“框架没按预期工作”时,应查看条件报告、有效属性和实际 Bean,而不是继续叠加注解。
配置分开保存:代码中的合理默认值、非敏感环境配置、外部注入的秘密。多份 profile 与命令行环境变量存在优先级,诊断时必须确认最终值;不要只看 application.properties 里的那一行。
@Transactional 的边界是代理调用
常见代理模式下,从容器外部经代理调用事务方法才能应用拦截。同一实例内 this.reserve() 自调用不会经过代理;private 方法也不适合作为这种入口。默认情况下 RuntimeException 和 Error 触发回滚,checked exception 需根据业务明确配置。
把事务放在一个完整业务用例上,例如创建预约、扣库存、写 outbox 一起提交。不要让 HTTP 请求整个生命周期都持有事务,更不能边持锁边等邮件服务。数据库连接、事务管理器和调用所在线程也有关系,切到另一个线程不会自动继承原事务。
单例 Bean 中的字段会被多个请求共享,应存放无状态依赖或不可变配置;请求参数使用局部值。通过 Bean scope 改成 prototype,不是解决共享数据库竞争的通用办法。
从启动到测试
先运行完整工程的 mvn test,再 mvn package 生成 jar。测试中区分纯服务测试、Web 切片和完整上下文;只验证 Controller 的 Mock 不能证明真实 SQL 与事务正确。启用健康端点时只暴露必要信息,避免将配置与敏感诊断端点公开。
练习与面试自查
在预约工程中把事务方法移到本类 private 方法,设计一个扣减失败后的状态检查,解释为什么自调用可能绕过事务。再恢复边界,让事务回归测试保持通过。
- Bean 是单例是否等于线程安全?事务代理与 Java synchronized 有何不同?
- Spring Boot 自动配置与普通 Java 配置有什么关系?怎样确认某配置为什么没有生效?
- 为什么捕获业务异常后不再抛出,可能让事务提交意外状态?