问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

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

创作时间:
作者:
@小白创作中心

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

引用
CSDN
1.
https://blog.csdn.net/meser88/article/details/130845880

在Spring框架中,循环依赖是一个常见的问题,尤其是在使用构造函数注入时。虽然Spring默认解决了一些常规场景下的循环依赖问题,但某些特殊场景(如prototype类型的循环依赖、构造函数注入的循环依赖等)仍然需要额外的处理。本文将深入分析@Lazy注解的原理,解释它如何解决这些特殊的循环依赖问题。

版本约定

Spring 5.3.9 (通过 SpringBoot 2.5.3 间接引入的依赖)

@Lazy注解的工作原理

在分析@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()

示例说明

假设存在以下循环依赖场景:

@Service
public class A {
    private B b;
    public A(@Lazy B b) {
        this.b = b;
    }
}
@Service
public class B {
    private A a;
    public B(@Lazy A a) {
        this.a = a;
    }
}

在这种场景下,Spring会报错:Requested bean is currently in creation: Is there an unresolvable circular reference?

但是,我们加上@Lazy之后,就能正常启动了!原因在于:

  • 假设A先加载,在创建A的实例时,会触发依赖属性B的加载,在加载B时发现它是一个被@Lazy标记过的属性。
  • 那么,就不会去直接加载B,而是产生了一个代理对象注入到了A中,这样A就能正常的初始化完成放入一级缓存了。
  • 自然,B加载时,再去注入A就能直接从一级缓存中获取到A,这样B也能正常初始化完成了。
  • 所以,循环依赖的问题就解决了!

@Lazy的两种使用方式

延迟加载

@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几乎是挨在一起的。

注意:被@Lazy标记的依赖属性比较特殊,实际注入的对象与Spring容器中存放的对象不是同一个对象!!!

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号