Skip to content

Spring 中的 @Lazy 注解的作用是什么? ​

  • 标签:后端、Spring

🏛 业务架构师 ​

@Lazy 的作用是把 Bean 的初始化从启动期推迟到第一次被使用时:标注后容器启动不创建它,首次 getBean/注入调用时才真正实例化。注入点用它时,实际注入的是一个代理,首次方法调用触发真实初始化。

业务架构师视角,我按收益与风险来定它的使用边界:

合理用途:

  1. 启动加速:低频重资源 Bean(冷门的报表引擎、大模型客户端)延迟创建,缩短启动时间——直接改善 K8s 扩容拉起速度和故障恢复时长;
  2. 解循环依赖的应急手段:配合 IOC 也能打破启动期的环,但我的定位是"止血",根治靠重构依赖关系,团队里用它必须在代码注释里说明业务理由和重构计划;
  3. 可选重依赖:用 ObjectProvider 的 lazy 语义实现"用到才有、没有能降级"。

风险提醒:

  • 延迟初始化 = 延迟暴露问题:配置错误、依赖缺失不在启动时报错,而是运行到某个业务路径才炸——这违反我"启动期 fail-fast"的原则;
  • 首次调用的耗时尖峰会打到用户请求上(可感知的慢请求)。

所以我的规范是:全局 spring.main.lazy-initialization 默认不开;个别 Bean 定点 lazy + 启动后预热任务兜底。延迟是拿确定性换速度,要逐点决策,不能一刀切。

🏗 系统架构师 ​

作用两个层面:

  • Bean 级:标在 @Component/@Bean 上,启动不初始化,首次 getBean 才创建;
  • 注入点级:标在依赖参数上,注入的是懒代理,真正调用方法时才触发目标初始化——这也是它能绕开循环依赖的原理:把构造期环延后到运行期首次调用。

收益与风险的权衡(我的使用判断):

收益:冷启动优化(重资源 Bean 延迟初始化省常驻内存);只有个别路径用到的重依赖不拖累启动。

风险:

  1. 把启动期失败推迟到运行期——本该启动即爆的装配错误变成线上第一个请求的报错;懒加载单例还让首次请求承担初始化延迟,p99 抖动来源;
  2. 用它解循环依赖是止痛不治病,依赖环的架构问题还在;
  3. 大面积全局懒加载必须配套「启动后主动预热」,否则等于把冷启动成本转嫁给用户流量。

结论:点状用(个别重 Bean),不当全局开关用。

💻 全栈开发专家 ​

@Lazy 两个效果:Bean 懒初始化 + 注入点延迟解析。

用法一:声明处——首次使用才初始化,而不是启动时:

java
@Lazy
@Component
public class HeavyReportGenerator { }    // 启动不建,首次调用才建

用法二:注入点标 @Lazy——容器注入一个代理,真正调用方法才触发目标 Bean 初始化。这能解构造器循环依赖(官方认可的姿势):

java
@Component
public class A {
    private final B b;
    public A(@Lazy B b) { this.b = b; }  // b 是代理,环被打破
}

生产用途三个:大对象/低频组件懒加载缩短启动时间;拆循环依赖(比 allow-circular-references=true 干净,后者是掩盖问题);依赖顺序不确定时避开启动期初始化竞争。

坑:@Lazy 注入的是 CGLIB 代理,目标 Bean 的 @PostConstruct 语义被推迟;与 AOP 代理叠加时出现两层代理,类型转换异常排查要看两层。我的实践:只给基础设施组件(报告生成器、冷门渠道客户端)用,业务 Bean 保持急加载——问题早暴露比晚暴露便宜。