【实践向】当移除了三级缓存……

有目标就不怕路远。年轻人.无论你现在身在何方.重要的是你将要向何处去。只有明确的目标才能助你成功。没有目标的航船.任何方向的风对他来说都是逆风。因此,再遥远的旅程,只要有目标.就不怕路远。没有目标,哪来的劲头?一车尔尼雷夫斯基

导读:本篇文章讲解 【实践向】当移除了三级缓存……,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

【Spring源码系列- IOC】

1

【Spring源码】0.安装Gradle环境

2

【Spring源码】1.下载与编译_pom relocation to an other version number is not f

3

【Spring源码】2.试个水先~Debug找到传说中的三级缓存(图解向,堆图预警)

4

【Spring源码】3. xml文件如何转换成BeanDefinition(主要涉及prepareRefresh()+ obtainFreshBeanFactory()两个函数,图解向,堆图预警)_spring xml转bean

5

【Spring源码】4. 自己搞个标签?~自定义标签保姆级全过程(图解向,堆图预警)

6

【Spring源码】5.spring的bean工厂准备工作(prepareBeanFactory(beanFactory)

7

【Spring源码】6. Spring扩展自定义属性编辑器保姆级教程

8

【Spring源码】7. 如何添加自定义的BeanFactoryPostProcessor

9

【Spring源码】8. 捋下invokeBeanFactoryPostProcessors()主要处理流程

10

【Spring源码】9. 超级重要的ConfigurationClassPostProcessor

11

【Spring源码】10. 递归调用的processConfigurationClass()方法

12

【Spring源码】11. 我是注解类不?checkConfigurationClassCandidate()注解类判断方法详解

13

【Spring源码】12. 注册bean处理器registerBeanPostProcessors()

14

【Spring源码】13. 国际化处理initMessageSource()源码解析

【补充内容】【保姆级】SpringBoot项目中的i18n国际化

15

【Spring源码】14. 消息多播器(观察者模式)

【补充内容】【保姆级示例向】观察者模式

16

【Spring源码】15. Bean的创建过程(1.概述篇)

17

【Spring源码】16. Bean的创建过程(2)

18

【Spring源码】17.创建Bean这篇认真的@(・●・)@

【补充内容】

【保姆级·创建对象】如何通过Supplier创建对象

【保姆级·创建对象】如何通过factory-method创建对象

【保姆级·创建对象】如何利用resolveBeforeInstantiation()在预处理阶段返回一个Bean的实例对象

19

【Spring源码】18. factory-method创建对象关键函数详解:instantiateUsingFactoryMethod()

20

【Spring源码】19. 没合适的构造器?找determineCandidateConstructors()!

21

【Spring源码】20. MergedBeanDefinitionPostProcessor修改/合并bean定义

【补充内容】

【保姆级】@PostConstruct & @PreDestroy使用示例

【Spring源码】AutowiredAnnotationBeanPostProcessor.postProcessMergedBeanDefinition()详解

【Spring源码】CommonAnnotationBeanPostProcessor.postProcessMergedBeanDefinition()详解

22

【Spring源码】21. 初探循环依赖

【补充内容】

【保姆级】手把手Debug循环依赖的整体流程

【实践向】当移除了三级缓存……

【分析向】没有三级缓存会导致什么?

【Spring源码】插播一个创建代理对象的wrapIfNecessary()方法

23

【Spring源码】22. 属性填充populateBean()详解

【补充内容】

【Spring源码】自动注入·名称:autowireByName()详解

【Spring源码】自动注入·类型:autowireByType()详解

【Spring源码】属性值的解析与赋值:populateBean().applyPropertyValues()

【保姆级】超超超简单的自定义注解实现@Autowired同款功能

24

【Spring源码】23. 执行初始化逻辑:initializeBean()

本文会手把手带你一起把使用二级缓存替换三级缓存,看下移除了三级缓存,只有二级缓存会出什么问题,用实践回答那个被问了无数次的“为什么要有三级缓存?”以及“二级缓存解决不了循环依赖问题吗?”等类似问题( ̄∇ ̄)

既然要用二级缓存替换三级缓存,我们大致回忆下(b_d)都在哪里用到了三级缓存?

自问自答:主要是两个地方:

  1. 获取Bean的时候,Spring在获取Bean的时候,会先从一级缓存中找,找不到会去二级缓存中找,再找不到会到三级缓存

  1. 创建完Bean的实例对象后,会将当前bean以及它的工厂对象添加进三级缓存

那我们分别将这两个地方中涉及到三级缓存的逻辑修改下

移除三级缓存

移除向三级缓存中查找Bean的逻辑

先来到Spring中使用三级缓存的位置,就是DefaultSingletonBeanRegistry类中,不知道如何找的客官可以参考

【Spring源码】2.试个水先~Debug找到传说中的三级缓存

DefaultSingletonBeanRegistry类中有个getSingleton()的方法,里面是从缓存中获取Bean的逻辑

【实践向】当移除了三级缓存……

我们把这个函数整个复制下,把原来的注释掉,在复制的函数中进行修改

注意尽量不要直接在原函数上进行修改,因为后面我们还要再改回来呢(˶‾᷄ ⁻̫ ‾᷅˵)

改成这样

【实践向】当移除了三级缓存……

@Nullable
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);
         return singletonObject;
      }   }return singletonObject != null ? singletonObject : null;
}

移除向三级缓存中添加Bean的逻辑

了解Bean的创建流程的客官还记不记得有个叫做addSingletonFactory()的方法(可以参考文章【Spring源码】16. Bean的创建过程(2)

这个方法就在DefaultSingletonBeanRegistry类中

【实践向】当移除了三级缓存……

当前bean以及它的工厂对象就是在这个方法中被放入三级缓存的

我们想要移除三级缓存,只保留二级缓存,就需要把这个方法中对于三级缓存的操作修改为只保留二级缓存的操作

单击方法回到它位于doCreateBean()方法中的调用位置

addSingletonFactory()方法中操作了三个集合:singletonFactories(三级缓存)、earlySingletonObjects(二级缓存)、registeredSingletons(注册bean集合)

去除三级缓存,我们只添加对earlySingletonObjects(二级缓存)、registeredSingletons(注册bean集合)这两个集合的操作

【实践向】当移除了三级缓存……

由于这两个集合是类DefaultSingletonBeanRegistry私有的(private),因此想要在别的类中调用需要修改成公共的(public)

【实践向】当移除了三级缓存……

执行测试

修改完毕,我们执行下上篇文章中的测试代码看是否会报错(想要自己亲自执行下的可以直接去为文章

【保姆级】手把手Debug循环依赖的整体流程 中复制下)

【实践向】当移除了三级缓存……

嘿嘿没问题对不对,所以其实单纯的循环依赖,二级缓存就足够了,那么为什么非要再搞一个三级缓存呢?

又自问自答:因为代理

引入代理

新建测试类

代理类

我们再新建一个日志类(AOP必然产生动态代理)

【实践向】当移除了三级缓存……

package com.aqin.custom.circulate;

/** * @Description* @Authoraqin1012 AQin. * @Date11/23/22 1:19 PM * @Version1.0 */
public class MyLogger {public void beforeMethod() {System.out.println("beforeMethod");
   }public void afterMethod() {System.out.println("afterMethod");
   }}

配置类

在原有配置基础上,添加日志类以及AOP相关配置

【实践向】当移除了三级缓存……

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"><bean id="beanA" class="com.aqin.custom.circulate.BeanA"><property name="beanB" ref="beanB"></property></bean><bean id="beanB" class="com.aqin.custom.circulate.BeanB"><property name="beanA" ref="beanA"></property></bean><bean id="myLogger" class="com.aqin.custom.circulate.MyLogger"></bean><aop:config><aop:aspect id="myLogger" ref="myLogger"><aop:pointcut id="method" expression="execution(* com.aqin.custom.circulate.*.*(..))"/><aop:before method="beforeMethod" pointcut-ref="method"/><aop:after method="afterMethod" pointcut-ref="method"/></aop:aspect></aop:config></beans>

添加依赖

在module下的build.gradle文件中添加AOP的依赖

【实践向】当移除了三级缓存……

不然会报下面👇的异常

【实践向】当移除了三级缓存……

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.aop.aspectj.AspectJPointcutAdvisor#0': Cannot create inner bean '(inner bean)#3a883ce7' of type [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice] while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#3a883ce7': Resolution of declared constructors on bean Class [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice] from ClassLoader [sun.misc.Launcher$AppClassLoader@73d16e93] failed; nested exception is java.lang.NoClassDefFoundError: org/aspectj/lang/JoinPoint

启动类

【实践向】当移除了三级缓存……

public class Test {public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("circulate.xml");
      BeanA beanA = applicationContext.getBean(BeanA.class);
      System.out.println(beanA);
      System.out.println(beanA.getBeanB());
   }}

启动类添加一行方法的调用,用于测试AOP的执行效果

启动执行

【实践向】当移除了三级缓存……

果然、报错了=[,,_,,]:3

Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Bean with name 'beanA' has been injected into other beans [beanB] 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 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.

点击上图中黄色框框框住的类,会定位到报错位置

【实践向】当移除了三级缓存……

所以是在执行doCreateBean()方法时,报了这个异常

我们大致翻一下(分析在最后一部分)

在上下文初始化过程中遇到的异常 – 取消刷新尝试:org.springframework.beans.factory.BeanCurrentlyInCreationException。创建名称为’beanA’的Bean时出错。名字为’beanA’的Bean已经作为循环引用的一部分被注入到其他Bean[beanB]的原始版本中,但最终被包装了。这意味着上述其他Bean没有使用Bean的最终版本。这通常是过于急切的类型匹配的结果–例如,考虑使用’getBeanNamesForType’并关闭’allowEagerInit’标志。

将代码改回去

【实践向】当移除了三级缓存……

执行成功(。・ω・。)ノ

分析与总结

上面的实践过程直观的证明了一个我们早就知道的结论(˶‾᷄ ⁻̫ ‾᷅˵):

如果不存在代理对象,二级缓存就可以解决循环依赖性的问题,但是当存在代理对象的时候,二级缓存则无法完全解决循环依赖,需要引入三级缓存

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/135371.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!