IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring原理篇(8)--循环依赖到底是如何解决的?你只需要看这一篇原理;你就知道循环依赖是怎么解决的; 就很神奇 对不对? -> 正文阅读

[Java知识库]Spring原理篇(8)--循环依赖到底是如何解决的?你只需要看这一篇原理;你就知道循环依赖是怎么解决的; 就很神奇 对不对?

@TOC# Spring系列
记录在程序走的每一步___auth:huf


现在这个章节 我们来到了Spring的循环依赖; 我们在现实项目中处处可见; 因为Spring的机制 帮我们解决了循环依赖; 所以我们就可以不用再关注循环依赖了玛? 如果面试过程中 别人问你 循环依赖是怎么解决的 你怎么回答?.

Spring 循环依赖;

第一个问题 : 什么是循环依赖?

什么是循环依赖? 很简单的一个问题; 给一个案例

@Service
public class A {
    @Autowired
    B b;
}
@Service
public class B {
    @Autowired
    A a;
}

这就是循环依赖; 当 Spring 容器中 (ioc) ABean 里面有一个 属性 B , BBean 有一个属性 A;他们相互引用; 注意 是在IOC容器中; 如果不是在容器中 这样写 是没有问题的; 我们把实体交给Spring管理 实际上是管理的实体的生命周期; 也就是Bean的生命周期;
以下我们画图 来帮助同学们理解;

A创建
需要B
B创建
需要A

通过上面这个图 我们很简洁明了的知道; 他们在做一个循环; 他们的问题是出在了Spring的生命周期里面 我们在上面一个篇章讲到; Spring启动的依赖注入; 但是却没有陈述这部分 循环依赖;
下面我们来看一眼 在Spring的生命周期简化版:
1:Spring扫描class得到BeanDefinition;
2:根据得到的BeanDefinition去生成bean
3. 首先根据class推断构造方法
4. 根据推断出来的构造方法,反射,得到一个对象(暂时叫做原始对象)
5. 填充原始对象中的属性(依赖注入)
6. 如果原始对象中的某个方法被AOP了,那么则需要根据原始对象生成一个代理对象
7. 把最终生成的代理对象放入单例池(源码中叫做singletonObjects)中,下次getBean时就直接 从单例池拿即可;
可以看到,对于Spring中的Bean的生成过程,步骤还是很多的,并且不仅仅只有上面的7步,还有很 多很多,比如Aware回调、初始化等等,这里不详细讨论。

 比如上文说的A类,A类中存在一个B类的b属性,所以,当A类生成了一个原始对象之后,就会去给b 属性去赋值,
 此时就会根据b属性的类型和属性名去BeanFactory中去获取B类所对应的单例bean。
 如果此时BeanFactory中存在B对应的Bean,那么直接拿来赋值给b属性;
 如果此时BeanFactory中 不存在B对应的Bean,则需要生成一个B对应的Bean,然后赋值给b属性。									
 问题就出现在第二种情况,如果此时B类在BeanFactory中还没有生成对应的Bean,
 那么就需要去生 成,就会经过B的Bean的生命周期。

第二个问题 : Spring是如何却循环依赖的?

在Spring中,通过某些机制帮开发者解决了部分循环依赖的问 题,这个机制就是三级缓存。

一级缓存 singletonObjects

单例池; 也就是singletonObject 我们创建对象的时候 首先就会去单例池寻找一下 看以下是否有这个单例池; 以下是源码展示: 在doGetBean的方法内:

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
		String beanName = transformedBeanName(name);
		Object beanInstance;
		这个地方 getSingleton 源码内;
		Object sharedInstance = getSingleton(beanName);

这里不过多的去陈述
一句话总结: singletonObjects 中缓存的是已经经历了完整生命周期的bean对象

二级缓存 earlySingletonObjects

earlySingletonObjects 比singletonObjects多了一个early,表示缓存的是早期的bean对象。 早期是什么意思?表示Bean的生命周期还没走完就把这个Bean放入了earlySingletonObjects。

三级缓存 ingletonFactories

singletonFactories中缓存的是ObjectFactory,表示对象工厂,表示用来创建早期bean对象的 工厂。


如果说现在 要我们去解决循环依赖; 我们要有什么思路去解决?
我的思路是这样的:
我们自己创建一个Map; 创建A的时候 一创建出来. 我们就把这个A对象对进去Map里面
key:“A” value:A: 然后A继续往下注入属性; 现在发现有属性B 这时候先去Map用拿一次,没拿到
那么 开始创建B B一创建; 那么立马把B 保存在Map中; 然后B开始注入属性 发现有一个属性A 然后去Map里面拿到A属性; 注入进来; 那么A的创建 也能够完成.


从上面这个解决方案中, 是不是比较完美的一个解决方案.? 实际上在上面的方案中 缺少了Spring的另外一个重要的功能体系;

aop

这里不会累赘AOP的原理以及源码; 仅仅是抛出一个问题:

		AOP就是通过一个BeanPostProcessor来实现的;
		这个BeanPostProcessor就是 AnnotationAwareAspectJAutoProxyCreator,
		它的父类是AbstractAutoProxyCreator,
		而在 Spring中AOP利用的要么是JDK动态代理,要么CGLib的动态代理,所以如果给一个类中的某个方法 
		设置了切面,那么这个类最终就需要生成一个代理对象。 
		一般过程就是:A类--->生成一个普通对象-->属性注入-->基于切面生成一个代理对象
		-->把代理对象放入singletonObjects单例池中

我们根据上面的解决方案 我们在B的注入点里面 保存了A的原始对象. 那么 A要是个含有切面的代理对象
最后他们的对象是不是同一个对象?


承接上面一个篇章 的 doCreateBean 源码中我们写到 有一句话: 是解决循环依赖; 循环依赖将放在下个篇章去讲; 如果上一个篇章没有看的同学 可以去看一下; 以下是源码:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
		省掉很多代码.... 上面的代码全部在前面文章中 已经一句一句的描述过; 
		到这里地方;Bean就彻底的创建完成; 仅仅是创建出来;这个Bean此时 并没经过初始化; 属性注入
		所有这里的Bean 就相当是New了一个出来;
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			singletonFactories中存的是某个beanName对应的ObjectFactory,在bean的生命周期中, 
			生成完原始对象之后,就会构造一个ObjectFactory存入singletonFactories中。
			这个ObjectFactory 是一个函数式接口,所以支持Lambda表达式
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}
		省掉很多代码....
}

getEarlyBeanReference

	该方法会去执行SmartInstantiationAwareBeanPostProcessor中的getEarlyBeanReference方法,
	 而这个接口下的实现类中只有两个类实现了这个方法,
	 一个是AbstractAutoProxyCreator,
	 一个是InstantiationAwareBeanPostProcessorAdapter
	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
		return exposedObject;
	}

InstantiationAwareBeanPostProcessorAdapter

@Override 
public Object getEarlyBeanReference(Object bean, String beanName)
 throws BeansException { 
	 return bean; 
}

AbstractAutoProxyCreator

在整个Spring中,默认就只有AbstractAutoProxyCreator真正意义上实现了 
getEarlyBeanReference方法,而该类就是用来进行AOP的
AnnotationAwareAspectJAutoProxyCreator的父类是AbstractAutoProxyCreator
@Override 
public Object getEarlyBeanReference(Object bean, String beanName) {
   Object cacheKey = getCacheKey(bean.getClass(), beanName); 					
   this.earlyProxyReferences.put(cacheKey, bean); 
   return wrapIfNecessary(bean, beanName, cacheKey); 
 }
1:A创建时
2:A原始对象生成一个ObjectFactory
3:需要B
4:B去创建
5:需要A
6:B创建完成
7:将B赋值给A对象
8:AB对应的Bean都创建完
singletonObjects

总结

配上图 在节点 (2)中: ObjectFactory就是上文说的labmda表达式,中间有getEarlyBeanReference方 法,注意存入singletonFactories时并不会执行lambda表达式,也就是不会执行 getEarlyBeanReference方法;
在节点(5)
1:从singletonFactories根据beanName得到一个ObjectFactory,然后执行 ObjectFactory,也就是执行getEarlyBeanReference方法,此时会得到一个A原始对象经过AOP之 后的代理对象,然后把该代理对象放入earlySingletonObjects中,注意此时并没有把代理对象放入 singletonObjects中,那什么时候放入到singletonObjects中呢?

2:我们这个时候得来理解一下earlySingletonObjects的作用,此时,我们只得到了A原始对象的代理对 象,这个对象还不完整,因为A原始对象还没有进行属性填充,所以此时不能直接把A的代理对象放 入singletonObjects中,所以只能把代理对象放入earlySingletonObjects,假设现在有其他对象依 赖了A,那么则可以从earlySingletonObjects中得到A原始对象的代理对象了,并且是A的同一个代 理对象。

3:当B创建完了之后,A继续进行生命周期,而A在完成属性注入后,会按照它本身的逻辑去进行AOP, 而此时我们知道A原始对象已经经历过了AOP,所以对于A本身而言,不会再去进行AOP了,那么怎 么判断一个对象是否经历过了AOP呢?会利用上文提到的earlyProxyReferences,在 AbstractAutoProxyCreator的postProcessAfterInitialization方法中,会去判断当前beanName是 否在earlyProxyReferences,如果在则表示已经提前进行过AOP了,无需再次进行AOP。

4:对于A而言,进行了AOP的判断后,以及BeanPostProcessor的执行之后,就需要把A对应的对象放 入singletonObjects中了,但是我们知道,应该是要把A的代理对象放入singletonObjects中,所以 此时需要从earlySingletonObjects中得到代理对象,然后入singletonObjects中。

整个循环依赖解决完毕;

最后祝各位中秋节快乐 我们之后再见;

see you

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-09-22 14:31:58  更:2021-09-22 14:32:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 17:11:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码