实验环境: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)都是是单例的。

那么,我修改一下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】的更多相关文章

  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部分-Spring是如何解决Bean循环依赖的【6】

    实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...

  4. Spring源码-IOC部分-容器简介【1】

    实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...

  5. Spring源码-IOC部分-容器初始化过程【2】

    实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...

  6. Spring源码-IOC部分-Xml Bean解析注册过程【3】

    实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...

  7. Spring源码-IOC部分-自定义IOC容器及Bean解析注册【4】

    实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...

  8. Spring源码-IOC部分-Bean实例化过程【5】

    实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...

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

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

随机推荐

  1. 食物链(poj1182)

    食物链 Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 57387   Accepted: 16781 Description ...

  2. Java学到什么程度可以面试工作?

    ​先说结论: 1 大多数公司,对于Java初级开发的要求是,会用Spring Boot+JPA做增删改查 2 所以零基础的Java小白,无需学太多的内容,只要掌握Spring Boot+JPA做增删改 ...

  3. Git reflog 引用日志使用详解

    本章节主要介绍 git reflog 命令. Git 使用一种称为引用日志或"reflogs"的机制来跟踪分支顶端的更新. 许多 Git 命令接受用于指定引用或"ref& ...

  4. C++ std-11 常用方法

    对多个值取最值 C++标准库提供了获取最大值和最小值的方法: int mi = std::min(x1, x2); int ma = std::max(x1, x2); 如果想获取超过两个数的最值呢? ...

  5. <学习opencv>过滤器和卷积

    /*=========================================================================*/ // 过滤器和卷积 /*========== ...

  6. CS5266参数|Capstone CS5266|CS5266应用方案

    随着目前手机.笔电和平板类产品都是用的Type-C接口,特别是苹果类的笔电和平板就只有一个Type-C接口,在很多工作.学习.娱乐中突显很多不方便的情况,别是需要一些其他的功能如:鼠标键盘接口USB2 ...

  7. Java初学者作业——简单程序根据用户输入的会员类型以及购物金额,判断是否能够享受活动优惠

    返回本章节 返回作业目录 需求说明: 超市周年庆举行购物满减活动,编写Java程序,根据用户输入的会员类型以及购物金额,判断是否能够享受活动优惠,会员类型的输入不限制大小写.具体获取规则:若为VIP会 ...

  8. minio文件上传与下载

    目录 一.minio简介 二.minio安装 一.java中使用 一.minio简介 MinIO 是在 GNU Affero 通用公共许可证 v3.0 下发布的高性能对象存储. 它是与 Amazon ...

  9. vim安装使用pathogen

    pathogen是什么? pathogen一般作为vim新手的第一个插件,用来统一管理vim插件包, (官方解释)非常容易的管理你的 'runtimepath' ,在实际项目中,vim-pathoge ...

  10. R语言基本数据对象之向量的主要运算

    在R语言里操作和接触的所有东西都称作对象(object).对象有很多种类 可以包含各种类型的数据.R 语言里所有的东西都被称为对象,R语言中常见的数据类型有几下几种,分别是字符型 (character ...