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.rhkb.cn/news/176082.html

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

相关文章

通用开源自动化测试框架 - Robot Framework

一、什么是 Robot Framework&#xff1f; 1. Robot Framework 的历史由来 Robot Framework是一种通用的自动化测试框架&#xff0c;最早由Pekka Klrck在2005年开发&#xff0c;并由Nokia Networks作为内部工具使用。后来&#xff0c;该项目以开源形式发布&#xff0c;并得到了…

Py之pypdf:pypdf的简介、安装、使用方法之详细攻略

Py之pypdf&#xff1a;pypdf的简介、安装、使用方法之详细攻略 目录 pypdf的简介 pypdf的安装 pypdf的使用方法 1、基础用法 pypdf的简介 pypdf是一个免费的、开源的纯python PDF库&#xff0c;能够拆分、合并、裁剪和转换PDF文件的页面。它还可以为PDF文件添加自定义数据…

喜讯!合合信息顺利通过CMMI3级评估

近日&#xff0c;在擎标顾问团的咨询辅导下&#xff0c;上海合合信息科技股份有限公司&#xff08;简称“合合信息”&#xff09;顺利通过了CMMI3级评估。CMMI是国际上最流行、最实用的一种软件生产过程标准和软件企业成熟度等级认证的标准&#xff0c;通过该认证表明企业在开发…

CS224W1.1——图机器学习介绍

文章目录 1. 介绍2. 主要问题3. 深度学习如何应用在图结构中4. 课程大纲 学习一下斯坦福CS224W的图机器学习&#xff08;2021年&#xff09;&#xff0c;并做一下学习笔记&#xff0c;主要是研究方向与图神经网络相关。这次是第一次笔记&#xff0c;图片很多都是从斯坦福的PPT里…

Flask基本教程以及Jinjia2模板引擎简介

flask基本使用 直接看代码吧&#xff0c;非常容易上手&#xff1a; # 创建flask应用 app Flask(__name__)# 路由 app.route("/index", methods[GET]) def index():return "FLASK&#xff1a;欢迎访问主页&#xff01;"if __name__ "__main__"…

Flask 路由机制分析之一

一、前言 《Flask Run运行机制剖析》这篇我们讲了应用启动的内部机制&#xff0c;启动后就开始监听Http请求了&#xff0c;请求过来如何跳到对应的函数执行&#xff0c;这就是路由机制。我们沿用上一篇例子&#xff0c;来探究一下app.route("/")内部干了些什么事。 …

VDA到Excel方案介绍之自定义邮件接收主题

VDA标准是德国汽车工业协会&#xff08;Verband der Automobilindustrie&#xff0c;简称VDA&#xff09;制定的一系列汽车行业标准。这些标准包括了汽车生产、质量管理、供应链管理、环境保护、安全性能等方面的规范和指南。VDA标准通常被德国和国际上的汽车制造商采用&#x…

【算法-数组1】二分查找 和 移除元素

今天&#xff0c;带来XXX的讲解。文中不足错漏之处望请斧正&#xff01; 理论基础 二分查找 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#…

yum 命令

基本语法 yum [选项] [参数] 选项说明 -y 对所有提问都回答“yes” 参数说明 实操 yum list | grep firefox yum -y remove firefox yum -y install firefox

使用MobaXterm向linux窗口化传输文件

使用MobaXterm向linux窗口化传输文件 之前上大学的时候&#xff0c;经常是XSheel配合Xftp使用&#xff0c;Xftp可以窗口化的往linux服务器传输文件&#xff0c;但是有一个问题&#xff0c;就是Xftp是收费的。 后来工作之后师兄给推荐了一个免费的&#xff0c;又好用的类似于Xf…

uni-app/vue 文字转语音朗读(附小程序语音识别和朗读)uniapp小程序使用文字转语音播报类似支付宝收款播报小程序语音识别和朗读)

uni-app/vue 文字转语音朗读&#xff08;小程序语音识别和朗读&#xff09; uniapp小程序功能集合 1、uniapp小程序文字转语音播报 一、第一种方式&#xff1a;直接加语音包 固定的文本 先利用工具生成了 文本语音mp3文件&#xff0c;放入项目中&#xff0c;直接用就好了 …

【CSS】position

CSS position 1.静态布局 static static 是 position 属性的默认值&#xff0c;表示没有定位。使用静态定位的元素会按照元素正常的位置显示&#xff0c;并且不会受到 top、bottom、left、right 和 z-index 属性的影响。 2.相对定位 relative 相对定位就是元素相对于自己默…

Spring Cloud 之RabbitMQ的学习【详细】

服务通信 分布式系统通信两种方式&#xff1a; 直接远程调用&#xff08;同步&#xff09;借助第三方间接通信&#xff08;异步&#xff09; 同步通讯的问题 Feign就属于同步通讯。存在的如下问题 耦合度高&#xff0c;每次添加新的模块就要修改原有模块的代码性能下降&am…

css(层叠样式表)

文章目录 一、CSS介绍二、CSS使用方式1. 行内样式/内联样式&#xff08;单一页面中使用&#xff09;设置背景颜色 background-color:green; 2. 内嵌样式&#xff08;少量页面中使用&#xff09;3. 外链样式表&#xff08;项目中使用&#xff09; 三、 样式表特征1. 层叠性2. 继…

学习redis之前的泛泛而谈(特性介绍,应用场景,Ubuntu安装与通用命令介绍)

文章目录 前言关于分布式系统Redis特性Redis应用场景Redis5安装redis命令最核心的两个命令&#xff1a;get和setkeysexitsdelexpirettlredis中key的过期策略type redis数据类型的内部实现方式redis的单线程 前言 redis最重要的概念&#xff1a;在内存中存储数据 为什么要设计一…

代购商城源码是否可以定制开发?

定制开发&#xff0c;符合个性需求 代购商城源码是现代电子商务中的重要工具&#xff0c;它为代购商提供了建立在线店铺、管理产品和订单、处理支付和物流等功能。然而&#xff0c;对于不同的代购商而言&#xff0c;在源码的基础上进行个性化定制开发无疑是提升竞争力和用户体验…

2023年软件测试工具总结 —— 单元测试工具

在应用程序中&#xff0c;单元是具有一个或多个输入和单个输出的软件中最小可测试部分。单元测试是一种测试软件代码单元的方法&#xff0c;通常包括一个或两个输入&#xff0c;产生一个输出。单元测试主要关注独立模块的功能正确性&#xff0c;目的是确保每个单元都按照预期的…

毛发渲染方案实现

一、毛发材质概述 以前毛发只能用离线来做 现在实时毛发逐渐可能。长毛渲染和短毛渲染采用的是不同的方案。 二、长毛类制作分析 各向异性 kajiya算法 # 三、短毛类制作分析 四、制作心得及技巧

成为一个优秀的测试工程师需要具备哪些知识和经验?

看到这个题目&#xff0c;头脑中马上就拆分出了3个小问题&#xff1a; 1、什么是优秀的测试工程师&#xff1f; 2、优秀测试工程师需要哪些知识&#xff1f; 3、优秀测试工程师需要哪些经验&#xff1f; 一个个讲解。 一、什么才是一名优秀的测试工程师呢&#xff1f; 什么才是…

奇富科技引领大数据调度革命:高效、稳定、实时诊断

日前&#xff0c;在世界最大的开源基金会 Apache旗下最为活跃的项目之一DolphinScheduler组织的分享活动上&#xff0c;奇富科技的数据平台专家刘坤元应邀为国内外技术工作者献上一场题为《Apache DolphinScheduler在奇富科技的优化实践》的精彩分享&#xff0c;为大数据任务调…