Spring 高频坑有哪些?循环依赖、事务代理与懒加载如何规避?
结论先行:Spring 开发里绝大多数“怪问题”源于同一个底层事实——注解增强走代理,容器管理生命周期。三大高频坑:① 启动循环依赖报错(Boot 2.6+ 默认禁止);② @Transactional/@Async/@Cacheable 因自调用或非 public 方法失效;③ 懒加载把“启动错误”推迟成“运行期事故”。规避的核心是:构造器注入 + 分层清晰 + 依赖外部调用。
一、启动循环依赖报错
现象:BeanCurrentlyInCreationException / “依赖形成闭环”;Boot 2.6 起默认禁止循环引用(报错提示改为 allow-circular-references 属性)。
| 方式 | 说明 |
|---|---|
| 构造器注入循环 | 无法解决,必须重构 |
| setter/字段注入(单例) | 老版本靠三级缓存可解,Boot 2.6+ 默认仍拒绝 |
| @Lazy 打破 | 延迟注入代理,绕开构建期循环 |
| 正确姿势 | 用构造器注入 + 拆分依赖方向,从根上消除 |
# 只建议临时过渡,不要作为常态
spring:
main:
allow-circular-references: true
二、事务与代理失效
典型场景:Service 内 this.doSomething() 调同类的 @Transactional 方法;方法不是 public;异常被 catch 吞掉;调用方跨线程。
@Service
public class OrderService {
@Transactional
public void a() { insert(); } // 代理生效
public void b() {
a(); // this.a():直调原始对象 → 事务失效!
}
}
规避手段:
- 增强方法放到另一个 Bean,跨 Bean 注入调用;
- 自注入(@Lazy 注入自身代理)或 AopContext.currentProxy();
- 保证 public、异常向外抛(或 rollbackFor 指定受检异常);
- 事务边界只在 Service 层,别在 Controller/异步线程里开事务;
- Boot 3.x 默认 CGLIB 代理:final 类/方法无法代理,注意设计。
三、懒加载的坑
spring:
main:
lazy-initialization: true # 全量懒加载:启动快但错误后置
- @Lazy 局部用:Bean 首次被使用时才创建,可解决部分循环依赖,也用于“大而少用”的资源;
- 坑:配置错误、依赖缺失在启动时发现不了,首笔流量才报错 → 线上事故;
- 结论:默认不全局懒加载;个别 @Lazy 需确认“延迟创建”语义符合预期。
常见追问 / 记忆点
- 追问:为什么 Boot 2.6 开始默认禁止循环依赖?答:鼓励用构造器注入与清晰分层,把设计问题在启动期暴露而非带病运行。
- 追问:@Transactional 失效的第一排查顺序?答:看是否自调用、是否非 public、异常是否被吞、是否走代理 Bean。
- 记忆点:代理增强靠外部调用、异常要外抛、循环依赖靠重构;懒加载是“把炸弹延后引爆”。