Spring源码-IOC部分-循环依赖-用实例证明去掉二级缓存会出现什么问题【7】

时间:2022-09-30 09:18:55
实验环境:spring-framework-5.0.2、jdk8、gradle4.3.1

上文 Spring源码-IOC部分-Spring是如何解决Bean循环依赖的【6】 讲到,如果不使用三级缓存(singletonObjects、earlySingletonObjects、singletonFactories),只使用两级缓存(singletonObjects、singletonFactories)的话,对于普通的bean没有影响,但对于AOP代理的bean会导致重复创建bean实例,违法了单例原则。

本文就用一个实际例子来证明一下。

1、定义一个注解

package beans;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAopAnnotation {
String value();
}

2、定义AOP,把切面切到@MyAopAnnotation上,这样的话@MyAopAnnotation标在谁上面,谁就是代理对象,比较灵活

package beans;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component; @Aspect
@Component
@EnableAspectJAutoProxy
public class MyAop { @Pointcut("@annotation(beans.MyAopAnnotation)")
public void pointCat() {
} @Before("pointCat()")
public void before(JoinPoint joinPoint) {
System.out.println("执行AOP before方法");
}
}

3、定义bean,我把@MyAopAnnotation配置在TestBean方面上,也就是说TestBean需要创建AOP代理。另外TestBean、User、User2之间相互注入

TestBean
package beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; @Component
public class TestBean { @Autowired
private User user; @Autowired
private User2 user2; @Autowired
private TestBean testBean; public User getUser() {
return user;
} public User2 getUser2() {
return user2;
} public TestBean getTestBean() {
return testBean;
} @MyAopAnnotation("")
public void hello() {
System.out.println("TestBean 执行 hello 方法 ");
}
}
User
package beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; @Component
public class User { @Autowired
private User2 user2; @Autowired
private TestBean testBean; public TestBean getTestBean() {
return testBean;
} public User2 getUser2() {
return user2;
} }
User2
package beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component; @Component
@DependsOn({"user"})
public class User2 { @Autowired
private User user; @Autowired
private TestBean testBean; public User getUser() {
return user;
} public TestBean getTestBean() {
return testBean;
} }

4、运行代码

public class Main {

	public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.scan("beans");
context.refresh(); TestBean testBean = (TestBean) context.getBean("testBean");
testBean.hello(); User user = context.getBean(User.class);
User2 user2 = context.getBean(User2.class);
System.out.println("user == user2.getUser() : " + (user == user2.getUser())); System.out.println("testBean == user.getTestBean() : " + (testBean == user.getTestBean()));
System.out.println("testBean == user2.getTestBean() : " + (testBean == user2.getTestBean()));
System.out.println("user.getTestBean() == user2.getTestBean() : " + (user.getTestBean() == user2.getTestBean())); context.close();
}

运行结果如下:可以看到AOP生效了,另外各个bean注入的属性是同一个bean,由此证明无论是代理bean(TestBean)还是普通bean(User、User2)都是是单例的。

Spring源码-IOC部分-循环依赖-用实例证明去掉二级缓存会出现什么问题【7】

那么,我修改一下getSingleton方法,把earlySingletonObjects屏蔽掉,也就是说bean在没有成为完整对象之前会一直从singletonFactory.getObject()获取早期bean实例。上文已经说过了,singletonFactory.getObject()是个工厂方法,每调用一次就会执行getEarlyBeanReference方法,而getEarlyBeanReference方法针对AOP代理的bean会创建一个新的代理对象,普通的bean直接返回。

修改后的getSingleton
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 1.【从一级缓存里面获取】从单例对象缓存中获取beanName对应的单例对象
Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 3.加锁进行操作
synchronized (this.singletonObjects) { // 4.【从二级缓存里面获取】从早期单例对象缓存中获取单例对象
// 之所称成为早期单例对象,是因为earlySingletonObjects里的对象的都是通过提前曝光的ObjectFactory创建出来的,还未进行属性填充等操作
// TODO 屏蔽掉earlySingletonObjects
//singletonObject = this.earlySingletonObjects.get(beanName); // 5.如果在早期单例对象缓存中也没有,并且允许创建早期单例对象引用
if (singletonObject == null && allowEarlyReference) { // 6.【从三级缓存里面获取】从单例工厂缓存中获取beanName的单例工厂
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) { // 7.如果存在单例对象工厂,则通过工厂创建一个单例对象
singletonObject = singletonFactory.getObject(); // TODO 屏蔽掉earlySingletonObjects
// 8.将通过单例对象工厂创建的单例对象,放到早期单例对象缓存中
//this.earlySingletonObjects.put(beanName, singletonObject);
// 9.移除该beanName对应的单例对象工厂,因为该单例工厂已经创建了一个实例对象,并且放到earlySingletonObjects缓存了,
// 因此,后续获取beanName的单例对象,可以通过earlySingletonObjects缓存拿到,不需要在用到该单例工厂
//this.singletonFactories.remove(beanName);
}
}
}
} return singletonObject;
}

我们再运行一下,发现user和user2.getUser()是同一个对象,因为user只是一个普通的bean,无论获取多少次都是它自己。而testBean呢,发现各自不同了,就是因为它是代理对象,没有了二级缓存earlySingletonObjects,直接从三级缓存singletonFactories里面拿,每拿一次就返回一个新的代理对象,所以此时testBean已经不是单例的了。

Spring源码-IOC部分-循环依赖-用实例证明去掉二级缓存会出现什么问题【7】

Spring源码-IOC部分-循环依赖-用实例证明去掉二级缓存会出现什么问题【7】的更多相关文章

  1. Spring源码分析之循环依赖及解决方案

    Spring源码分析之循环依赖及解决方案 往期文章: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostPro ...

  2. Spring源码--debug分析循环依赖--构造器注入

    目的:源码调试构造器注入,看看是怎么报错的. spring:5.2.3 jdk:1.8 一.准备 首先准备两个循环依赖的类:userService和roleServic <bean id=&qu ...

  3. Spring源码——IOC控制反转

    1.基础知识 Spring有两个核心功能,分别是ioc和aop,其中ioc是控制反转,aop是切面编程. 在ioc中,还有一个名次叫DI,也就是依赖注入.嗯,好像IOC和DI是指同一个,好像又感觉他俩 ...

  4. Spring 源码分析之 bean 依赖注入原理&lpar;注入属性&rpar;

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

  5. 零基础带你看Spring源码——IOC控制反转

    本章开始来学习下Spring的源码,看看Spring框架最核心.最常用的功能是怎么实现的. 网上介绍Spring,说源码的文章,大多数都是生搬硬推,都是直接看来的观点换个描述就放出来.这并不能说有问题 ...

  6. Spring源码解析-基于注解依赖注入

    在spring2.5版本提供了注解的依赖注入功能,可以减少对xml配置. 主要使用的是 AnnotationConfigApplicationContext: 一个注解配置上下文 AutowiredA ...

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

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

  8. 转 Spring源码剖析——核心IOC容器原理

    Spring源码剖析——核心IOC容器原理 2016年08月05日 15:06:16 阅读数:8312 标签: spring源码ioc编程bean 更多 个人分类: Java https://blog ...

  9. 再探循环依赖 &srarr; Spring 是如何判定原型循环依赖和构造方法循环依赖的?

    开心一刻 一天,侄子和我哥聊天,我坐在旁边听着 侄子:爸爸,你爱我妈妈吗? 哥:这话说的,不爱能有你吗? 侄子:确定有我不是因为荷尔蒙吗? 哥:因为什么荷尔蒙,因为爱情! 侄子:那我妈花点钱,你咋老说 ...

  10. Ioc容器依赖注入-Spring 源码系列(2)

    Ioc容器依赖注入-Spring 源码系列(2) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostPr ...

随机推荐

  1. Hadoop中MR程序的几种提交运行模式

    本地模型运行 1:在windows的eclipse里面直接运行main方法,就会将job提交给本地执行器localjobrunner执行       ----输入输出数据可以放在本地路径下(c:/wc ...

  2. 使用Gson转换json数据为Java对象的一个例子

    记录工作中碰到的一个内容. 原料是微信平台的一个接口json数据. { "errcode" : 0, "errmsg" : "ok", &q ...

  3. sed delete

    sed -i '1d' a.txt删首行 sed -i '$d' b.txt删尾行 sed -i 's/[ ]//g' c.txt删空格 sed -i '/^$/d' d.txt删空行 sed -i ...

  4. win8内置管理员用户无法激活此应用

    在运行中输入:“gpedit.msc”,就会启动组策略编辑器, 计算机配置 --> Windows设置 --> 安全设置 --> 本地策略 -->  安全选项  ::::  用 ...

  5. 【codevs1257】 打砖块

    http://codevs.cn/problem/1257/ (题目链接) 题意 在等腰三角形上打砖块,总共有m发炮弹,每块砖有一个权值,求打出的最大权值 Solution 今天考试题,考场上的2个小 ...

  6. CSS3 弹性盒布局模型&lpar;转&rpar;

    简介 引入弹性盒布局模型的目的是提供一种更加有效的方式来对一个容器中的条目进行排列. 对齐和分配空白空间.即便容器中条目的尺寸未知或是动态变化的,弹性盒布局模型也能正常的工作.在该布局模型中,容器会根 ...

  7. Java 字符转码之UTF-8转为GBK&sol;GB2312

    java跟python类似的做法,在java中字符串的编码是java修改过的一种Unicode编码,所以看到java中的字符串,心理要默念这个东西是java修改过的一种Unicode编码的编码. pa ...

  8. decodeURIComponent

    var s = '%%' try { s = decodeURIComponent(s) } catch(e) { console.log(e) } console.log(s)

  9. 消息队列-ActiveMQ

    1 业务需求描述 举例描述: 再警情通报的业务时通过发送消息界面可以选择 警情联络,和船情通报两种消息 发送方式可分为 一对一发送:部门对部门.个人对个人 一对多发送:部门对多部门.个人对多人 2 功 ...

  10. msf

    show exploit show payload msf使用数据库加快搜索,不然每次都等半天 service postgresql startmsfdb reinitmsf > db_rebu ...