Spring 源码(14)Spring Bean 的创建过程(6)对象的提前暴露
知识回顾
解析完Bean
信息的合并,可以知道Spring
在实例化Bean
之后,属性填充前,对Bean
进行了Bean
的合并操作,这里的操作主要做了对Bean
对象标记了@Autowired
、@Value
、@Resource
、@PostConstruct
、@PreDestroy
注解的字段或者方法进行解析,主要涉及到类都是BeanPostProcessor
的实现,可见BeanPostProcessor
接口的重要性。
这里再次回顾下BeanPostProcessor
接口有哪些子接口:
InstantiationAwareBeanPostProcessor
Spring
给机会提前进行实例化,可用通过代理进行对象的创建SmartInstantiationAwareBeanPostProcessor
用于预测
Bean
的类型,决定Bean
的构造函数,用于实例化MergedBeanDefinitionPostProcessor
用于合并
Bean
的信息,即解析Bean对象方法上或者字段上标记的注解,比如@Resource
、@Autowired
、@PostConstruct
等。DestructionAwareBeanPostProcessor
用于销毁
Bean
时调用的,比如执行标有@PreDestroy
注解的方法
这些子接口的实现类比较多,比如:
AutowiredAnnotationBeanPostProcessor
ComonAnnotationBeanPostProcessor
InitDestroyAnnotationBeanPostProcessor
AnnotationAwareAspectJAutoProxyCreator
ScheduledAnnotationBeanPostProcessor
当然还不止这里列出来的,还有其他的就不列了,接下来分析Spring
源码接下来做了什么?
对象的提前暴露
看源码:
// 省略代码....
// 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));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 添加一个lambda表达式到三级缓存中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
// 省略代码....
源码这里就添加了一个lambda
表达式到一个Map
中,然后结束了,并且明确说明了提前暴露是为了解决循环依赖问题。
什么是循环依赖?
循环依赖顾名思义,就是你中有我,我中有你,打个比方现在有个对象A
,他有个属性b
,这个属性b是对象B
的,然后对象B
中有个属性a
,属性a
是对象A
的。
现在开始创建对象,按照Spring
的标准创建流程getBean
-->doGetBean
-->createBean
-->doCreateBean
,先实例化,然后属性填充,然后执行aware
方法,然后执行BeanPostProcessor
的before
方法,然后执行init-method
,然后执行BeanPostProcessor
的after
方法。那么在执行属性填充时必然会去查找a
或者b
属性对应的对象,如果找不到就会去创建,那么就会出现下图的样子:
这样必然就出现了循环依赖,你我紧紧相拥,不想放开,死也要在一起的情形。
那么Spring
为什么解决循环依赖需要进行提前暴露对象呢?
所以这个问题就很简单了,我们都知道Bean
的创建是将实例化和初始化分开的,实例化之后的对象在JVM
堆中已经开辟了内存空间地址,这个地址是不会变的,除非山崩地裂,海枯石烂,也就是应用重启了。
因此可以将已经实例化的对象放在另外一个Map
中,一般来说都称之为半成品,当填充属性时,可以将先设置半成品对象,等到对象创建完之后在将半成品换成成品,这样的话对象进行属性填充时就可以直接先使用半成品填充,等到开始初始化时再将对象创建出来即可。
这样看来循环依赖只需要二级缓存就够了,但是在Spring
中,存在一种特殊的对象,就是代理对象。也就是说在放入的半成品我们现在多了一种对象,那就是代理对象,这个时候就会出现使用代理对象还是普通对象呢?所以干脆在搞一个Map
专门存放代理对象,这样就区分出来了,然后在使用的时候先判断下我们创建的对象是需要代理还是不需要代理,如果需要代理,那么就创建一个代理对象放在map
中,否则直接使用普通对象就可以了。
Spring中的实现方式
Spring
是怎么处理的呢?Spring
是将所有的对象都放在三级缓存中,也就是lambda
表达式中:
// 添加一个lambda表达式到三级缓存中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
// 判断一级缓存中是否存在
if (!this.singletonObjects.containsKey(beanName)) {
// 没有就放入三级缓存中
this.singletonFactories.put(beanName, singletonFactory);
// 清空二级缓存
this.earlySingletonObjects.remove(beanName);
// 添加到已经注册的单例集合中
this.registeredSingletons.add(beanName);
}
}
}
在属性填充的时候,会执行到getBean
,然后从缓存中获取getSingleton
:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
// 从一级缓存中获取bean实例
Object singletonObject = this.singletonObjects.get(beanName);
// 如果一级缓存中没有数据并且没有正在创建的Bean直接返回
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 如果有正在创建的Bean,那么冲二级缓存中获取,早期的单例对象
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
// 二次检查一级缓存中是否有单例对象
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 二次判断二级缓存中是否存在单例对象
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 从三级缓存中获取Bean
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 如果三级缓存中 单例工厂中有对象,那么就将该对象放在二级缓存中,并且清掉三级缓存
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
在获取单例对象时,会执行到三级缓存,然后执行getObject
方法,最终就会触发getEarlyBeanReference
方法的调用:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
// 添加 三级缓存,判断是否需要进行代理创建对象,是一个动态代理创建的代理对象
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
这里会判断,如果这个BeanDefinition
是否满足条件,如果不满足,那么直接返回了,否则就会执行到for
循环中的代码,而getEarlyBeanReference
方法在Spring
中只有AbstractAutoProxyCreator
类进行了实质的实现:
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 早期代理对象的引用集合
this.earlyProxyReferences.put(cacheKey, bean);
// 创建代理
return wrapIfNecessary(bean, beanName, cacheKey);
}
点进去:
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 创建代理对象
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
首先进行了判断,如果不满足创建代理的条件,都是直接返回这个对象,否则进入创建代理的方法,创建出代理对象,最终放入缓存中。点入到最后会发现使用了两种代理创建方式:
源码中的提前暴露对象牵扯出很多东西,循环依赖,三级缓存,aop等,这里解析了个大概,接下来继续主流程中的属性填充populaeBean
方法。
Spring 源码(14)Spring Bean 的创建过程(6)对象的提前暴露的更多相关文章
- Spring源码分析之Bean的创建过程详解
前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...
- Spring 源码分析之 bean 依赖注入原理(注入属性)
最近在研究Spring bean 生命周期相关知识点以及源码,所以打算写一篇 Spring bean生命周期相关的文章,但是整理过程中发现涉及的点太多而且又很复杂,很难在一篇文章中把Spri ...
- Spring源码-IOC部分-Bean实例化过程【5】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码解读Spring IOC原理
一.什么是Ioc/DI? IoC 容器:最主要是完成了完成对象的创建和依赖的管理注入等等. 先从我们自己设计这样一个视角来考虑: 所谓控制反转,就是把原先我们代码里面需要实现的对象创建.依赖的代码,反 ...
- dubbo源码分析3-service bean的创建与发布
dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...
- Spring源码分析专题 —— IOC容器启动过程(上篇)
声明 1.建议先阅读<Spring源码分析专题 -- 阅读指引> 2.强烈建议阅读过程中要参照调用过程图,每篇都有其对应的调用过程图 3.写文不易,转载请标明出处 前言 关于 IOC 容器 ...
- 【Spring源码分析】Bean加载流程概览(转)
转载自:https://www.cnblogs.com/xrq730/p/6285358.html 代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. ...
- Spring源码分析:Bean加载流程概览及配置文件读取
很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事的都是Java Web的工作,对于程序员来说,一个Web项目用到Spring,只是配置一下配置文件而已 ...
- 【Spring源码分析】Bean加载流程概览
代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...
- Spring 源码分析之 bean 实例化原理
本次主要想写spring bean的实例化相关的内容.创建spring bean 实例是spring bean 生命周期的第一阶段.bean 的生命周期主要有如下几个步骤: 创建bean的实例 给实例 ...
随机推荐
- 快速注册service服务
一.通过SpringContextHolder的getBean来注册service服务 导入的包:import com.bessky.platform.context.SpringContextHol ...
- BMZCTF SDNISC2020_过去和现在
SDNISC2020_过去和现在 打开附件就一张图片 根据题意感觉是图片中隐藏了什么信息 使用binwalk -e分离这里foremost不行 三个文件查看在第一个中发现flag
- Quartz高可用定时任务快速上手
定时任务使用指南 如果你想做定时任务,有高可用方面的需求,或者仅仅想入门快,上手简单,那么选用它准没错. 定时任务模块是对Quartz框架进一步封装,使用更加简洁. 1.引入依赖 <depend ...
- SVG Sprite 入门(SVG图标解决方案)
关于浏览器图标解决方案,一直就有很多 CSS Sprite,Icon Font,CSS Icon以及SVG.相对而言svg矢量图标以及支持浏览器自身解析的优点,很多团队都已经在使用了.这篇文章主要说明 ...
- token的工作原理及其功能
一.前言 登录模块是我们在前端项目中一定会有的模块,模块中有一个重要的部分是用户登录验证,对于验证用户是否登录过,我们直接处理办法是检查缓存中是否存在token值,若存在则用户可直接登录,反之,用户需 ...
- Java中 i++和++i 的区别
学习目标: 理解i++和++i的区别 学习内容: 1.i++ / i- - i++/i- -:遇到 i++或 i- -,i先参与运算,然后 i 再自加或自减1 代码如下: int a = 1; int ...
- phpshe xml注入
*php商城系统 xml注入* **页面样式* *Xml原理参考:* https://www.cnblogs.com/20175211lyz/p/11413335.html *漏洞函数simplexm ...
- Mybatis模糊查询结果为空的解决方案
写在前面 Mybatis使用模糊查询,查询结果为空的解决方案,我的代码是 select * from sp_user where 1=1 <if test="username!=nul ...
- Struts2-拦截器原理
拦截器原理包含Aop思想和责任链模式 1.Aop思想 aop是面向切面编程,有基本功能,扩展功能,不通过修改源代码方式扩展功能.(动态代理) 2.责任链模式,Java有23种设计模式,责任链模式是其中 ...
- spring-基于注解的aop开发(快速入门)
步骤: 1.导入坐标 <dependency> <groupId>junit</groupId> <artifactId>junit</artif ...