Spring@Lazy是如何解决构造函数循环依赖问题

Spring实例化源码解析之循环依赖CircularReference这章的最后我们提了一个构造函数形成的循环依赖问题,本章就是讲解利用@Lazy注解如何解决构造函数循环依赖和其原理。

准备工作

首先创建两个构造函数循环依赖的类,TestA和TestB,代码如下:

@Service
public class TestA {public TestA(@Lazy TestB testB) {this.testB = testB;}TestB testB;public void testA(){testB.testB();}
}@Service
public class TestB {public TestB(TestA testA) {this.testA = testA;}TestA testA;public void testB(){System.out.println("有人掉我了 ^^");}
}

这样并不会有循环依赖的异常,所以说@Lazy确实可以解决构造函数循环依赖的问题。

@使用@Lazy注解

@Lazy注解since 3.0,

/** Copyright 2002-2021 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package org.springframework.context.annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** Indicates whether a bean is to be lazily initialized.** <p>May be used on any class directly or indirectly annotated with {@link* org.springframework.stereotype.Component @Component} or on methods annotated with* {@link Bean @Bean}.** <p>If this annotation is not present on a {@code @Component} or {@code @Bean} definition,* eager initialization will occur. If present and set to {@code true}, the {@code @Bean} or* {@code @Component} will not be initialized until referenced by another bean or explicitly* retrieved from the enclosing {@link org.springframework.beans.factory.BeanFactory* BeanFactory}. If present and set to {@code false}, the bean will be instantiated on* startup by bean factories that perform eager initialization of singletons.** <p>If Lazy is present on a {@link Configuration @Configuration} class, this* indicates that all {@code @Bean} methods within that {@code @Configuration}* should be lazily initialized. If {@code @Lazy} is present and false on a {@code @Bean}* method within a {@code @Lazy}-annotated {@code @Configuration} class, this indicates* overriding the 'default lazy' behavior and that the bean should be eagerly initialized.** <p>In addition to its role for component initialization, this annotation may also be placed* on injection points marked with {@link org.springframework.beans.factory.annotation.Autowired}* or {@link javax.inject.Inject}: In that context, it leads to the creation of a* lazy-resolution proxy for all affected dependencies, as an alternative to using* {@link org.springframework.beans.factory.ObjectFactory} or {@link javax.inject.Provider}.* Please note that such a lazy-resolution proxy will always be injected; if the target* dependency does not exist, you will only be able to find out through an exception on* invocation. As a consequence, such an injection point results in unintuitive behavior* for optional dependencies. For a programmatic equivalent, allowing for lazy references* with more sophistication, consider {@link org.springframework.beans.factory.ObjectProvider}.** @author Chris Beams* @author Juergen Hoeller* @since 3.0* @see Primary* @see Bean* @see Configuration* @see org.springframework.stereotype.Component*/
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {/*** Whether lazy initialization should occur.*/boolean value() default true;}

这个注释描述了@Lazy注解在Spring框架中的用法和行为。我将其翻译如下:

表示一个bean是否应该被懒初始化。可以用于任何直接或间接被 @Component 注解标记的类,或者用于被 @Bean 注解标记的方法上。

如果在 @Component 或 @Bean 定义上没有使用这个注解,bean 将会被急切初始化。如果使用了该注解并设置为 true,那么被 @Bean 或 @Component 标记的类将不会在被另一个bean引用或者在封装它的BeanFactory中显式获取之前进行初始化。如果使用了该注解并设置为 false,则bean将会在启动时由急切初始化单例的bean工厂中被实例化。

如果 @Lazy 注解放在一个 @Configuration 类上,表示该配置中的所有 @Bean 方法都应该被懒初始化。如果在一个 @Lazy 注解的 @Configuration 类中的 @Bean 方法上使用了 @Lazy 并设置为 false,这表示覆盖了“默认懒初始化”行为,该bean将会被急切初始化。

除了在组件初始化中的作用外,该注解还可以放在标记为 org.springframework.beans.factory.annotation.Autowired 或 javax.inject.Inject 的注入点上:在这个上下文中,它会为所有受影响的依赖关系创建一个懒解析的代理,作为使用 org.springframework.beans.factory.ObjectFactory 或 javax.inject.Provider 的替代方案。请注意,这种懒解析的代理会被始终注入;如果目标依赖不存在,你只能在调用时通过异常找出。因此,这样的注入点对于可选依赖关系来说会导致行为不直观。对于具备更多复杂性的懒引用的编程等价方式,请考虑使用 org.springframework.beans.factory.ObjectProvider。

@Lazy原理

我在TestA的构造函数中使用了@Lazy懒加载TestB,根据经验,TestA必须会去走Bean的创建流程,以往的普通循环依赖问题都是在populateBean期间(属性注入)做的。但是有个问题,如果我使用了@Lazy说明是在调用构造函数的时候懒加载TestB,那么我TestA中注入了个什么对象?在调用的时候怎么找到正确的TestB,因为TestB并没有懒加载。

分析流程

根据普通Bean循环依赖的经验,我判断会在AbstractAutowireCapableBeanFactory#doCreateBean方法中的属性注入完成后,TestA中就会注入TestB对象。所以我在populateBean打了个条件断点。

在这里插入图片描述

发现不对劲,因为在populateBean之前,TestA中就有了TestB的代理对象。搞错了,再来。

在这里插入图片描述

然后在AbstractAutowireCapableBeanFactory#doCreateBean方法中的第一行开始打断点,发现在createBeanInstance方法执行完之后就创建了TestB的代理对象。

在这里插入图片描述

所以基本定位到在调用构造函数创建TestA的时候就把TestB的代理对象创建好了。

源码跟踪

源码查看足迹可以参考下面的类:

AbstractApplicationContext#refresh

AbstractApplicationContext#finishBeanFactoryInitialization

DefaultListableBeanFactory#preInstantiateSingletons

AbstractBeanFactory#getBean

AbstractBeanFactory#doGetBean

DefaultSingletonBeanRegistry#getSingleton

AbstractBeanFactory#lambda

AbstractAutowireCapableBeanFactory#createBean

AbstractAutowireCapableBeanFactory#doCreateBean

AbstractAutowireCapableBeanFactory#createBeanInstance

AbstractAutowireCapableBeanFactory#autowireConstructor

ConstructorResolver#autowireConstructor

ConstructorResolver#createArgumentArray

ConstructorResolver#resolveAutowiredArgument

DefaultListableBeanFactory#resolveDependency

ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary

ContextAnnotationAutowireCandidateResolver#buildLazyResolutionProxy:83

这里有个小技巧,如果你使用的时IDEA,快捷键double Shift copy上述一行的栈信息可以直接跳转到次方法中

buildLazyResolutionProxy

ContextAnnotationAutowireCandidateResolver#buildLazyResolutionProxy方法是用于创建懒解析(lazy resolution)的代理对象。

protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {BeanFactory beanFactory = getBeanFactory();Assert.state(beanFactory instanceof DefaultListableBeanFactory,"BeanFactory needs to be a DefaultListableBeanFactory");final DefaultListableBeanFactory dlbf = (DefaultListableBeanFactory) beanFactory;TargetSource ts = new TargetSource() {@Overridepublic Class<?> getTargetClass() {return descriptor.getDependencyType();}@Overridepublic boolean isStatic() {return false;}@Overridepublic Object getTarget() {Set<String> autowiredBeanNames = (beanName != null ? new LinkedHashSet<>(1) : null);Object target = dlbf.doResolveDependency(descriptor, beanName, autowiredBeanNames, null);if (target == null) {Class<?> type = getTargetClass();if (Map.class == type) {return Collections.emptyMap();}else if (List.class == type) {return Collections.emptyList();}else if (Set.class == type || Collection.class == type) {return Collections.emptySet();}throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(),"Optional dependency not present for lazy injection point");}if (autowiredBeanNames != null) {for (String autowiredBeanName : autowiredBeanNames) {if (dlbf.containsBean(autowiredBeanName)) {dlbf.registerDependentBean(autowiredBeanName, beanName);}}}return target;}@Overridepublic void releaseTarget(Object target) {}};ProxyFactory pf = new ProxyFactory();pf.setTargetSource(ts);Class<?> dependencyType = descriptor.getDependencyType();if (dependencyType.isInterface()) {pf.addInterface(dependencyType);}return pf.getProxy(dlbf.getBeanClassLoader());}

很明显这里其实就是通过定义TargeSource来创建一个代理对象,然后注入到TestA中,在TestA需要使用注入的TestB时,会执行这个getTarget方法来获取,如果容器里面有TestB就从容器中获取,如果没有就走创建流程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/123839.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

qt-C++笔记之在两个标签页中按行读取两个不同的文件并且滚动条自适应滚动范围高度

qt-C笔记之在两个标签页中按行读取两个不同的文件并且滚动条自适应滚动范围高度 code review! 文章目录 qt-C笔记之在两个标签页中按行读取两个不同的文件并且滚动条自适应滚动范围高度1.运行2.文件结构3.main.cc4.main.pro5.a.txt6.b.txt7.上述代码中QVBoxLayout&#xff0c…

世界前沿技术发展报告2023《世界航空技术发展报告》(四)无人机技术

&#xff08;四&#xff09;无人机技术 1.无人作战飞机1.1 美国空军披露可与下一代战斗机编组作战的协同式无人作战飞机项目1.2 俄罗斯无人作战飞机取得重要进展 2.支援保障无人机2.1 欧洲无人机项目通过首个里程碑2.2 美国海军继续开展MQ-25无人加油机测试工作 3.微小型无人机…

TextureView和SurfaceView

1、Surface Surface对应了一块屏幕的缓冲区&#xff0c;每一个window对应一个Surface&#xff0c;任何View都是画在Surface上的&#xff0c;传统的View共享一块屏幕缓冲区&#xff0c;所有的绘制都必须在UI线程上进行。 2、SurfaceView 顾名思义就是Surface的View&#xff0c;…

NSS刷题 js前端修改 os.path.join漏洞

打算刷一遍nssweb题&#xff08;任重道远&#xff09; 前面很简单 都是签到题 这里主要记录一下没想到的题目 [GDOUCTF 2023]hate eat snake 这里 是对js的处理 有弹窗 说明可能存在 alert 我们去看看js 这里进行了判断 如果 getScore>-0x1e9* 我们结合上面 我觉得是6…

数据结构 -- ArrayList与LinkedList的区别

一、二者的相同点 1&#xff0c;它们都是继承自List接口。 二、二者的区别 1&#xff0c;数据结构&#xff1a;ArrayList是&#xff08;Array动态数组&#xff09;的数据结构&#xff1b;而LinkedList是&#xff08;Link双向链表&#xff09;的数据结构。ArrayList 自由性较…

RabbitMQ基础

目录 RabbitMQ的可靠性投递 确保消息正确地发送至 RabbitMQ 确保消息接收方消费了消息 流程分析 1.生产者发送消息给Broker 2.交换机路由消息到队列 3.消息存储在队列 4.消费者订阅并消费消息 三个重要概念 RabbitMQ集群模式 RabbitMQ的可靠性投递 在 RabbitMQ 中&a…

BUUCTF qr 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 这是一个二维码&#xff0c;谁用谁知道&#xff01; 密文&#xff1a; 下载附件&#xff0c;得到一张二维码图片。 解题思路&#xff1a; 1、这是一道签到题&#xff0c;扫描二维码得到flag。 flag&#xff1a;…

一文了解Elasticsearch

数据分类 数据按数据结构分类主要有三种&#xff1a;结构化数据、半结构化数据和非结构化数据。 结构化数据 结构化数据具有明确定义数据模型和格式的数据类型。 特点&#xff1a; 数据具有固定的结构和模式。 数据项明确定义数据类型和长度。 适合用于数据查询、过滤和分…

377. 组合总和 Ⅳ 70.魔改爬楼梯

377. 组合总和 Ⅳ 题目&#xff1a; 给一个正整数数组和一个正整数目标值&#xff0c;数组的每个元素可取无限次&#xff0c;求总额达到目标值的最大排列数。 dp[j]含义&#xff1a; dp[j]&#xff1a;达到目标值j的整数组合数为dp[j] 递推公式&#xff1a; 求装满背包有几…

在CARLA中手动开车,添加双目相机stereo camera,激光雷达Lidar

CARLA的使用逻辑&#xff1a; 首先创建客户端 设置如果2秒没有从服务器返回任何内容&#xff0c;则终止 client carla.Client("127.0.0.1", 2000) client.set_timeout(2.0) 从客户端中get world world client.get_world() 设置setting并应用 这里使用固定时…

【C++的OpenCV】第十四课-OpenCV基础强化(三):Mat元素的访问之data和step属性

&#x1f389;&#x1f389;&#x1f389; 欢迎来到小白 p i a o 的学习空间&#xff01; \color{red}{欢迎来到小白piao的学习空间&#xff01;} 欢迎来到小白piao的学习空间&#xff01;&#x1f389;&#x1f389;&#x1f389; &#x1f496; C\Python所有的入门技术皆在 我…

【年终特惠】基于最新导则下生态环评报告编制技术暨报告篇、制图篇、指数篇、综合应用篇系统性实践技能提升

根据生态环评内容庞杂、综合性强的特点&#xff0c;依据生态环评最新导则&#xff0c;将内容分为4大篇章(报告篇、制图篇、指数篇、综合篇)、10大专题(生态环评报告编制、土地利用图的制作、植被类型及植被覆盖度图的制作、物种适宜生境分布图的制作、生物多样性测定、生物量及…

前端Vue页面中如何展示本地图片

<el-table :data"tableData" stripe style"width: 100%"><el-table-column prop"imgUrl" label"图片"><template v-slot"scope"><img :src "http://localhost:8888/image/ scope.row.imgUrl&qu…

R-FCN: Object Detection via Region-based Fully Convolutional Networks(2016.6)

文章目录 AbstractIntroduction当前最先进目标检测存在的问题针对上述问题&#xff0c;我们提出... Our approachOverviewBackbone architecturePosition-sensitive score maps & Position-sensitive RoI pooling Related WorkExperimentsConclusion 原文链接 源代码 Abstr…

飞天使-mysql8.0远程连接允许

mysql -u root -p 查看身份验证类型 mysql> use mysql; Database changed mysql> SELECT Host, User, plugin from user; ------------------------------------------------- | Host | User | plugin | ------------------------------------------------- | % | root …

Sass、Less和Stylus之间有什么主要的区别?

Sass、Less和Stylus是三种常见的CSS预处理器&#xff0c;它们在功能和语法上有一些区别。以下是它们之间的主要区别&#xff1a; 1&#xff1a;语法差异&#xff1a; Sass使用缩进的语法&#xff0c;使用类似于Python的缩进来表示嵌套规则和块级作用域。Less和Stylus使用类似…

大数据之LibrA数据库系统告警处理(ALM-12002 HA资源异常)

告警解释 HA软件周期性检测Manager的WebService浮动IP地址和数据库。当HA软件检测到浮动IP地址或数据库异常时&#xff0c;产生该告警。 当HA检测到浮动IP地址或数据库正常后&#xff0c;告警恢复。 告警属性 告警参数 对系统的影响 如果Manager的WebService浮动IP地址异常…

SSM咖啡点餐管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 SSM 咖啡点餐管理系统是一套完善的信息系统&#xff0c;结合SSM框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主 要采用B/S模式开…

selenium : TypeError: object of type ‘float‘ has no len()

使用selenium时报错 TypeError: object of type float has no len() 。 显然selenium不允许直接输入浮点类型数据&#xff08;字符串与整形可以&#xff09;。 但是问题excel中这个数据值为空&#xff0c;只能猜测不同电脑打开excel时格式不同影响读取数据。 将该数据使用st…

vue3 Suspense组件

在 Vue 3 中&#xff0c;<Suspense> 组件用于处理异步组件加载时的等待状态和错误处理。它允许你在加载异步组件时显示一个自定义的加载指示器&#xff0c;以及在加载失败时显示错误信息。以下是一个详细的 <Suspense> 组件的使用示例&#xff1a; 首先&#xff0…