
Spring 的 @Lazy 注解通过动态代理实现延迟加载,代理对象在首次调用时才从 ApplicationContext 获取真实 Bean,因此即使依赖字段声明为 final,也能安全使用构造器注入。
spring 的 `@lazy` 注解通过动态代理实现延迟加载,代理对象在首次调用时才从 applicationcontext 获取真实 bean,因此即使依赖字段声明为 `final`,也能安全使用构造器注入。
在 Spring 应用中,开发者常误以为 @Lazy 与 final 字段存在矛盾:既然字段不可变,而懒加载又要求“延迟初始化”,那构造器中如何完成注入?答案是——Spring 并不直接注入目标 Bean 实例,而是注入一个轻量级代理(proxy),该代理本身是完整构建的、可被 final 持有的对象,其内部封装了对 ApplicationContext 的引用,并将所有方法调用委托给运行时才解析的真实 Bean。
✅ 核心机制:代理即实参
当使用 @Lazy @Autowired(或构造器参数上的 @Lazy)时,Spring 容器不会立即创建被依赖的 Bean,而是生成一个实现了相同接口(或继承同类)的代理类(如 LazyYourBeanProxy)。这个代理在构造阶段即可实例化并赋值给 final 字段,完全符合 Java 语义和 Spring 构造器注入契约。
以下是一个可运行的典型示例:
interface Service {
String greet();
}
@Component
class RealService implements Service {
public RealService() {
System.out.println("✅ RealService instantiated (only on first use!)");
}
@Override
public String greet() {
return "Hello from lazy-loaded service";
}
}
@Service
class Client {
private final Service service;
// ✅ 合法且推荐:@Lazy + final + 构造器注入
public Client(@Lazy Service service) {
this.service = service; // 注入的是代理,非 RealService 实例
}
public void execute() {
System.out.println("➡️ Calling service.greet()...");
System.out.println(service.greet()); // ⚡ 此刻才触发 RealService 创建与调用
}
}
运行 client.execute() 时输出为:
➡️ Calling service.greet()... ✅ RealService instantiated (only on first use!) Hello from lazy-loaded service
可见:RealService 的构造仅发生在第一次方法调用时,而非容器启动或 Client 初始化时。
⚠️ 注意事项
- 仅接口/抽象类支持 JDK 动态代理:若被 @Lazy 的 Bean 是具体类且无接口,Spring 默认使用 CGLIB 代理(需类非 final,且方法非 final)。此时 @Lazy 仍兼容 final 字段注入,但被代理的目标类本身不能是 final 类。
- 代理透明性:调用方无需感知代理存在,所有 final 字段持有者均可像操作普通 Bean 一样调用方法。
- 循环依赖场景下慎用:@Lazy 可缓解部分循环依赖,但不应作为设计缺陷的补救手段;优先考虑重构依赖关系。
- @Lazy 在 @Configuration 类中行为不同:用于 @Bean 方法时,表示该 Bean 自身延迟初始化;用于注入点时,表示依赖项延迟解析——本文讨论的是后者。
✅ 总结
@Lazy 与 final 不仅不矛盾,反而是现代 Spring 应用中推荐的组合:它保障了不可变性(immutable dependency)、线程安全性(无后续修改风险),同时借助代理机制完美实现按需加载。只要理解代理本质,就能放心在构造器注入中大胆使用 @Lazy final —— 这正是 Spring IoC 成熟设计的体现。










