@Lazy原理分析——它为什么可以解决特殊的循环依赖问题?

前言

前面分析循环依赖问题时,我们遇到了一些 Spring 无法解决的循环依赖问题,最终都通过 @Lazy 解决了。
那 @Lazy 是什么原理呢?它为什么可以解决这种特殊的循环依赖问题?
下面我们就来探索一下!

版本约定

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 默认是没有解决的。
主要的场景如下:

  1. prototype 类型的循环依赖
  2. constructor 注入的循环依赖
  3. @Async 类型的 Bean 的循环依赖

这些特殊的场景,我们都可以通过 @Lazy 来解决。

@Lazy 的本质就是将注入的依赖变成了一个代理对象。使用 @Lazy 时,不会触发依赖 bean 的加载(这里说的是注释在类上,不是属性上,下面讲的延迟加载和延迟注入的区别)。

@Lazy 注入的代理 bean 在什么时候才会加载真正的 bean,走 getBean 的流程?

答:通过调试代码,我们会发现 A 中注入的 @Lazy B 是一个代理对象。
在 A 中,通过 B 的代理对象去调用 B 的方法时,才会去触发 B 的加载。

Lazy

直接通过 ApplicationContext.getBean(B.class) 获取到的是一个未被代理的对象。
注意:被 @Lazy 标记的依赖属性比较特殊,实际注入的对象与 Spring 容器中存放的对象不是同一个对象!!!

 注意延迟加载和延迟注入的区别,AB循环依赖,@Lazy加在A类上,属于延迟加载,这个时候是不会为A生成代理对象的,延迟加载等于调整了AB的实例化顺序,让B先实例化了。

延迟加载

@Component
public class DemoLazy2 {

    @Autowired
    DemoLazy1 demoLazy1;

}


@Component
@Lazy
public class DemoLazy1 {
    @Autowired
    DemoLazy2 demoLazy2;

}

如图,无代理对象产生。原理在调整了bean的实例化顺序,DemoLazy1是先实例化的,但加了@Lazy注解,实例化被跳过,DemoLazy2实例化时触发了DemoLazy1的实例化,源码AbstractApplicationContext#finishBeanFactoryInitialization>
DefaultListableBeanFactory#preInstantiateSingletons(这里是轮询beanDefinitionNames实例化单例非懒加载bean,注入属性时,不会走这段逻辑,也就是说@Lazy加类上实例化时在这里被跳过,加属性上不走这个逻辑会生成代理对象)

延迟注入

@Component
public class DemoLazy2 {

    @Autowired
    @Lazy
    DemoLazy1 demoLazy1;

}


@Component
public class DemoLazy1 {
    @Autowired
    DemoLazy2 demoLazy2;

}

如图,延迟依赖的bean是代理bean了。再看看上文说的,实际注入的对象与 Spring 容器中存放的对象不是同一个对象!!!,如图。

无论延迟加载还是延迟依赖都会实例化循环依赖的两个bean,只有两者一起使用,才是真正的懒加载,即使用到的时候再实例化。

延迟加载+延迟依赖

@Component
public class DemoLazy2 {

    @Autowired
    @Lazy
    DemoLazy1 demoLazy1;

}


@Component
@Lazy
public class DemoLazy1 {
    @Autowired
    DemoLazy2 demoLazy2;

}

如图,单例池并没有DemoLazy1,正常情况它和DemoLazy2几乎是挨在一起的。

————————————————
版权声明:本文为CSDN博主「老王学源码」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wang489687009/article/details/120577472

相关文章:

Spring源码之@Lazy和预实例化

【Spring源码三千问】@Lazy延迟加载与延迟注入有什么区别?

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值