Spring源码-IOC部分-循环依赖-用实例证明去掉二级缓存会出现什么问题【7】
实验环境:spring-framework-5.0.2、jdk8、gradle4.3.1
- Spring源码-IOC部分-容器简介【1】
- Spring源码-IOC部分-容器初始化过程【2】
- Spring源码-IOC部分-Xml Bean解析注册过程【3】
- Spring源码-IOC部分-自定义IOC容器及Bean解析注册【4】
- Spring源码-IOC部分-Bean实例化过程【5】
- Spring源码-IOC部分-Spring是如何解决Bean循环依赖的【6】
- Spring源码-IOC部分-循环依赖-用实例证明去掉二级缓存会出现什么问题【7】
上文 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)都是是单例的。

那么,我修改一下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源码分析之循环依赖及解决方案
Spring源码分析之循环依赖及解决方案 往期文章: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostPro ...
- Spring源码--debug分析循环依赖--构造器注入
目的:源码调试构造器注入,看看是怎么报错的. spring:5.2.3 jdk:1.8 一.准备 首先准备两个循环依赖的类:userService和roleServic <bean id=&qu ...
- Spring源码-IOC部分-Spring是如何解决Bean循环依赖的【6】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码-IOC部分-容器简介【1】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码-IOC部分-容器初始化过程【2】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码-IOC部分-Xml Bean解析注册过程【3】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码-IOC部分-自定义IOC容器及Bean解析注册【4】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码-IOC部分-Bean实例化过程【5】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码——IOC控制反转
1.基础知识 Spring有两个核心功能,分别是ioc和aop,其中ioc是控制反转,aop是切面编程. 在ioc中,还有一个名次叫DI,也就是依赖注入.嗯,好像IOC和DI是指同一个,好像又感觉他俩 ...
随机推荐
- Buy Tickets(poj2828)
Buy Tickets Time Limit: 4000MS Memory Limit: 65536K Total Submissions: 17416 Accepted: 8646 Desc ...
- 【漏洞复现】ThinkAdmin v5和v6 未授权列目录任意文件读取(CVE-2020-25540)
ThinkAdmin v5和v6 未授权列目录/任意文件读取(CVE-2020-25540) 漏洞简介 ThinkAdmin是一套基于ThinkPHP框架的通用后台管理系统.ThinkAdmin v6 ...
- 通过Rancher Desktop在桌面上运行K8s
Rancher 发行的操作系统新选择:Rancher Desktop for Windows,它可以帮助你在Windows桌面上管理Kubernetes和容器.当然他当然会支持Linux,Mac的. ...
- Python学习笔记:利用pd.get_dummies实现哑变量编码
一.理论介绍 虚拟变量(dummy variable)也叫哑变量,是一种将多分类变量转换为二分变量的一种形式. 如果多分类变量有k个类别,则可以转化为k-1个二分变量. 需要有一个参照的类别. 在非线 ...
- PDF的信息提取的问题
PDF对企业应用来说是刚需. 然而PDF显然不是一种对机器友好的格式,它只是对人类友好,就是说方便阅读打印,但让程序去提取其中的内容却很难.下面简单说说为什么是这样. 以前还读书的时候(20+ ...
- android studio 获取 SHA1 值
1. 生成密钥文件 2.找到控制台 输入指令 cd c: cd C:\Users\[当前登录的用户文件夹]\.android 如 cd C:\Users\cenxi\.android 就是这里 然后输 ...
- [ Flask ] myblog_flask问题集(RESTfull风格)
VUE问题 前端VUE怎么捕获所有404NOT FOUND的路由呢? [ 解决方案 ] vue-router路由守卫,参考文档:动态路由匹配 对于路由.../edit/<id>,自己能编辑 ...
- BugKu CTF(杂项篇MISC)-贝斯手
打开是以下内容 先看一下给了哪些提示 1.介绍 没了?不,拉到最底下还有 2.女神剧照 密码我4不会告诉你的,除非你知道我的女神是哪一年出生的(细品) 大致已经明白了,四位数密码,出生年份 文件是以下 ...
- 【Java】二分法查找
二分法查找 前提:所要查找的数组必须有序 public class Dichotomy { public static void main(String[] args) { int[] array = ...
- Flowable实战(八)BPMN2.0 任务
任务是流程中最重要的组成部分.Flowable提供了多种任务类型,以满足实际需求. 常用任务类型有: 用户任务 Java Service任务 脚本任务 业务规则任务 执行监听器 任务监听器 多 ...