知识回顾

解析完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方法,然后执行BeanPostProcessorbefore方法,然后执行init-method,然后执行BeanPostProcessorafter方法。那么在执行属性填充时必然会去查找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)对象的提前暴露的更多相关文章

  1. Spring源码分析之Bean的创建过程详解

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...

  2. Spring 源码分析之 bean 依赖注入原理(注入属性)

         最近在研究Spring bean 生命周期相关知识点以及源码,所以打算写一篇 Spring bean生命周期相关的文章,但是整理过程中发现涉及的点太多而且又很复杂,很难在一篇文章中把Spri ...

  3. Spring源码-IOC部分-Bean实例化过程【5】

    实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...

  4. Spring源码解读Spring IOC原理

    一.什么是Ioc/DI? IoC 容器:最主要是完成了完成对象的创建和依赖的管理注入等等. 先从我们自己设计这样一个视角来考虑: 所谓控制反转,就是把原先我们代码里面需要实现的对象创建.依赖的代码,反 ...

  5. dubbo源码分析3-service bean的创建与发布

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  6. Spring源码分析专题 —— IOC容器启动过程(上篇)

    声明 1.建议先阅读<Spring源码分析专题 -- 阅读指引> 2.强烈建议阅读过程中要参照调用过程图,每篇都有其对应的调用过程图 3.写文不易,转载请标明出处 前言 关于 IOC 容器 ...

  7. 【Spring源码分析】Bean加载流程概览(转)

    转载自:https://www.cnblogs.com/xrq730/p/6285358.html 代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. ...

  8. Spring源码分析:Bean加载流程概览及配置文件读取

    很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事的都是Java Web的工作,对于程序员来说,一个Web项目用到Spring,只是配置一下配置文件而已 ...

  9. 【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

  10. Spring 源码分析之 bean 实例化原理

    本次主要想写spring bean的实例化相关的内容.创建spring bean 实例是spring bean 生命周期的第一阶段.bean 的生命周期主要有如下几个步骤: 创建bean的实例 给实例 ...

随机推荐

  1. OpenCV - Add Noise的一些方法

    噪声常用有两种:一种椒盐噪声,一种高斯噪声. import numpy as np def pepper_and_salt(src, proportion): """ : ...

  2. Python - 本地文件读写(初级)

  3. 插值方法 - Newton向前向后等距插值

    通常我们在求插值节点的开头部分插值点附近函数值时,使用Newton前插公式:求插值节点的末尾部分插值点附近函数值时,使用Newton后插公式. 代码: 1 # -*- coding: utf-8 -* ...

  4. 插值方法 - Lagrange插值多项式

    Lagrange插值多项式代码: 1 # -*- coding: utf-8 -*- 2 """ 3 Created on Wed Mar 25 15:43:42 202 ...

  5. i2c总线编码

    i2c总线编码 发送启动信号S 在同步时钟线SCL 为高电平时,数据线出现的由高到低的下降沿. 启动信号子程序STA 1 /************************************** ...

  6. jQ模拟打字效果插件typetype

    typetype是一个jquery插件,可以模拟人类的打字效果. 效果图如下所示: 查看演示 http://weber.pub/demo/160828/jQuery.Type/jQuery.type. ...

  7. java中递归的用法和例子

    递归   直接或者间接调用自己, public class Test{    public static void main(String[] args){        int i = 5;    ...

  8. MySQL 中 SQL语句大全(详细)

    sql语句总结 总结内容 1. 基本概念 2. SQL列的常用类型 3. DDL简单操作 3.1 数据库操作 3.2 表操作 4. DML操作 4.1 修改操作(UPDATE SET) 4.2 插入操 ...

  9. ZXing Blazor 扫码组件 , ssr/wasm通用

    项目介绍 本项目是利用 ZXing 进行封装的 Blazor 组件库 直接调用手机或者桌面电脑摄像头进行扫码 项目截图              项目地址 https://github.com/den ...

  10. DC-1 靶机渗透

    DC-1 靶机渗透 *概况*: 下载地址 https://www.vulnhub.com/entry/dc-1,292/ *官方描述:* DC-1 is a purposely built vulne ...