Spring 循环引用(二)源码分析

Spring 循环引用(二)源码分析

Spring 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html)

Spring 循环引用相关文章:

  1. 《Spring 循环引用(一)一个循环依赖引发的 BUG》:https://www.cnblogs.com/binarylei/p/10325698.html
  2. 《Spring 循环引用(二)源码分析》:https://www.cnblogs.com/binarylei/p/10326046.html

一、Spring 中单例 bean 的管理

Spring 对单例 bean 的管理都是在 DefaultSingletonBeanRegistry 中完成的,这里会涉及到其内部所使用的几个内部属性:

// 1.1 保存最终创建成功的单例 beanName -> beanInstance
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 1.2 中间变量,beanName -> Objectfactory
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 1.3 中间变量,bean 还在创建的时候就可以获取,用于检测循环引用
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

这里涉及用于存储 bean 的不同的 Map,可能让人感到崩溃,简单解释如下:

  1. singletonObjects:用于保存 beanName 和创建 bean 实例之间的关系。beanName -> beanInstance
  2. singletonFactories:用于保存 beanName 和对象工厂的引用关系,一旦最终对象被创建(通过 objectFactory.getObject()),此引用信息将删除。beanName -> Objectfactory
  3. earlySingletonObjects:用于保存 beanName 和创建的原始 bean 的引用关系,注意这里是原始 bean,即使用工厂方法或构造方法创建出来的对象,一旦对象最终创建好,此引用信息将删除。 与 singletonObjects 的不同之处在于,此时 bean 还在创建过程中,而且之后还可以进行增强,也就是代理后这两个 bean 就不是同一个了。可以通过 getBean 方法获取到了,其目的是用来检测循环引用。

从上面的解释,可以看出,这 singletonFactories 和 earlySingletonObjects 都是一个临时的辅助状态。在所有的对象创建完毕之后,此两个对象的 size 都为 0。那么再来看下这两个对象如何进行协作:

(1) 方法1:创建单例对象

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            // 1. 将这个 bean 添加到 singletonsCurrentlyInCreation 集合中,这样就可以判断 bean 是否存在创建
            beforeSingletonCreation(beanName);
            // 2. 初始化 bean,委托给 ObjectFactory 完成
            singletonObject = singletonFactory.getObject();
            // 3. 从 singletonsCurrentlyInCreation 移除该 bean
            afterSingletonCreation(beanName);
            // 4. 创建完成进行注册,这样下次就可以从缓存中直接获取这个对象了
            addSingleton(beanName, singletonObject);
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
}

(2) 方法2:单例对象创建完成进行注册

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

(3) 方法3:将实例化后的对象暴露到容器中

Spring 在 bean 实例化后就会调用 addSingletonFactory 将这个对象提前暴露到容器中,这们就可以通过 getBean(A) 得到这个对象,即使这个对象仍正在创建。用于解决循环依赖。

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);
        }
    }
}

(4) 方法4:从缓存中获取 bean

这个方法也是专门用于解决循环依赖的问题,当不存在循环依赖时 earlySingletonObjects 总是 null。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

二、Spring 创建 bean 过程

我们从 BeanFactory#getBean(beanName) 调用说起,看一下这几个方法的调用顺序:

2.1 AbstractBeanFactory#doGetBean

这个方法先从缓存中获取 bean,没有再创建 bean,因此会调用方法 4 和方法 1,我们看一下调用过程。

(1) getSingleton(beanName, true)

doGetBean 首先从缓存中获取数据,Object sharedInstance = getSingleton(beanName),这个方法最终会调用 getSingleton(beanName, true)

(2) getSingleton(beanName, singletonFactory)

如果缓存中没有 bean,则会调用 addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) 来创建一个新 bean,代码如下:

if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> {
        try {
            return createBean(beanName, mbd, args);
        }
        catch (BeansException ex) {
            destroySingleton(beanName);
            throw ex;
        }
    });
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

一旦调用 getSingleton(beanName, singletonFactory) 方法,这个方法创建开始时就会标记这个 bean 为正在创建,创建结束后移除对应的标记。直接创建 bean 的过程实际上是委托给了 createBean 方法。继续跟踪这个方法。

2.2 AbstractAutowireCapableBeanFactory#doCreateBean

doCreateBean 方法中完成了单例的 bean 有以下几个主要的步骤:

  1. createBeanInstance 实例化 bean 对象,一般是通过反射调用默认的构造器。
  2. populateBean bean 属性注入,在这个步骤会从 Spring 容器中查找对应属性字段的值,解决循环依赖问题。
  3. initializeBean 调用的 bean 定义的初始化方法。

(3) addSingletonFactory(beanName, singletonFactory)

在 createBeanInstance 后 populateBean 前 Spring 会将这个实例化的 bean 提前暴露到容器中,这样 populateBean 属性注入时就可以通过 getBean(A) 查找到。

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

(4) getSingleton(beanName, false)

在 bean 初始化完成还后还需要进行依赖的检查,这时因为提前暴露的这个 bean(即使用工厂方法或构造方法创建出来的对象) initializeBean 还可以进行增强,这样这两个 bean 就不是同一个了。Spring 默认是不允许这种情况发生的。

if (earlySingletonExposure) {
    Object earlySingletonReference = getSingleton(beanName, false);
    if (earlySingletonReference != null) {
        if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
        }
        else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            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 " +
                        "‘getBeanNamesOfType‘ with the ‘allowEagerInit‘ flag turned off, for example.");
            }
        }
    }
}

(5) addSingleton(beanName, singletonObject)

在 bean 创建结束后还有一步是在 getSingleton(beanName, singletonFactory) 中完成的,调用 addSingleton(beanName, singletonObject),即注册最终的 bean,同时清空中间的辅助状态。

这样单例 bean 的创建过程就完成了,下面就需要分析循环引用下 singletonFactories、earlySingletonObjects 这两个集合的状态。

三、循环引用下 Bean 状态分析

3.1 正常情况

在正常的情况下,调用顺序如下:以下有无,表示是否持有对指定 Bean 的引用

过程 方法 singletonFactories earlySingletonObjects singletonObjects
缓存中获取 getSingleton(beanName, true)
创建 bean getSingleton(beanName, singletonFactory)
提前暴露到容器中 addSingletonFactory(beanName, singletonFactory)
依赖检查 getSingleton(beanName, false)
注册 addSingleton(beanName, singletonObject)

可以看到正常情况下,单例 bean 暴露的对象只会出现在 singletonFactories 集合中,不可能出现在 earlySingletonObjects 集合中,除非在创建 bean 的过程中又调用了 getSingleton(beanName, true) 方法,也就是此时出现了循环引用。

3.2 循环引用

但是出现循环引用之后呢,就会出现这种情况:

过程 方法 singletonFactories earlySingletonObjects singletonObjects
缓存中获取 A getSingleton(A, true) A无B无 A无B无 A无B无
创建 A getSingleton(A, singletonFactory) A无B无 A无B无 A无B无
暴露 A 到容器中 addSingletonFactory(A, singletonFactory) A有B无 A无B无 A无B无
populateBean(A, mbd, instanceWrapper) A 注入 B 时又依赖了 A,此时由 B 准备解析 A……
缓存中获取 A getSingleton(A, true) A无B有 A有B无 A无B无
注册 B addSingleton(B, singletonObject) A无B无 A有B无 A无B有
populateBean(A, mbd, instanceWrapper) 完成属性注入
A- = initializeBean(beanName, exposedObject, mbd) 在 initializeBean 之后 A 变为 A-
依赖检查 getSingleton(beanName, false) A无B无 A有B无 A无B有
注册 A getSingleton(beanName, false) A无B无 A无B无 A有B有

在上面这个过程中,在对 A 进行验证时,就会从 earlySingletonObjects 中取得一个 A,但是这个 A 和后面的 A- 可能不是同一个对象,这是因为有了 beanPostProcessor 存在,它可以改变 bean 的最终值,比如对原始 bean 进行封装,代理等。在这个过程中,出现了 3 个对象 A, A-, B,而 B 中所持有的 A 对象为原始的 A。如果这里的 A 和 A- 不是同一个对象,即产生了 beanA 有了 beanB 的引用,但 beanB 并没有 beanA 的引用,而是另一个 beanA 的引用。



每天用心记录一点点。内容也许不重要,但习惯很重要!

原文地址:https://www.cnblogs.com/binarylei/p/10326046.html

时间: 2025-01-16 14:56:05

Spring 循环引用(二)源码分析的相关文章

Spring整合MyBatis(二)源码分析

在Spring配置Mybatis的文件中我们可以看到如下代码: <!-- 扫描dao --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="org.tarena.note.dao"> </property> MapperScannerConfig

Spring中Bean命名源码分析

Spring中Bean命名源码分析 一.案例代码 首先是demo的整体结构 其次是各个部分的代码,代码本身比较简单,不是我们关注的重点 配置类 /** * @Author Helius * @Create 2019-10-25-20:16 */ @Configuration @ComponentScan(basePackages = {"service"}) public class SpringConfiguration { } 接口的实现类 public interface Use

Spring MVC初始化部分源码分析

首先定位到org.springframework.context.support.AbstractApplicationContext中的refresh()方法: public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh

spring boot 2.0 源码分析(三)

通过上一章的源码分析,我们知道了spring boot里面的listeners到底是什么(META-INF/spring.factories定义的资源的实例),以及它是创建和启动的,今天我们继续深入分析一下SpringApplication实例变量中的run函数中的其他内容.还是先把run函数的代码贴出来: /** * Run the Spring application, creating and refreshing a new * {@link ApplicationContext}. *

Spring AOP介绍及源码分析

一.AOP介绍 举个例子来说明一下吧!现在系统中有很多的业务方法,如上传产品信息.修改产品信息.发布公司库等:现在需要对这些方法的执行做性能监控,看每个业务方法的执行时间:在不改变原业务代码的基础上,也许我们会这么做: Offer接口: Offer实现: Offer代理: 我们要通过下面的方式来使用: 上面的例子的输出为: 上面的例子中,OfferProxy实现了IOffer,而所有的业务实现均委托给其成员offer:可以想像,这应该就是最简单的AOP的实现了:但这种方式会存在一个问题:如果有非

spring boot 2.0 源码分析(五)

在上一篇文章中我们详细分析了spring boot是如何准备上下文环境的,今天我们来看一下run函数剩余的内容.还是先把run函数贴出来: /** * Run the Spring application, creating and refreshing a new * {@link ApplicationContext}. * @param args the application arguments (usually passed from a Java main method) * @re

Spring bean定义解析源码分析

在上一篇Spring IOC容器启动简介中在ClassPathXmlApplicationContext的基础粗略的分析了IOC容器的启动过程,对一些比较复杂的步骤没有详细的说明,从本篇开始对其中的一些比较复杂的步骤进行分析.本篇对基于ClassPathXmlApplicationContext的IOC容器的bean定义的解析与加载过程进行分析.bean定义解析加载的简单时序图如下: bean定义的解析通过XmlBeanDefinitionReader来完成,在解析前先做一些准备工作:1.设置环

spring事务管理 TransactionProxyFactoryBean源码分析

J2EE,当然离不开事务,事务又当然少不了Spring声明式事务.spring声明式事务,很多码农门,应该和笔者一样,停留在使用上,及仅仅了解点原理.如:Spring事务管理原理"代理+AOP",再深入了解就不太清楚了.一直对声明式事务实现特别感兴趣,今天抽时间,剖析一下下. 1.准备 BeanFactory,及对象生成周期 AOP代理对象生成过程 1.1.BeanFactory 及生命周期 Factory class name 作用 ListableBeanFactory 枚举所有的

spring mvc 3.2源码分析

1,httpServlet public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { request = (HttpServletRequest) req; response = (HttpServletResponse) res

springAOP源码分析

SpringAop实现为动态代理进行实现的,实现方式有2种,JDK动态代理和CGlib动态代理先写一个AOP的案列加以说明配置文件代码为: <bean id="userDao" class="com.spring.aop.service.UserDaoImpl"/> <bean id="logger" class="com.spring.aop.log.Logger" /> <!-- 切面:切入点