@Lazy原理分析——它为什么可以解决特殊的循环依赖问题
前言
前面分析循环依赖问题时,我们遇到了一些 Spring 无法解决的循环依赖问题,最终都通过 @Lazy
解决了。
那 @Lazy
是什么原理呢?它为什么可以解决这种特殊的循环依赖问题?
下面我们就来探索一下!
传送门:
【Spring源码三千问】哪些循环依赖问题Spring解决不了?
【Spring源码三千问】Spring 是怎样解决循环依赖问题的?
版本约定
Spring 5.3.9 (通过 SpringBoot 2.5.3 间接引入的依赖)
正文
前面我们在分析 @Resource 与 @Autowired 的区别 时,@Resource 注入时是通过 ResourceElement.java
来处理的。
且注入时会处理 @Lazy
的逻辑,相应的代码如下:
// CommonAnnotationBeanPostProcessor.ResourceElement#getResourceToInject()
protected Object getResourceToInject(Object target, String requestingBeanName) {
return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :
getResource(this, requestingBeanName));
}
protected Object buildLazyResourceProxy(final LookupElement element, final @Nullable String requestingBeanName) {
TargetSource ts = new TargetSource() {
@Override
public Class<?> getTargetClass() {
return element.lookupType;
}
@Override
public boolean isStatic() {
return false;
}
@Override
public Object getTarget() {
return getResource(element, requestingBeanName);
}
@Override
public void releaseTarget(Object target) {
}
};
ProxyFactory pf = new ProxyFactory();
pf.setTargetSource(ts);
if (element.lookupType.isInterface()) {
pf.addInterface(element.lookupType);
}
ClassLoader classLoader = (this.beanFactory instanceof ConfigurableBeanFactory ?
((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader() : null);
return pf.getProxy(classLoader);
}
可以看出,被 @Lazy 标记的属性,在 populateBean 注入依赖时,会直接注入一个 proxy 对象。并且,不会触发注入对象的加载。
这样的话,就不会产生 bean 的循环加载问题了。
@Autowired 对 @Lazy 的处理也是类似的,可以查看
AutowiredFieldElement.java
中对 @Lazy 的处理。大同小异的。
具体代码是在:ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary() 中
举个栗子:
比如: A --> B --> A,且 都是通过构造函数依赖的
这种场景下,Spring 会报错:Requested bean is currently in creation: Is there an unresolvable circular reference?
但是,我们加上 @Lazy 之后,就能正常启动了!
原因分析:
导致报错的原因已经在前面的文章:哪些循环依赖问题Spring解决不了? 中分析过了,这里就不再赘述。
而为什么使用了 @Lazy 之后,程序就能正常启动了呢?
假设代码如下:
@Service
public class A {
private B b;
public A(@Lazy B b) {
this.b = b;
}
}
答: 假设 A 先加载,在创建 A 的实例时,会触发依赖属性 B 的加载,在加载 B 时发现它是一个被 @Lazy 标记过的属性。
那么,就不会去直接加载 B,而是产生了一个代理对象注入到了 A 中,这样 A 就能正常的初始化完成放入一级缓存了。
自然,B 加载时,再去注入 A 就能直接从一级缓存中获取到 A,这样 B 也能正常初始化完成了。
所以,循环依赖的问题就解决了!
小结
Spring 默认为我们解决了常规场景下的循环依赖问题。但是有些特殊的场景下的循环依赖,Spring 默认是没有解决的。
主要的场景如下:
- prototype 类型的循环依赖
- constructor 注入的循环依赖
- @Async 类型的 Bean 的循环依赖
这些特殊的场景,我们都可以通过 @Lazy 来解决。
@Lazy 的本质就是将注入的依赖变成了一个代理对象。使用 @Lazy 时,不会触发依赖 bean 的加载。
@Lazy 注入的代理 bean 在什么时候才会加载真正的 bean,走 getBean 的流程?
答:通过调试代码,我们会发现 A 中注入的 @Lazy B 是一个代理对象。
在 A 中,通过 B 的代理对象去调用 B 的方法时,才会去触发 B 的加载。
直接通过 ApplicationContext.getBean(B.class) 获取到的是一个未被代理的对象。
注意:被 @Lazy 标记的依赖属性比较特殊,实际注入的对象与 Spring 容器中存放的对象不是同一个对象!!!
SpringIoC源码视频讲解:
课程 | 地址 |
---|---|
SpringIoC源码解读由浅入深 | https://edu.51cto.com/sd/68e86 |
如果本文对你有所帮助,欢迎点赞收藏!
源码测试工程下载:
老王读Spring IoC源码分析&测试代码下载
老王读Spring AOP源码分析&测试代码下载
公众号后台回复:下载IoC 或者 下载AOP 可以免费下载源码测试工程…
阅读更多文章,请关注公众号: 老王学源码
系列博文:
【老王读Spring IoC-0】Spring IoC 引入
【老王读Spring IoC-1】IoC 之控制反转引入
【老王读Spring IoC-2】IoC 之 BeanDefinition 扫描注册
【老王读Spring IoC-3】Spring bean 的创建过程
【老王读Spring IoC-4】IoC 之依赖注入原理
【老王读Spring IoC-5】Spring IoC 小结——控制反转、依赖注入
相关阅读:
【Spring源码三千问】@Resource 与 @Autowired 的区别
【Spring源码三千问】bean name 的生成规则
【Spring源码三千问】BeanDefinition详细分析
【Spring源码三千问】Spring 是怎样解决循环依赖问题的?
【Spring源码三千问】哪些循环依赖问题Spring解决不了?
【Spring源码三千问】@Lazy为什么可以解决特殊的循环依赖问题?
【Spring源码三千问】BeanDefinition注册、Bean注册、Dependency注册有什么区别?
【Spring源码三千问】Bean的Scope有哪些?scope=request是什么原理?
【Spring源码三千问】为什么要用三级缓存来解决循环依赖问题?二级缓存行不行?一级缓存行不行?