首页 > 其他分享 >说透 Spring循环依赖和3级缓存

说透 Spring循环依赖和3级缓存

时间:2023-05-04 13:03:20浏览次数:35  
标签:缓存 Spring beanName getObject bean 依赖 singletonObject


Bean加载的总体流程

FactoryBean的使用

地位:FactoryBean接口是Spring重要的拓展接口

用途:用于复杂的初始化 或者 框架集成

使用:

自定义类继承接口FactoryBean

原理:spring的getBean方法会回调FactoryBean的getObject方法

缓存中获取单例bean

说明下3级缓存指的是什么:

map的名称

完整性

几级缓存

singletonObjects

完整的bean(就是我们最终使用的)

第一级缓存

earlySingletonObjects

半完整的bean(循环依赖时会出现)

第二级缓存

singletonFactories

工厂bean(使用FactoryBean接口会出现)

第三级缓存

从3级缓存中获取bean,原则是第一级没有去第二级,第二级没有去第三极查找。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 从第一级缓存中查找
    Object 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 lock
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    // 加锁之后需要重新检查 第一级缓存、第二级缓存(保证多线程下的原子性)
                    if (singletonObject == null) {
                        // 从第三极缓存中查找
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {

                            // 调用工厂方法【1、工厂模式下会调用;2、循环依赖下会调用】
                            singletonObject = singletonFactory.getObject();

                            this.earlySingletonObjects.put(beanName, singletonObject);
                            // 从singletonFactories中remove掉这个ObjectFactory
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

从bean的实例中获取对象

ObjectFactory 与 FactoryBean接口的比较

相同点:都有getObject方法

不同点:作用完全不相同。

ObjectFactory

FactoryBean

相同点

有getObject方法

有getObject方法

不同点

用于“第三级缓存”,即singletonFactories

1、用于复杂bean的初始化2、用于框架集成(如mybatis与spring)

不同点体现的代码:

  • ObjectFactory

AbstractAutowireCapableBeanFactory#doCreateBean方法的代码片段(功能:加入第三极缓存)

// 默认下每个bean都会进入这个方法
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

DefaultSingletonBeanRegistry#getSingleton方法代码片段(功能:查找3级缓存)

// 从第三极缓存中查找
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {

    // 调用工厂方法【1、工厂模式下会调用;2、循环依赖下会调用】
    singletonObject = singletonFactory.getObject();

    this.earlySingletonObjects.put(beanName, singletonObject);
    // 从singletonFactories中remove掉这个ObjectFactory
    this.singletonFactories.remove(beanName);
}
  • FactoryBean

FactoryBean起作用的调用链路:

getBean —> AbstractBeanFactory#getObjectForBeanInstance —> FactoryBeanRegistrySupport#getObjectFromFactoryBean —> doGetObjectFromFactoryBean

// 代码片段

private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException {
    // 执行FactoryBean的getObject方法。
    /**
     * 注意:跟函数式接口ObjectFactory的定义非常相似(在getBean方法中出现,getObject的作用是定义模板方法中的特殊操作)
     * [T getObject() throws BeansException;] 与 [T getObject() throws Exception;]
     */
    Object object = factory.getObject();
    return object;
}

获取单例

描述的DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>)方法

1、检查缓存是否已经加载过

2、如没有加载,则记录beanName正在加载的状态

3、加载单例前记录加载状态

4、通过调用参数传入的ObjectFactory的getObject方法完成实例化bean

5、加载单例后的处理方法调用

6、将结果记录至缓存并删除加载bean过程中的所记录的各种辅助状态。

准备创建Bean

循环依赖

要理解spring是如何解决循环依赖的,我们需要先了解我们吧什么定义循环依赖。最简单的,bean1中有一个属性prop2,bean2中有一个属性prop1,prop2指向bean2,prop1指向bean1,这就是一个”最简单“的循环依赖的例子。

上面的例子太简单了,不能满足我们研究spring中3级缓存如何在循环依赖中的作用,所以,我们必须要把例子复杂起来一些。

测试环境准备

1、首先,我们把测试的代码贴出来


Bean_1.java、Bean_2.java、Bean_3.java文件:

@Data
public class Bean_1 {
    private Bean_2 prop_2;
    private Bean_3 prop_3;
}

@Data
public class Bean_2 {
    private Bean_1 prop_1;
}

@Data
public class Bean_3 {
    private Bean_1 prop_1;
}

application.xml文件:

<bean id="bean_1"
        class="com.firefish.springsourcecodedeepanalysis.cycle.setter.Bean_1"
        scope="singleton">
        <property name="prop_2" ref="bean_2"></property>
        <property name="prop_3" ref="bean_3"></property>
    </bean>

    <bean id="bean_2"
        class="com.firefish.springsourcecodedeepanalysis.cycle.setter.Bean_2"
        scope="singleton">
        <property name="prop_1" ref="bean_1"></property>
    </bean>

    <bean id="bean_3"
        class="com.firefish.springsourcecodedeepanalysis.cycle.setter.Bean_3"
        scope="singleton">
        <property name="prop_1" ref="bean_1"></property>
    </bean>

Main.java文件:

public class Main {
    public static void main(String[] args) {
        XmlBeanFactory context = new XmlBeanFactory(new ClassPathResource("com/firefish/springsourcecodedeepanalysis/cycle/setter/application.xml"));
        System.out.println(context.getBean("bean_3"));
    }
}

2、然后,我们把bean的关系也贴出来


3、到这里其实我们就具备了研究3级缓存和循环依赖的条件了,只要运行Main.java。

bean生命周期中关于循环依赖的介绍

循环依赖的解决与bean的生命周期息息相关,我们不得不介绍下与循环依赖关联的一些点。

下面是bean生命周期与循环依赖关系密切的流程:

1、从3层级缓存中查找bean(getSingleton方法)

2、doCreateBean 之前记录"正在加载bean"(beforeSingletonCreation方法)

3、实例化bean(createBeanInstance方法调用构造器)

4、加入到第三级缓存(addSingletonFactory方法)

5、属性填充、初始化、后置处理器(重点是属性填充)

6、结束beanA的创建,移除bean的创建状态

画图演示存在循环依赖下,bean在3级缓存中的迁移过程

以上面的代码为实例,在循环依赖下,下面将用图片的形式展示bean_1、bean_2、bean_3在bean生命周期执行过程中如何通过3级缓存解决循环依赖问题。

1、从3层级缓冲中查找bean_3,发现找不到,则创建bean_3;先记录bean_3正在创建中的状态,实例化bean_3,把bean_3加入第三级缓存中


2、对bean_3进行属性填充,发现依赖prop_1,进而去创建bean_1

3、从3层级缓冲中查找bean_1,发现找不到,则创建bean_1;先记录bean_1正在创建中的状态,实例化bean_1,把bean_1加入第三级缓存中

说透 Spring循环依赖和3级缓存_加载

4、对bean_1进行属性填充,发现依赖prop_2、prop_3;依据属性填充的先后顺序,先getBean_2,后getBean_3;进而就去创建bean_2

5、从3层级缓冲中查找bean_2,发现找不到,则创建bean_2;先记录bean_2正在创建中的状态,实例化bean_2,把bean_2加入第三级缓存中

说透 Spring循环依赖和3级缓存_三级缓存_02

6、对bean_2进行属性填充,发现依赖prop_1;进而去getBean_1

7、发现bean_1已经在创建中了,从3级缓冲的第三级缓存中可以找到bean_1了,调用bean_1的getObject方法(这里完成应用SmartInstantiationAwareBeanPostProcessor接口处理器来增强原始bean的功能。aop就是通过这个实现的)。从这里开始似乎开始循环依赖的结束了…。

8、把可能增强后的bean_1加入到第二级缓存中(为什么???),现在的情况如下图了。

说透 Spring循环依赖和3级缓存_spring_03

9、目前为止,完成了bean_2的属性填充,然后它的初始化等等流程…,最后完成了bean_2的完整创建,它被移动到第一级缓存供用户使用。

说透 Spring循环依赖和3级缓存_spring_04

10、接着步骤4,bean_1的属性prop_3还没有完成,现在进行bean3获取,即getBean_3

11、发现bean_3已经在创建中了,从3级缓冲的第三级缓存中可以找到bean_3了,调用了bean_3的getObject方法,状态如下了。

说透 Spring循环依赖和3级缓存_缓存_05

12、到这里,完成了bean_1的prop_2、prop_3的属性填充,以为着bean_1也就完整了创建,状态如下:

说透 Spring循环依赖和3级缓存_加载_06

13、最后完整了bean_3的创建

说透 Spring循环依赖和3级缓存_三级缓存_07

过程的状态图都演示完了,下面总结下各种map的作用吧。(多debug几遍源码可能更好理解)

几种map的作用

  • singletonsCurrentlyInCreation(记录了那些bean正在创建中)
  • singletonObjects(完整的bean的缓存)
  • earlySingletonObjects

为什么需要earlySingletonObjects的缓存???

1、如果没有它,当多次依赖bean(如上文中的bean_1)将不得不从第三级缓存的getObject方法中重新创建新的bean,这就违反了”单例bean“;

2、对于这种中间状态的bean(如上文中的bean_1),总要有一个地方吧它缓存起来

总结:earlySingletonObjects的作用就是防止多次创建bean,也就是作为”半初始化“单例bean的一个中间缓存。

  • singletonFactories

这个只有一个作用:应用 SmartInstantiationAwareBeanPostProcessor 接口完整bean的功能增强,如aop功能

总结

可以重点了解下循环依赖下,3个bean状态在3级缓存中的迁移情况,可以debug2遍看看;

了解后就不难理解这几种map的作用了。下面贴几张总结性的一些图,或许有用。

说透 Spring循环依赖和3级缓存_加载_08

说透 Spring循环依赖和3级缓存_加载_09


标签:缓存,Spring,beanName,getObject,bean,依赖,singletonObject
From: https://blog.51cto.com/u_16096603/6242635

相关文章

  • Spring的3大核心拓展点
    文章结构Spring的3大拓展点BeanFactory、BeanFactoryPostProcessor、BeanPostProcessor、FactoryBean的区别Spring的3大拓展点了解过Spring的同学可能会知道Spring有3大重要拓展接口,之所以说是3个而不是4个不是空穴来风,有官方文档说的.这3个重要的接口就是:BeanPostProcessor(用途......
  • SmartInitializingSingleton 特殊的Spring接口
    SmartInitializingSingleton接口是ApplicationContext对BeanFactory的增强功能。文章目录什么是SmartInitializingSingleton接口在什么时候起作用有什么用途什么是SmartInitializingSingleton接口SmartInitializingSingleton接口是ApplicationContext对BeanFactory的增强功能可......
  • Spring Boot核心原理《一》Spring Boot的启动流程
    文章结构1.容器启动入口2.初始化SpringApplication2.1构造器2.1.1deduceWebApplicationType2.2.2createApplicationContext3.run方法3.1prepareContext方法(重点)3.2refreshContext方法(重点)本文以SpringBoot版本2.0.2.RELEASE为例介绍1.容器启动入口首先从S......
  • Deltix Round, Spring 2021 (open for everyone, rated, Div. 1 + Div. 2)
    好久没发博客了,发一篇。A求出每个\(0\)与往前/往后最近的\(1\)的距离即可。时间复杂度\(\mathcal{O}(n)\)。B\((x,y)\to(x+y,y)\to(x+y,-x)\to(y,-x)\to(y-x,-x)\to(y-x,-y)\to(-x,-y)\)。时间复杂度\(\mathcal{O}(n)\)。C开个栈模拟......
  • SpringBoot 集成 Shiro 简单教程
    1.前言 ApacheShiro是一个功能强大且易于使用的Java安全框架,提供了认证,授权,加密,和会话管理。Shiro有三大核心组件:Subject: 即当前用户,在权限管理的应用程序里往往需要知道谁能够操作什么,谁拥有操作该程序的权利,shiro中则需要通过Subject来提供基础的当前用户信息,Sub......
  • Spring AOP官方文档学习笔记(三)之基于xml的Spring AOP
    1.声明schema,导入命名空间(1)如果我们想要使用基于xml的springaop,那么,第一步,我们需要在xml配置文件中声明springaopschema,导入命名空间,如下这是一个标准的模板<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmln......
  • SpringBoot项目部署在外置Tomcat正常启动,但项目没有被加载的问题
    最近打算部署个SpringBoot项目到外置Tomcat运行,但是发现tomcat启动成功,访问却一直404,刚开始以为是Tomcat的问题,就一直在改Tomcat配置。最后发现tomcat启动时根本就没加载到项目,因为控制台没有打印"SpringBoot"的项目标志经过一番百度查找,最后发现是因为项目启动类没有继承Spring......
  • SpringMVC03_校验和拦截器
    以下代码全过程在上篇一、SpringMVC校验​ 举一个简单的例子,在登陆时我们要检验用户名是否输入、密码是否合法。(一)引入依赖框架​ 在Spring-MVC中我们需要添加Hibernate的Validator检验框架,注意下面的版本号,615Final对应的应该是importjavax.validation.constraints......
  • SpringSecurity简介
    ------------恢复内容开始------------SpringSecurity简介SpringSecurity是spring家族中的一个安全框架,相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富一般来说中大型的项目都是使用springsecurity来做安全框架,小项目有Shiro的比较多,因为相比与Spri......
  • 从Spring源码分析@Autowired依赖注入实现原理
    在平常项目开发中,使用@Autowired注解进行字段注入很常用,本篇就通过Spring源码,重点分析这种方式实现依赖注入的过程。本篇Spring源码版本为5.1.7.RELEASE。在源码中,关键类是AbstractAutowireCapableBeanFactory,这个类继承AbstractBeanFactory,所以在Spring上下文启动......