1、循环依赖详解(一)

news2024/11/26 23:30:20

什么是循环依赖?
什么情况下循环依赖可以被处理?

Spring是如何解决的循环依赖?

只有在setter方式注入的情况下,循环依赖才能解决(错)
三级缓存的目的是为了提高效率(错)

什么是循环依赖?

从字面上来理解就是A依赖B的同时B也依赖了A
在这里插入图片描述

体现到代码层次就是这个样子

@Component
public class A {
   // A中注入了B
   @Autowired
   private B b;
}

@Component
public class B {
   // B中也注入了A
   @Autowired
   private A a;
}

当然,这是最常见的一种循环依赖,比较特殊的还有

// 自己依赖自己
@Component
public class A {
   // A中注入了A
   @Autowired
   private A a;
}

虽然体现形式不一样,但是实际上都是同一个问题----->循环依赖

对象引用循环依赖在某些业务场景上可能是合理存在的,但是由于Spring容器设计了依赖注入机制,即Spring容器在创建bean实例化以后就要给bean中的属性自动赋值,要全部自动赋值之后,才能交给用户使用。如果出现循环依赖的情况,以两个bean互相依赖的情况作为举例,假设有AService已经实例化(但未完成初始化),但是AService中需要自动赋值的BService并没有初始化,如果Spring立刻初始化BService,发现BService中需要自动赋值AService也没有初始化完成,这样就会出现相互等待,形成死循环,可能导致Spring容器都无法启动了。
由此可见,对Bean的填充属性是循环依赖源头的开始。

为了容易理解Spring解决循环依赖过程,先简单看下Spring容器创建Bena的主要流程。

在这里插入图片描述

spring将内部管理的诸多对象称为一个个bean,而这些bean的创建流程大致分为两个大阶段:

spring 容器预热阶段
bean实际创建阶段

    下面本人将从上述两个阶段来阐述bean的完整创建过程。

容器预热阶段

  1. 思路
    在对象进行创建之前,spring容器需要了解所创建的对象的信息,才能在后续阶段根据了解的信息创建bean对象。这些信息即是实际工作中我们为对象所写的配置信息,它们一般以xml文件、properties文件和注解的形式存在于我们的项目之中。

     一个以xml文件为存储形式的bean配置文件如下:
    
<bean id="person" class="com.yuansu.pojo.Person" name="personAlias">
	<property name="name" value="yuansu"/>
    <property name="dog" ref="alan"/>
    <property name="cat" ref="katie"/>
</bean>
   因此,在容器预热阶段,spring将会读取配置文件,并将bean的必要信息存储在自己容器内部,用于后续对象创建。
  1. BeanDefination:bean对象配置信息的存储形式
    那么,spring内部是用什么样的形式来存储bean的配置信息的呢?我们知道,在java中,万物皆对象,spring选择用java对象的形式存储上述的bean配置信息,而这个对象名字就是BeanDefination。

     BeanDefination中存储了包括属性、构造方法参数、依赖的 Bean 名称及是否单例、延迟加载等信息:
    
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
 
	// 单例、原型标识符
	String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
	String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
 
    // 标识 Bean 的类别,分别对应 用户定义的 Bean、来源于配置文件的 Bean、Spring 内部的 Bean
	int ROLE_APPLICATION = 0;
	int ROLE_SUPPORT = 1;
	int ROLE_INFRASTRUCTURE = 2;
 
    // 设置、返回 Bean 的父类名称
	void setParentName(@Nullable String parentName);
	String getParentName();
 
    // 设置、返回 Bean 的 className
	void setBeanClassName(@Nullable String beanClassName);
	String getBeanClassName();
 
    // 设置、返回 Bean 的作用域
	void setScope(@Nullable String scope);
	String getScope();
 
    // 设置、返回 Bean 是否懒加载
	void setLazyInit(boolean lazyInit);
	boolean isLazyInit();
	
 
 
    ...
}
    此处我们仅需了解BeanDefination是bean对象配置信息的java类封装,更深知识暂不做讨论。
  1. BeanDefinationReader:将xml等文件配置信息读取为BeanDefination
    xml等形式的配置信息转化为BeanDefination自然不是眨个眼就能直接完成转化的,需要通过某种工具对其进行读取并且转化。这里的工具即是我们此处提出的BeanDefinationReader。

     BeanDefinationReader是一个接口,其不同的实现类对不同形式的配置信息进行读取并封装为BeanDefination。下图是BeanDefinationReader的结构图:
    

在这里插入图片描述
如上图所示,XmlBeanDefinationReader可以读取xml文件类型的配置信息并存储为BeanDefination。其他Reader以此类推。

  1. BeanFactoryPostRegistry:BeanDefination的存储仓库
    当BeanDefinationReader将配置文件读取并存储到BeanDefination中后,Spring需要通过bean的id寻找到对应的BeanDefination从而获取其配置信息。这种通过Bean定义的id找到对象的BeanDefination的对应关系或者说映射关系又是如何保存的呢?这就引出了BeanDefinationRegistry了。

    Spring通过BeanDefinationReader将配置元信息加载到内存生成相应的BeanDefination之后,就将其注册到BeanDefinationRegistry中,BeanDefinationRegistry就是一个存放BeanDefination的仓库,它也是一种键值对的形式,通过特定的Bean定义的id,映射到相应的BeanDefination。
    
  2. BeanFactoryPostProcessor:对BeanDefination进行扩展处理
    BeanFactoryPostProcessor是容器启动阶段Spring提供的一个扩展点,主要负责对注册到BeanDefinationRegistry中的一个个的BeanDefination进行一定程度上的修改与替换。

     例如我们的配置元信息中有些可能会修改的配置信息散落到各处,不够灵活,修改相应配置的时候比较麻烦,这时我们可以使用占位符的方式来配置。例如配置Jdbc的DataSource连接的时候可以这样配置:
    
<bean id="dataSource"  
    class="org.apache.commons.dbcp.BasicDataSource"  
    destroy-method="close">  
    <property name="maxIdle" value="${jdbc.maxIdle}"></property>  
    <property name="maxActive" value="${jdbc.maxActive}"></property>  
    <property name="maxWait" value="${jdbc.maxWait}"></property>  
    <property name="minIdle" value="${jdbc.minIdle}"></property>  
  
    <property name="driverClassName"  
        value="${jdbc.driverClassName}">  
    </property>  
    <property name="url" value="${jdbc.url}"></property>  
  
    <property name="username" value="${jdbc.username}"></property>  
    <property name="password" value="${jdbc.password}"></property>  
</bean>

BeanFactoryPostProcessor就会对注册到BeanDefinationRegistry中的BeanDefination做最后的修改,替换$占位符为配置文件中的真实的数据。
5. 总结
上述5点即是容器预热阶段的几个部分,下面我们使用图片的形式回顾上述流程:
在这里插入图片描述
Bean创建阶段

  1. Bean生命周期:
    实例化 -> 属性赋值 -> 初始化 -> 使用 -> 销毁

  2. 实例化:
    目的:Spring将转化BeanDefinition中BeanDefinition为实例Bean(放在包装类BeanWrapper中)。

(1) 实例化时机
容器启动阶段与Bean实例化阶段存在多少时间差,Spring把这个决定权交给了程序员自己进行决定。Bean创建时间有两种策略:懒加载和非懒加载。

    懒加载(isLazyInit):直到我们伸手向Spring要依赖对象实例之前,Bean都是以BeanDefinationRegistry中的一个个的BeanDefination的形式存在,也就是Spring只有在我们需要依赖对象的时候才开启相应对象的实例化阶段。

    非懒加载:容器启动阶段完成之后,将立即启动Bean实例化阶段,通过隐式的调用所有依赖对象的getBean方法来实例化所有配置的Bean并保存起来。

(2)实例化的三种方式
Spring中Bean的实例化本质其实就是JVM中java实例对象的加载-连接-初始化过程。

1,使用类构造器实例化(无参构造函数)

直接通过Spring工厂返回类的实例对象。

2,使用静态工厂方法实例化(简单工厂模式)

Spring工厂调用自定义工厂的静态方法返回类的实例对象。

3,使用实例工厂方法实例化(工厂方法模式)

Spring工厂调用工厂的普通方法(非静态方法)返回类的实例对象。

(3)BeanWrapper:Bean对象的封装外壳
Spring中的Bean并不是以一个个的本来模样存在的,由于Spring IOC容器中要管理多种类型的对象,因此为了统一对不同类型对象的访问,Spring给所有创建的Bean实例穿上了一层外套,这个外套就是BeanWrapper。

BeanWrapper实际上是对反射相关API的简单封装,使得上层使用反射完成相关的业务逻辑大大的简化,我们要获取某个对象的属性,调用某个对象的方法,现在不需要在写繁杂的反射API了以及处理一堆麻烦的异常,直接通过BeanWrapper就可以完成相关操作,非常方便。

  1. 属性赋值
    目的:上一步创建出来的对象还是个空白对象,需要为其设置属性以及依赖对象。

     对于基本类型的属性:如果配置元信息中有配置,那么将直接使用配置元信息中的设置值赋值即可,即使基本类型的属性没有设置值,那么得益于JVM对象实例化过程,属性依然可以被赋予默认的初始化零值。
    
     对于引用类型的属性:Spring会将所有已经创建好的对象放入一个Map结构中,此时Spring会检查所依赖的对象是否已经被纳入容器的管理范围之内,也就是Map中是否已经有对应对象的实例了。如果有,那么直接注入,如果没有,那么Spring会暂时放下该对象的实例化过程,转而先去实例化依赖对象,再回过头来完成该对象的实例化过程。
    
    1. 初始化
      目的:在交付bean之前做一些处理

(1)检测对象是否实现aware接口
A 、概念:
aware接口为Bean对象提供了解Spring容器本身的能力,aware系列接口增强了Spring bean的功能,但是也会造成对Spring框架的绑定,增大了与Spring框架的耦合度。(Aware是“意识到的,察觉到的”的意思,实现了Aware系列接口表明:可以意识到、可以察觉到)
aware接口有以下顺序:
在这里插入图片描述

B、aware接口特点:

都以“Aware”结尾
都是Aware接口的子接口,即都继承了Aware接口
接口内均定义了一个set方法

C、使用方式:一个Bean对象想要获得spring容器某个部分的引用作为自己成员变量进行使用,就需要实现上述某个接口,并声明相关的成员变量来接收。

/**
 * 实现了
 * 	ApplicationContextAware
 *  BeanClassLoaderAware
 *  BeanFactoryAware
 *  BeanNameAware
 *  接口
 * @author dengp
 *
 */
public class User implements ApplicationContextAware,BeanClassLoaderAware,BeanFactoryAware,BeanNameAware{
 
	private int id;
	
	private String name;
	// 保存感知的信息
	private String beanName;
	// 保存感知的信息
	private BeanFactory beanFactory;
	// 保存感知的信息
	private ApplicationContext ac;
	// 保存感知的信息
	private ClassLoader classLoader;
	
	public BeanFactory getBeanFactory() {
		return beanFactory;
	}
 
	public ApplicationContext getAc() {
		return ac;
	}
 
	public ClassLoader getClassLoader() {
		return classLoader;
	}
 
	public User(){
		System.out.println("User 被实例化");
	}
 
	public int getId() {
		return id;
	}
 
	public void setId(int id) {
		this.id = id;
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	public String getBeanName() {
		return beanName;
	}
	
	/**
	 * 自定义的初始化方法
	 */
	public void start(){
		System.out.println("User 中自定义的初始化方法");
	}
	
	@Override
	public String toString() {
		return "User [id=" + id + ", name=" + name + ", beanName=" + beanName + "]";
	}
 
	@Override
	public void setBeanClassLoader(ClassLoader classLoader) {
		System.out.println(">>> setBeanClassLoader");
		this.classLoader = classLoader;
	}
 
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		System.out.println(">>> setApplicationContext");
		this.ac = applicationContext;
	}
 
	@Override
	public void setBeanName(String name) {
		System.out.println(">>> setBeanName");
		this.beanName = name;
	}
 
	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		System.out.println(">>> setBeanFactory");
		this.beanFactory = beanFactory;
	}
}

D、 注意

    Spring IOC容器大体可以分为两种:

    BeanFactory:提供IOC思想所设想所有的功能,同时也融入AOP等相关功能模块,可以说BeanFactory是Spring提供的一个基本的IOC容器。

    ApplicationContext:构建于BeanFactory之上,同时提供了诸如容器内的时间发布、统一的资源加载策略、国际化的支持等功能,是Spring提供的更为高级的IOC容器。

    对于BeanFactory来说,这一步的实现是先检查相关的Aware接口,然后去Spring的对象池(也就是容器,也就是那个Map结构)中去查找相关的实例(例如对于ApplicationContextAware接口,就去找ApplicationContext实例),也就是说我们必须要在配置文件中或者使用注解的方式,将相关实例注册容器中,BeanFactory才可以为我们自动注入。

    对于ApplicationContext来说,由于其本身继承了一系列的相关接口,所以当检测到Aware相关接口,需要相关依赖对象的时候,ApplicationContext完全可以将自身注入到其中。
  1. BeanPostProcessor前置处理

目的:BeanPostProcessor前置处理就是在要生产的Bean实例放到容器之前,允许我们程序员对Bean实例进行一定程度的修改,替换等操作。

前面讲到的ApplicationContext对于Aware接口的检查与自动注入就是通过BeanPostProcessor实现的,在这一步Spring将检查Bean中是否实现了相关的Aware接口,如果是的话,那么就将其自身注入Bean中即可。

Spring中AOP就是在这一步实现的偷梁换柱,产生对于原生对象的代理对象,然后将对源对象上的方法调用,转而使用代理对象的相同方法调用实现的。

  1. 自定义初始化逻辑
    初始化有两种方式,实现InitializingBean接口或者配置init-method参数。

注意:在两者同时存在时,实现InitializingBean接口的afterpropertiesSet()方法执行顺序在配置init-method参数的方法前。

  1. 实现InitializingBean接口:InitializingBean是Spring提供的拓展性接口,InitializingBean接口为bean提供了属性赋值后初始化的处理方法,它只有一个afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。

  2. 配置init-method参数:通过init-method参数指定某个方法为初始化方法,然后此方法就会在bean初始化的时候执行。

xml形式:

<bean id="myBean" class="com.yuansu.MyBean" init-method="myInit"></bean>

注解形式:


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
 
@Configuration
public class BeanConfig {
 
    @Bean(initMethod = "myInit", destroyMethod = "myDestroy")
    public MyBean myBean(){
        MyBean mybean = new MyBean();
        return myBean;
    }
 
}
 
  1. BeanPostProcessor后置处理
    与前置处理类似,这里是在Bean自定义逻辑也执行完成之后,Spring又留给我们的最后一个扩展点。我们可以在这里在做一些我们想要的扩展。

  2. 自定义销毁逻辑
    销毁有两种方式,实现DisposableBean接口或者配置init-method参数。

  3. 实现DisposableBean接口:类似初始化实现InitializingBean接口。

  4. 配置destroy-method参数:类似初始化配置init-method参数。

  5. 使用
    这个时候可以对bean对象进行正常使用。

  6. 销毁
    Spring的Bean在为我们服务完之后,马上就要消亡了(通常是在容器关闭的时候),别忘了我们的自定义销毁逻辑,这时候Spring将以回调的方式调用我们自定义的销毁逻辑,然后Bean就这样走完了光荣的一生!

在这里插入图片描述

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

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

相关文章

在基于亚马逊云科技的湖仓一体架构上构建数据血缘的探索和实践

背景介绍 随着大数据技术的进步&#xff0c;企业和组织越来越依赖数据驱动的决策。数据的质量、来源及其流动性因此显得非常关键。数据血缘分析为我们提供了一种追踪数据从起点到终点的方法&#xff0c;有助于理解数据如何被转换和消费&#xff0c;同时对数据治理和合规性起到关…

Ajax学习笔记第8天

放弃该放弃的是无奈&#xff0c;放弃不该放弃的是无能&#xff0c;不放弃该放弃的是无知&#xff0c;不放弃不该放弃的是执着&#xff01; 【1. 聊天室小案例】 文件目录 初始mysql数据库 index.html window.location.assign(url); 触发窗口加载并显示指定的 url的内容 当前…

TSINGSEE青犀特高压输电线可视化智能远程监测监控方案

一、背景需求分析 特高压输电线路周边地形复杂&#xff0c;纵横延伸几十甚至几百千米&#xff0c;并且受所处地理环境和气候影响很大。传统输电线路检查主要依靠维护人员周期性巡视&#xff0c;缺乏一定的时效性&#xff0c;在巡视周期的真空期也不能及时掌握线路走廊外力变化…

AQS面试题总结

一&#xff1a;线程等待唤醒的实现方法 方式一&#xff1a;使用Object中的wait()方法让线程等待&#xff0c;使用Object中的notify()方法唤醒线程 必须都在synchronized同步代码块内使用&#xff0c;调用wait&#xff0c;notify是锁定的对象&#xff1b; notify必须在wait后执…

振弦式传感器读数模块VM5系列介绍

VM5系列是专门针对单线圈式振弦传感器研发&#xff0c;可完成传感器的线圈激励、频率读数、温度测量等工作&#xff0c;具有标准的 UART&#xff08;TTL/RS232/RS485&#xff09;和 IIC 数字接口、模拟量输出接口&#xff08;电压或电流&#xff09;&#xff0c;通过数字接口数…

【论文阅读笔记】GLM-130B: AN OPEN BILINGUAL PRE-TRAINEDMODEL

Glm-130b:开放式双语预训练模型 摘要 我们介绍了GLM-130B&#xff0c;一个具有1300亿个参数的双语(英语和汉语)预训练语言模型。这是一个至少与GPT-3(达芬奇)一样好的100b规模模型的开源尝试&#xff0c;并揭示了如何成功地对这种规模的模型进行预训练。在这一过程中&#xff0…

arcgis图上添加发光效果!

看完本文, 你可以不借助外部图片素材, 让你的图纸符号表达出你想要的光! 我们以之前的某个项目图纸为例,来介绍下让符号发光的技术! 第一步—底图整理 准备好栅格影像底图、行政边界的矢量数据,确保“数据合适、位置正确、边界吻合”。 确定好图纸的大小、出图比例、投…

食品企业数字孪生可视化管理平台,实现智慧轻工业高质量发展

如今&#xff0c;数字技术正在打破传统食品产业的边界&#xff0c;随着食品加工产业链不断进化为智慧体&#xff0c;数字孪生技术已经成了食品行业数字进阶的重要抓手。食品加工数字孪生工厂&#xff0c;通过应用数字孪生技术&#xff0c;将食品加工工厂的自动化生产线全过程进…

浏览器哪家强——PC端篇

今天的分享将围绕一个大家再熟悉不过的名称展开——浏览器。 根据百科给出的解释&#xff1a;浏览器是用来检索、展示以及传递Web信息资源的应用程序。通俗的说&#xff0c;浏览器就是一种阅读工具&#xff0c;类似记事本、word、wps&#xff0c;只不过后者阅读的是文本文档&am…

Linux0.11内核源码解析-malloc

malloc介绍 Linux内核版本0.11中的malloc.c文件实现了内存分配的功能。在这个版本的Linux内核中&#xff0c;malloc.c文件包含了内核级别的内存分配函数&#xff0c;用于分配和释放内核中的内存。这些函数可以帮助内核管理可用的内存&#xff0c;并允许内核动态地分配和释放内…

ajax-axios发送 get请求 或者 发送post请求带有请求体参数

/* axios v0.21.1 | (c) 2020 by Matt Zabriskie */ !function(e,t){"object"typeof exports&&"object"typeof module?module.exportst():"function"typeof define&&define.amd?define([],t):"object"typeof export…

记一次大数据事故@用了很久的虚拟机环境突然不能联网了

记一次大数据事故用了很久的虚拟机环境突然不能联网了 背景 今天打开自己电脑上的虚拟机环境打算练习一下flink&#xff0c;结果发现vmware里虚拟机能正常开机&#xff0c;也能正常进图os&#xff0c;但是就是不能ping通主机&#xff0c;主机也不能ping通虚拟机 探查 1、…

绝缘检测原理和绝缘电阻计算方法

文章目录 简介绝缘检测功能绝缘检测原理绝缘电阻检测的常用方法不平衡电桥法 绝缘电阻绝缘电阻的计算 绝缘检测开启或关闭为什么根据 V1 &#xff1c; V2 或 V1 ≥ V2 判断是上桥臂并入电阻还是下桥臂并入电阻 简介 绝缘检测是判断动力&#xff08;正、负&#xff09;总线与外…

Maven本地配置获取nexus私服的依赖

场景 Nexus-在项目中使用Maven私服&#xff0c;Deploy到私服、上传第三方jar包、在项目中使用私服jar包&#xff1a; Nexus-在项目中使用Maven私服&#xff0c;Deploy到私服、上传第三方jar包、在项目中使用私服jar包_nexus maven-releases 允许deploy-CSDN博客 在上面讲的是…

【6】c++11新特性(稳定性和兼容性)—>Lambda表达式

基本用法 lambda表达式是c最重要也是最常用的特性之一&#xff0c;这是现代编程语言的一个特点&#xff0c;lambda表达式有如下的一些优点&#xff1a; &#xff08;1&#xff09;声明式的编成风格&#xff1a;就地匿名定义目标函数活着函数对象&#xff0c;不需要额外写一个命…

Ubuntu20.04安装CUDA、cuDNN、tensorflow2可行流程(症状:tensorflow2在RTX3090上运行卡住)

最近发现我之前在2080ti上运行好好的代码&#xff0c;结果在3090上运行会卡住很久&#xff0c;而且模型预测结果完全乱掉&#xff0c;于是被迫研究了一天怎么在Ubuntu20.04安装CUDA、cuDNN、tensorflow2。 1.安装CUDA&#xff08;包括CUDA驱动和CUDA toolkit&#xff0c;注意此…

【MySQL】MySQL的安装与配置环境变量(使其在控制台上使用)

作者主页&#xff1a;paper jie_博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《MySQL》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精力)打造&a…

删除文件要谨慎!如何在Linux中删除目录或文件

删除目录和文件是任何操作系统中最基本但最重要的功能之一。在Linux中,如果运行的是窗口环境,则可以使用文件管理器应用程序查找和删除文件。也许你是通过SSH远程登录的,或者你的Linux计算机没有安装GUI,或者你想对你要删除的内容有更多的控制权。与Linux中的任何东西一样,…

【微服务 Spring Cloud Alibaba】- Nacos 服务注册中心

目录 1. 什么是注册中心&#xff1f; 1.2 注册中心的作用 2. SpringBoot 整合 Nacos 实现服务注册中心 2.1 将服务注册到 Nacos 2.2 实现消费者 3. 服务列表各个参数的含义、作用以及应用场景 1. 什么是注册中心&#xff1f; 注册中心是微服务架构中的一个重要组件&…

NoSQL数据库以及架构介绍

文章目录 一. 什么是NoSQL&#xff1f;二. NoSQL分类三. NoSQL与关系数据库有什么区别四. NoSQL主要优势和缺点五. NoSQL体系框架 其它相关推荐&#xff1a; 系统架构之微服务架构 系统架构设计之微内核架构 鸿蒙操作系统架构 架构设计之大数据架构&#xff08;Lambda架构、Kap…