Spring中的三级缓存使用及说明

 更新时间:2025年11月18日 09:41:36   作者:chen_179  
文章介绍了Spring框架中循环依赖的问题,包括循环依赖的定义、常见场景和解决方法,构造器注入的循环依赖无法解决,而field属性注入(setter方法注入)循环依赖可以解决,Spring通过“三级缓存”机制来解决循环依赖问题,其中singletonFactories是关键

常见问题

循环依赖

循环依赖:就是 N 个类循环(嵌套)使用。

简单来说,就是多个 Bean 之间互相依赖或引用到对方,最终形成了 闭环

循环依赖的关系可以表示成下图:

上图中的 A,B,C 代表三个不同的对象,上图表示了三者的引用关系。

循环依赖不仅发生在多个类中,还可能发生在本类中,即 N = 1,这种事极限情况的循环依赖:自己依赖自己

注意:这里指的循环依赖不是方法之间的循环调用,而是对象的相互依赖关系。(因为方法之间的循环调用有出口也能正常运行)

代码示例

public class Main {

    public static void main(String[] args) throws Exception {
        System.out.println(new A());
    }

}

class A {
    public A() {
        new B();
    }
}

class B {
    public B() {
        new A();
    }
}

运行结果

Exception in thread "main" java.lang.StackOverflowError

从上面代码可以看到,若构造函数之间发生循环依赖(A的构造方法中依赖B,B的构造方法中依赖A),程序会在运行时一直循环调用最终导致内存溢出

Spring 中三大循环依赖场景 演示

1.构造器注入循环依赖

@Service
public class A {
    public A(B b) {
    }
}
@Service
public class B {
    public B(A a) {
    }
}

运行结果

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)

构造器注入构成的循环依赖是无法解决的!只能抛出

BeanCurrentlyInCreationException异常表示循环依赖。

错误原因
根本原因是 Spring 解决循环依赖依靠的是 Bean 的 中间态 这个概念,而中间态指的是 已经实例化,但未初始化的状态。而构造器负责的是实例化,故无法解决构造器循环依赖。

2.field属性注入(setter方法注入)循环依赖

@Service
public class A {
    @Autowired
    private B b;
}

@Service
public class B {
    @Autowired
    private A a;
}

最常用的依赖注入方式,不会出现循环依赖问题(省略 setter 方式的注入实现代码,类似)

3.prototype field 属性注入循环依赖

prototype 在平时使用情况较少,仍需重视

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class A {
    @Autowired
    private B b;
}

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class B {
    @Autowired
    private A a;
}

此处代码在运行时不会报错,因为非单例Bean默认不会初始化,而是使用时才会初始化

手动 getBean() 或者在一个单例 Bean 内 @Autowired A 或 B 就会出错

// 在单例Bean内注入
    @Autowired
    private A a;

注入运行结果

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'mytest.TestSpringBean': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a': Unsatisfied dependency expressed through field 'b'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:596)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374)

错误解决方案

使用 @Lazy:@Lazy 只是延迟初始化,当真正需要使用到开始初始化时,依旧会出现上面的异常。

Spring 循环依赖小结

1.不能解决的情况:

  • 构造器注入循环依赖
  • prototype filed 属性注入循环依赖

2.能够解决的情况:

  • field 属性注入(setter方法注入)循环依赖

“三级缓存”意义

Spring 解决循环依赖原理分析

  • 当获得对象的引用时,对象的属性是可以延迟设置的。但是构造器必须是在获取引用之前,引用由构造器生成,不生成没东西引用。
  • Spring 创建 Bean 流程

创建 Bean 的三个核心方法

  • createBeanInstance:例化,即调用对象的构造方法实例化对象
  • populateBean:填充属性,主要对 bean 的依赖属性注入(@Autowired)
  • initializeBean:回到一些如initMethod,InitalizingBean等方法

由初始化 单例Bean 的流程图可以看出,循环依赖主要发生在第二步(populateBean),也就是 field属性注入的处理

Spring 容器的“三级缓存”

  • Spring 容器的整个生命周期中,单例Bean对象是唯一的。即可以使用缓存来加速访问
  • Spring 源码中使用了大量的 Cache 手段,其中在循环依赖问题的解决过程中就使用了“三级缓存

三级缓存的意义

  • singletonObject:一级缓存,存放完全实例化且属性赋值完成的 Bean ,可以直接使用
  • earlySingletonObject:二级缓存,存放早期 Bean 的引用,尚未装配属性的 Bean
  • singletonFactories:三级缓存,存放实例化完成的 Bean 工厂

除了三级缓存,还有另外两个缓存

  • singletonsCurrentlyInCreation: bean 在创建的过程中都会存储在此,创建完成移出
  • alreadyCreated:存放至少被创建一次的 bean,不会重复。即标记 bean 是否创建完成

代码示例

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
	...
	// 从上至下 分表代表这“三级缓存”
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //一级缓存
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二级缓存
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级缓存
	...
	
	/** Names of beans that are currently in creation. */
	// 这个缓存也十分重要:它表示bean创建过程中都会在里面呆着~
	// 它在Bean开始创建时放值,创建完成时会将其移出~
	private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

	/** Names of beans that have already been created at least once. */
	// 当这个Bean被创建完成后,会标记为这个 注意:这里是set集合 不会重复
	// 至少被创建了一次的  都会放进这里~~~~
	private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
}

源码解析

  • 单例 Bean 源码
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
	...
	@Override
	@Nullable
	public Object getSingleton(String beanName) {
		return getSingleton(beanName, true);
	}
	@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		//1.先从一级缓存中获取,获取到直接返回
		Object singletonObject = this.singletonObjects.get(beanName);
		//2.如果获取不到或对象正在创建,就到二级缓存中去获取,获取到直接返回
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				singletonObject = this.earlySingletonObjects.get(beanName);
				//3.如果仍获取不到,且允许 singletonFactories(allowEarlyCurrentlyInCreation())通过 getObject()获取。
				//就到三级缓存中用 getObject() 获取。
				//获取到就从 singletonFactories中移出,且放进 earlySingletonObjects。
				//(即从三级缓存移动到二级缓存)
				if (singletonObject == null && allowEarlyReference) {
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						singletonObject = singletonFactory.getObject();
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}
	...
	public boolean isSingletonCurrentlyInCreation(String beanName) {
		return this.singletonsCurrentlyInCreation.contains(beanName);
	}
	protected boolean isActuallyInCreation(String beanName) {
		return isSingletonCurrentlyInCreation(beanName);
	}
	...
}

加入 singletonFactories 三级缓存的前提是执行了构造器,所以构造器的循环依赖无法解决。

getSingleton() 从缓存里获取单例对象步骤分析可知,Spring 解决循环依赖的重点:在于 singletonFactories这个三级缓存。此 Cache 存放着 ObjectFactory,是解决问题的关键。

// 它可以将创建对象的步骤封装到ObjectFactory中 交给自定义的Scope来选择是否需要创建对象来灵活的实现scope。  具体参见Scope接口
@FunctionalInterface
public interface ObjectFactory<T> {
	T getObject() throws BeansException;
}

经过 ObjectFactory.geteObject() 后,此时放进了 二级缓存 earlySingletonObjects 内的这个对象就已经实例化好了,虽然不够完善,但是该对象的引用 已经可以被其他引用了

补充:

二级缓存 earlySingletonObjects 中的数据何时进行 添加、删除?

  • 添加:源码中的 getSingleton() 里从三级缓存移动到二级缓存中(唯一)。
  • 删除:addSingleton、addSingletonFactorie、removeSingleton即添加单例、添加单例工厂 ObjectFactory的时候都会删除二级缓存里面对应的缓存值,是互斥的

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java并发编程示例(五):线程休眠与恢复

    Java并发编程示例(五):线程休眠与恢复

    这篇文章主要介绍了Java并发编程示例(五):线程休眠与恢复,在本节,我们将开发一个程序,使用sleep()方法来实现每秒钟打印一次当前时间,需要的朋友可以参考下
    2014-12-12
  • MybatisPlus搭建项目环境及分页插件

    MybatisPlus搭建项目环境及分页插件

    Mybatis-Plus(简称MP)是一个Mybatis的增强工具,在Mybatis的基础上只做增强不做改变,为简化开发、提高效率而生,下面这篇文章主要给大家介绍了关于MybatisPlus搭建项目环境及分页插件的相关资料,需要的朋友可以参考下
    2022-11-11
  • SpringBoot + MapStruct 属性映射工具的使用详解

    SpringBoot + MapStruct 属性映射工具的使用详解

    MapStruct 是一个代码生成器,简化了不同的 Java Bean 之间映射的处理,所谓的映射指的就是从一个实体变化成一个实体。接下来通过本文给大家介绍SpringBoot + MapStruct 属性映射工具的使用,需要的朋友可以参考下
    2021-09-09
  • Java中将字符串数字转换成数字类型的多种方法

    Java中将字符串数字转换成数字类型的多种方法

    这篇文章主要介绍了Java中将字符串数字转换成数字类型的多种方法,Java中字符串转数字可用parseXXX方法、自动类型推断、异常处理、BigDecimal及Apache库实现,需注意格式验证、数据可靠性、精度及性能选择,尤其处理用户输入时推荐带异常处理方案,需要的朋友可以参考下
    2025-08-08
  • java分布式面试CAP分别代表含义分析

    java分布式面试CAP分别代表含义分析

    这篇文章主要为大家介绍了java分布式面试中关于CAP分别代表含义的问题分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2022-03-03
  • springboot的调度服务与异步服务使用详解

    springboot的调度服务与异步服务使用详解

    本文主要介绍了Java的ScheduledExecutorService接口和Spring Boot中如何使用调度线程池,包括核心参数、创建方式、自定义线程池、Cron表达式,以及如何在Spring Boot中配置和使用异步任务,此外,还讨论了如何模拟系统繁忙和调整异步线程池的拒绝策略
    2025-02-02
  • java集合遍历的几种方式总结及详细比较

    java集合遍历的几种方式总结及详细比较

    下面小编就为大家带来一篇java集合遍历的几种方式总结及详细比较。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-04-04
  • SpringBoot 实现微信扫码登录的示例代码

    SpringBoot 实现微信扫码登录的示例代码

    本文主要介绍使用SpringBoot框架和微信开放平台实现微信扫码登录的功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-10-10
  • Java面试题之基本语法(图解)

    Java面试题之基本语法(图解)

    这篇文章主要介绍了关于Java面试题之基本语法的相关资料,文中通过图片说明介绍的很详细,相信对大家具有一定的参考价值,有需要的朋友们下面来一起看看吧。
    2017-02-02
  • java向数据库插入数据显示乱码的几种问题解决

    java向数据库插入数据显示乱码的几种问题解决

    这篇文章主要给大家介绍了关于java向数据库插入数据显示乱码问题的解决方案,文章分别罗列了前台乱码的问题、前台先后台插入数据后台接收到的数据是乱码以及后台向数据库插入数据是乱码等几种情况,需要的朋友可以参考下
    2021-11-11

最新评论