SpringBoot中@Lazy注解Bean为何与普通Autowired对象引用/哈希码不同?
为什么@Lazy注入的Bean与直接获取的Bean引用不同?
核心原因:@Lazy字段注入生成了CGLIB代理对象
当你在@Autowired字段上添加@Lazy注解时,Spring不会直接注入目标Bean的真实实例,而是创建一个CGLIB动态代理对象注入到字段中。这个代理对象会在第一次调用方法时,才从Spring容器中获取真正的单例Bean实例,之后所有方法调用都会转发给这个真实实例。
逐个解答你的疑问
1. 为什么引用不同但计数器结果是2/2?
代理对象的所有方法调用都会被转发到容器中的真实单例Bean实例。不管你调用代理对象的inc(),还是直接从ApplicationContext获取的真实Bean的inc(),最终操作的都是同一个AtomicInteger计数器,所以两次调用后计数器值为2,证明底层Bean确实是单例。
2. 为什么toString相同但hashCode不同、==检查为false?
- toString相同:CGLIB代理重写了
toString()方法,会返回真实Bean实例的toString()结果,所以你看到的字符串和真实Bean一致。 - hashCode不同:代理对象本身是一个独立的Java对象,它的
hashCode()返回的是代理实例自己的哈希值,而非真实Bean的哈希值。 - ==检查为false:
==比较的是对象引用,代理对象和真实Bean是两个不同的实例,所以返回false;而默认的equals()方法也是基于引用比较,所以同样返回false(除非你的CounterService重写了equals方法)。
3. Spring内部的实现机制
当Spring处理带有@Lazy的@Autowired字段时:
- 创建懒加载目标源(
LazyInitTargetSource),负责在需要时从容器中获取真实Bean实例。 - 基于目标类生成CGLIB代理类(因为
CounterService是类而非接口),代理类继承自CounterService,并持有上述懒加载目标源。 - 将代理对象注入到字段中。当第一次调用代理对象的任意方法时,懒加载目标源会触发真实Bean的初始化(如果还没初始化),并将方法调用转发给真实Bean。
- 后续所有方法调用都会直接转发给已经初始化的真实单例Bean,保证业务逻辑的一致性。
验证补充
如果要确认代理的存在,可以在测试中添加以下代码:
import org.springframework.aop.support.AopUtils; // ... System.out.println("counterService is proxy: " + AopUtils.isAopProxy(counterService)); System.out.println("counterService is CGLIB proxy: " + AopUtils.isCglibProxy(counterService));
输出会显示counterService确实是CGLIB代理对象。
内容的提问来源于stack exchange,提问作者Luv
相关产品推荐
相关产品推荐

