Spring源码-循环依赖

devtools/2024/10/21 23:18:51/

核心流程永:

A创建的时候,发现依赖了B,那就创建B.

B在创建的过程中,需要注入A,那就去单例池找A,如果找不到,那就去creatingSet中找A,如果存在A,那就说明存在循环依赖。判断A需不需要进行AOP,如果需要AOP,那就提前进行AOP;如果不需要,那就返回原始对象。

如果提前AOP,那就创建代理对象,并将创建出来的代理对象放到二级缓存中,二级缓存保证了代理对象只会被创建一次。

三级缓存

  1. singletonObjects 单例池(完整Bean生命周期)
  2. earlySingletonObjects (半成品)早期的单例池(普通对象或者代理对象),存放到是三级缓存执行后的结果
  3. singletonFactory (ObjectFactory)临时存放Lambda表达式<普通对象>,可以生成代理对象,放到二级缓存(生成后就清空)

使用到的核心方法:

1.addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)):三级缓存存放beanName和lambda表达式,lambda表达式存放原始对象,并且通过该lambda表达式可以生成AOP的代理对象

2.getSingleton():找bean--涉及一级缓存、二级缓存、三级缓存

说明:@EnableAOP就是生成一个BeanPostProcessor,然后AOP依赖BeanPostProcessor来生成代理对象

例子:

java">A a= new A();a.b = b;B b = new B();b.a = a;

说明:

1.A在创建的时候,在属性填充的时候,会调用getSingleton找B,此时B还没有创建,那这个方法就直接返回,去创建B

2.B在属性填充的时候,也会进到这个方法(getSingleton),去一级缓存,二级缓存拿,拿不到就加锁,去三级缓存拿,拿到lambda表达式,执行lambda表达式,拿到A的原始对象或者创建A的代理对象或者,完成B的创建,将B放到单例池。

3.然后A从单例池拿到B,完成后续创建流程。

如果不存在代理的逻辑,那么两级缓存就能解决循环依赖的问题

如果在初始化后要进行AOP,那么就需要在初始化后生成代理对象,如果存在循环依赖的问题,就需要提前进行AOP。

我们怎么知道出现了循环依赖呢?

在一开始就记录了creatingSet<>(正在创建中的bean)

核心代码如下:

java">// 为了解决循环依赖提前缓存单例创建工厂
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));// isSingletonCurrentlyInCreation正在创建中if (earlySingletonExposure) {if (logger.isTraceEnabled()) {logger.trace("Eagerly caching bean '" + beanName +"' to allow for resolving potential circular references");}// 循环依赖-添加到三级缓存addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}

1.Lambda表达式的执行

DefaultSingletonBeanRegistry#getSingleton

java">@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// Quick check for existing instance without full singleton lockObject singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 一级缓存没有singletonObject = this.earlySingletonObjects.get(beanName);// 去二级缓存拿if (singletonObject == null && allowEarlyReference) { // 二级缓存没有 加锁,去三级缓存找synchronized (this.singletonObjects) { // 加锁// Consistent creation of early reference within full singleton locksingletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();// 执行三级缓存的lambda表达式this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);// 用完 lambda表达式就移除}}}}}}return singletonObject;
}

执行lambda表达式,那就会调用getEarlyBeanReference(在类AbstractAutoProxyCreator)

在这个方法中,earlyProxyReferences.put:记录是否进行了AOP,如果进行了AOP,就放到这个Map中

然后调用wrapIfNecessary:判断是否需要AOP,需要就createProxy(创建代理对象)

正常进行AOP的地方(初始化后):AbstractAutoProxyCreator#postProcessAfterInitialization()

如果提前进行了AOP,那么这个方法不会再AOP,直接返回原始对象;之后去二级缓存找到代理对象,完成A的创建。

 

java">@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);if (this.earlyProxyReferences.remove(cacheKey) != bean) {// 如果AOP后发现bean发生改变return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;
}

2.几种循环依赖的情况

Enable注解就是生成一个BeanPostProcessor(不完全正确),比如@EnableTransactional没有新生成beanPostProcessor,添加的是Advisor。

2.1@Async会导致暴露的对象改变

java">if (earlySingletonExposure) {Object earlySingletonReference = getSingleton(beanName, false);if (earlySingletonReference != null) {if (exposedObject == bean) { // 如果有些postProcessor直接创建了bean,那这里就不一样exposedObject = earlySingletonReference;}else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {// beanName被哪些bean依赖了,现在发现beanName所对应的bean对象发生了改变,那么则会报错String[] dependentBeans = getDependentBeans(beanName);Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);for (String dependentBean : dependentBeans) {if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {actualDependentBeans.add(dependentBean);}}if (!actualDependentBeans.isEmpty()) {throw new BeanCurrentlyInCreationException(beanName,"Bean with name '" + beanName + "' has been injected into other beans [" +StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +"] in its raw version as part of a circular reference, but has eventually been " +"wrapped. This means that said other beans do not use the final version of the " +"bean. This is often the result of over-eager type matching - consider using " +"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");}}}
}

(AOP和@Async都存在的情况下,并且存在循环依赖,那就会抛异常)

@Async的大致执行原理:创建代理对象,异步开一个线程去完成操作的。和AOP是不同的

@EnableAsync就会新增一个postProcessor,这个postProcessor在AOP之后执行

Async会生成一个新的代理对象,那么此时exposedObject就发生改变了。这样就会导致赋值给其他Bean的和放到单例池的不是同一个Bean

解决办法:加了lazy注解之后,会给bService直接生成一个代理对象给A赋值。之后真正用到bService的时候,才会去找A,或者创建B的bean,比如这里的test用到bService

说明:A中加Async导致出错,是因为A依赖B,需要创建B,B在创建的时候用到了A的代理对象,赋值给B的这个代理对象和A最终生成的对象(A最终放到单例池的对象)不是同一个,所以报错

说明:把Async写到bService,就不会报错,此时是因为,B需要拿到完整的A,拿到A之后,就可以进行初始化、AOP以及Async,之后会生成完整的B。

2.2两个原型Bean产生的循环依赖

两个都是原型bean,那他们产生的循环依赖没有办法解决;如果只有一个原型Bean,那么不会报错

两个原型bean的原因:他们会 互相 一直创建彼此,导致死循环

2.3实例化使用构造方法产生的循环依赖

解决办法:通过@Lazy注解来解决,Spring会创建一个aService的代理对象来给B的构造方法使用

2.4@Transactional

不会报错,@Transactional因为没有新生成beanPostProcessor,添加的是Advisor,所以最后暴露的对象是同一个

2.5自己注入自己

不会出现问题

执行流程:

1.实例化A,存到三级缓存

2.注入A,先去一级缓存找A,再去二级缓存找A,再去三级缓存找A,此时肯定能找到A(或者是A的普通对象,或者是A的代理对象),那么完成A的注入

3.继续创建,完成A的创建


http://www.ppmy.cn/devtools/122169.html

相关文章

github命令行管理工具推荐

GitHub 管理工具推荐 背景 在使用 GitHub 管理仓库时&#xff0c;需要在 Web 端创建远程仓库&#xff0c;在本地创建本地仓库&#xff0c;然后再用 git remote add origin url 进行关联。这个过程相对繁琐&#xff0c;而且还有优化的空间。如果频繁创建仓库&#xff0c;就更能…

LeetCode 91. 解码方法

LeetCode 91. 解码方法 一条包含字母 A-Z 的消息通过以下映射进行了 编码 &#xff1a; “1” -> ‘A’ “2” -> ‘B’ “25” -> ‘Y’ “26” -> ‘Z’ 然而&#xff0c;在 解码 已编码的消息时&#xff0c;你意识到有许多不同的方式来解码&#xff0c;因为有些…

java:缓存 json格式

以下是修改后的代码&#xff1a; import org.apache.ibatis.session.SqlSession; import org.springframework.data.redis.core.RedisTemplate; import com.alibaba.fastjson.JSON; import java.util.List; import java.util.stream.Collectors; import java.util.logging.Logg…

学会这几个简单的bat代码,轻松在朋友面前装一波13[通俗易懂]

大家好&#xff0c;又见面了&#xff0c;我是你们的朋友全栈君。 这个标题是干什么用的? 最近看晚上某些人耍cmd耍的十分开心&#xff0c;还自称为“黑客”&#xff0c;着实比较搞笑.他们那些花里胡哨的东西在外行看来十分nb,但只要略懂一些&#xff0c;就会发现他们的那些十…

Flask+微信小程序实现Login+Profile

Python代码 首先flask的session用不了&#xff0c;只能用全局变量来实现。 import pymysql from flask import Flask, request, jsonify, session from flask_cors import CORS from flask import make_responseapp Flask(__name__) CORS(app, supports_credentialsTrue) #…

音视频入门基础:FLV专题(8)——FFmpeg源码中,解码Tag header的实现

一、引言 在《音视频入门基础&#xff1a;FLV专题&#xff08;7&#xff09;——Tag header简介》中对Tag header进行了简介&#xff0c;本文讲述FFmpeg源码中是怎样解码FLV文件的Tag header&#xff0c;拿到里面的信息。 二、FFmpeg源码中&#xff0c;解码Tag header的实现 …

08-Registry搭建docker私仓

08-Registry搭建docker私仓 Docker Registry Docker Registry是官方提供的工具&#xff0c;用于构建私有镜像仓库。 环境搭建 Docker Registry也是Docker Hub提供的一个镜像&#xff0c;可以直接拉取运行。 步骤&#xff1a; 拉取镜像 docker pull registry启动Docker R…

代码随想录一刷完结

非常偶然的机会让我看到这个算法训练营的存在&#xff0c;虽然我也没有多大的动力&#xff0c;但当时就觉得没什么事情&#xff0c;想着刷刷题&#xff0c;为以后找工作打打基础。 收获 提示&#xff1a;在刷题过程中的收获 第一次使用CSDN记录&#xff0c;每次有别人点赞和收…