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源码分析-Bean生命周期循环依赖和三级缓存 -> 正文阅读

[Java知识库]Spring源码分析-Bean生命周期循环依赖和三级缓存

Spring源码分析系列

Spring源码分析-启动流程浅析
Spring源码分析-BeanDefinition
Spring源码分析-Bean管理查找与注册(1)
Spring源码分析-Bean管理查找与注册(2)
Spring源码分析-Bean管理循环依赖和三级缓存
Spring源码分析-Bean生命周期概述
Spring源码分析-Bean生命周期createBean



前言

本篇博客将进一步分析bean管理内容,主要内容是循环依赖和三级缓存


一、循环依赖

1.1、什么是循环依赖

在这里插入图片描述
循环依赖:类A中有一个属性是类B,类B中有一个属性是类A,双方互相引用

1.2、怎么确定出现循环依赖

首先一个已完成初始化的bean是可以直接被其他bean进行引用,此时一定不会出现循环依赖,最终得出只有未初始化完成的bean才可能出现循环依赖
判断逻辑:
1)Order正常实例化,属性赋值时发现需要User对象,但是User对象不存在,则转到2)
2)User正常实例化,属性赋值时发现又需要Order对象,由于Order对象在ioc容器中不存在,又转向1)
至此就出现了循环依赖

1.3、如何解决循环依赖

考虑一个问题:当jvm实例化一个对象后,这个对象在被回收之前,内存地址会变吗?答案:当然不会变。这个就是解决循环依赖的关键:将未完成初始化的对象先缓存起来
在这里插入图片描述
上面这个种方式只是提供了一种解决思路,并不能解决所有场景,所以spring解决方式(三级缓存+缓存升级)如下图所示:
在这里插入图片描述

二、三级缓存

2.1、三级缓存概念

三级缓存听着高大尚,其实本质就是三个map,三级缓存定义在DefaultSingletonBeanRegistry.java,具体是:

/** Cache of singleton objects: bean name to bean instance. */
/** 第一级缓存,存放可用的成品Bean 例如:没有依赖的类对象,可直接放到map中 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. */
/** 第三级缓存,存的是Bean工厂对象,用以解决动态代理场景下的循环依赖 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
/** 第二级缓存,存放半成品的Bean,半成品的Bean是已创建对象,但是未注入属性和初始化。用以解决循环依赖 */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

三级缓存体现了一种设计模式:单一职责模式,具体说明:
1)一级缓存singletonObjects,key=bean的名字,value=bean实例对象,这个bean对象是完成了初始后的,是一个完整状态的bean
2)二级缓存,key,value与一级缓存是相同的,但是二级缓存中保存的是未初始化的bean,换句话说只进行了实例化,属性赋值还没有完成。另外二级缓存是为了解决循环依赖的,例如:ObjectA与ObjectB相互依赖,在创建ObjectA的时候,发现ObjectB还没有创建过,则先将ObjectA暂存在二级缓存中,转向创建ObjectB,所以二级缓存里面保存的是一个半成品对象(未完成初始化),不能解决AOP循环依赖问题
3)三级缓存,生成一个包装后的bean对象(本质代理对象),目的解决是AOP场景下的循环依赖问题,value是一个ObjectFactory,在代码中是一个lambda表达式

2.2、为什么需要三级缓存

我在网上搜了很多资料,并没有看到将这个知识点解释的很透彻的博客,所以我力争将其说的透彻一些。
先说一下我的观点:二级缓存,是可以AOP中循环依赖问题,完全不需要三级缓存。我知道我这个观点比较奇葩,和网上说的不一致,但是我会通过原理分析和代码来论证我的观点,如果有人有其他观点,可以留言进行讨论,我会逐一回复。

2.2.1、三级缓存保存内容

三级缓存,保存的是lambda表达式(函数式接口或者回调),代码如下:

//AbstractAutowireCapableBeanFactory.java doCreateBean
//向三级缓存中存lambda表达式
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    // synthetic为true代表BeanDefinition是合成的,通常aop场景下是true
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        //接口SmartInstantiationAwareBeanPostProcessor中getEarlyBeanReference方法,默认直接返回bean对象
        //如果配置文件或者注解中开启了AOP则在启动时候会注入 AbstractAutoProxyCreator
        //AbstractAutoProxyCreator类中的getEarlyBeanReference将会返回代理对象
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    // exposedObject要么是原始对象要么是代理对象
    return exposedObject;
}

我们从三级缓存中能获取到一个lambda表达式,这个表达式返回要么返回原始对象要么返回代理对象,这一点需要牢记。

2.2.2、三级缓存应用场景

场景1)属性赋值前,先将bean对象以lambda表示方式存储到三级缓存中,代码如上一小章节
场景2)在getBean(beanName, true)获取bean对象时,会查三级缓存,若三级缓存中存在bean对象,将bean对象放到二级缓存中且删除三级缓存中数据,代码如下:
在这里插入图片描述
思考一个问题:既然三级缓存中生成的对象(原生对象,代理对象),在缓存升级后存到二级中。ok,是不是在存三级的时候,我把对象创建出来,放到二级中,也是可行的?我认为是可行的,这样三级缓存就完全没有作用了。

2.2.2、代码验证

这里需要修改spring的源码,具体修改如下
修改1)注释掉Object getSingleton(String beanName, boolean allowEarlyReference) ,并增加自己写的方法:

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	Object singletonObject = this.singletonObjects.get(beanName); //一级缓存获取实例
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		singletonObject = this.earlySingletonObjects.get(beanName); //二级缓存
	}
	return singletonObject;
}

修改2)修改缓存定义访问权限,由private改成protected,如下:

/** Cache of early singleton objects: bean name to bean instance. */
/** 第二级缓存,存放半成品的Bean,半成品的Bean是已创建对象,但是未注入属性和初始化。用以解决循环依赖 */
protected final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

/** Set of registered singletons, containing the bean names in registration order. */
protected final Set<String> registeredSingletons = new LinkedHashSet<>(256);

修改3) 三级缓存注册的地方改成如下内容:

// AbstractAutowireCapableBeanFactory.java doCreateBean

//先放到 三级缓存 中 这个地方是lambda表达
//注释
//addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

/** add by xuxb 测试代码 需要将缓存声明private 改成 protected */
/** 这里直接获取对象并且注册到二级缓存中 */
Object targetBean = getEarlyBeanReference(beanName, mbd, bean);
earlySingletonObjects.put(beanName, targetBean); //添加二级缓存
registeredSingletons.add(beanName);

以上是spring源码的修改,下面就是验证过程,部分核心代码如下:

@Component
@Aspect
@EnableAspectJAutoProxy
public class MyAspect {

    @Before("execution(* com.worker.beans.*.*(..))")
    public void before() {
        System.out.println("before...");
    }

    @After("execution(* com.worker.beans.*.*(..))")
    public void after() {
        System.out.println("after...");
    }
}
@Component
public class Orders {
    @Autowired
    private User user; //互相依赖

    public Orders() {
        System.out.println("hello orders");
    }

    public void getOrders() {
        System.out.println("Orders is acquired");
    }
}
@Component
public class User {
    @Autowired
    private Orders orders; //互相依赖

    public User() {
        System.out.println("hello user");
    }

    public void getName() {
        System.out.println("User name is xuxb");
    }
}
public class WApp {
    public static void main(String[] args) {
        try {
            //ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
            ApplicationContext context = new AnnotationConfigApplicationContext("com.worker");
            User user = context.getBean(User.class);
            System.out.println(user);
            user.getName();
            System.out.println("------------------------");
            Orders orders = context.getBean(Orders.class);
            System.out.println(orders);
            orders.getOrders();
            //context.registerShutdownHook();
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

输出结果,是正确的:

> Task :spring-study-01:WApp.main()
hello orders
hello user
com.worker.beans.User@158a8276
before...
User name is xuxb
after...
------------------------
com.worker.beans.Orders@757277dc
before...
Orders is acquired
after...

BUILD SUCCESSFUL in 4s

通过上面的修改,二级缓存应该可以解决AOP循环依赖的问题,但是spring为什么要弄出一个三级缓存呢?
我这里说一下我的想法:spring应该是遵循了一种设计模式:单一职责模式。一级缓存保存的是可直接使用的对象,二级缓存保存的未初始化完成对象,三级缓存用于生成包装bean(代理对象)

2.3.三级缓存能解决所有的循环依赖吗

不能,有一种场景不能解决。那就是在构造方法中出现的循环依赖,具体如下:

@Component
public class User {
    public User(Orders orders) {//循环依赖
        System.out.println("hello user");
    }
    public void getName() {
        System.out.println("User name is xuxb");
    }
}
@Component
public class Orders {
    public Orders(User user) {//循环依赖
        System.out.println("hello orders");
    }
    public void getOrders() {
        System.out.println("Orders is acquired");
    }
}

三、总结

循环依赖是spring面试中常出现的问题,我这里通过理论和分析来进行验证,也许我的观点不正确,若有小伙伴有何疑义欢迎留言讨论。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-02-06 13:41:29  更:2022-02-06 13:41: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/24 9:55:29-

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