欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 建筑 > Spring循环依赖看这一篇就够了,解决它真的需要三级缓存吗?

Spring循环依赖看这一篇就够了,解决它真的需要三级缓存吗?

2025/2/23 0:34:02 来源:https://blog.csdn.net/qq_45875349/article/details/144699003  浏览:    关键词:Spring循环依赖看这一篇就够了,解决它真的需要三级缓存吗?

1. 什么是循环依赖

从字面意思来看就是彼此依赖,你中有我,我中有你,两者相互依赖。

在日常开发中,其实还是挺容易出现这种情况的,只不过我们写的类十分的多,以至于我们自己也没有注意到自己写的两个或者是多个类之间出现了循环依赖的情况?既然常见,那么又为什么我们没有发现并且也没有报错呢?这是不是就有点不对了?这是因为Spring已经为我们开发者默默的解决了循环依赖的问题。

代码示例:

@Configuration
@ComponentScan
public class AppConfig {
}@Service
public class AuthorService {@Autowiredprivate BookService bookService;}@Service
public class BookService {@Autowiredprivate AuthorService authorService;}public class MainApplication {public static void main(String[] args) {// 初始化Spring容器AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);// 获取BookService BeanBookService bookService = (BookService) annotationConfigApplicationContext.getBean("bookService");System.out.println(bookService); // 输出BookService及其依赖的AuthorService// 获取AuthorService BeanAuthorService authorService = (AuthorService) annotationConfigApplicationContext.getBean("authorService");System.out.println(authorService); // 输出AuthorService及其依赖的BookService// 关闭Spring容器annotationConfigApplicationContext.close();}
}

可见,代码中有循环依赖的现象但是并没有报错。这是为什么?

2. 如何解决的循环依赖(三级缓存)

先来说一下Bean的生命周期吧。Spring Bean的生命周期可以简单概括为4个阶段:

1. 实例化

2. 属性赋值

3. 初始化

4. 销毁

然后再来说一下三级缓存。

单例模式下,在第一次使用 Bean 时,会创建一个 Bean 对象,并放入 IoC 容器的缓存池中。后续再使用该 Bean 对象时,会直接从缓存池中获取。保存单例模式 Bean 的缓存池,采用了三级缓存设计,如下代码所示。

/** Cache of singleton objects: bean name --> bean instance */
/** 一级缓存:用于存放完全初始化好的 bean **/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);/** Cache of early singleton objects: bean name --> bean instance */
/** 二级缓存:存放原始的 bean 对象(尚未填充属性),用于解决循环依赖 */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);/** Cache of singleton factories: bean name --> ObjectFactory */
/** 三级级缓存:存放 bean 工厂对象,用于解决循环依赖 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16)
缓存层级名称   描述                             
第一层缓存singletonObjects单例对象缓存池,存放的 Bean 已经实例化、属性赋值、完全初始化好(成品
第二层缓存singletonObjects早期单例对象缓存池,存放的 Bean 已经实例化但尚未属性赋值、未执行 init 方法(半成品
第三层缓存singletonFactories单例工厂的缓存

所有被 Spring 管理的 Bean,最终都会存放在 singletonObjects 中,这里面存放的 Bean 是经历了所有生命周期的(除了销毁的生命周期),完整的,可以给用户使用的。

earlySingletonObjects 存放的是已经被实例化,但是还没有注入属性和执行 init 方法的 Bean。

singletonFactories 存放的是生产 Bean 的工厂。

Bean 都已经实例化了,为什么还需要一个生产 Bean 的工厂呢?这里实际上是跟 AOP 有关,如果项目中不需要为 Bean 进行代理,那么这个 Bean 工厂就会直接返回一开始实例化的对象,如果需要使用 AOP 进行代理,那么这个工厂就会发挥重要的作用了。后面会详细讲。


利用三级缓存解决循环依赖

Spring 是如何通过上面介绍的三级缓存来解决循环依赖的呢?这里只用 A,B 形成的循环依赖来举例:

  1. 实例化 A,此时 A 还未完成属性填充和初始化方法(@PostConstruct)的执行,A 只是一个半成品。
  2. 为 A 创建一个 Bean 工厂,并放入到 singletonFactories 中。
  3. 发现 A 需要注入 B 对象,但是一级、二级、三级缓存均为发现对象 B。
  4. 实例化 B,此时 B 还未完成属性填充和初始化方法(@PostConstruct)的执行,B 只是一个半成品。
  5. 为 B 创建一个 Bean 工厂,并放入到 singletonFactories 中。
  6. 发现 B 需要注入 A 对象,此时在一级、二级未发现对象 A,但是在三级缓存中发现了对象 A,从三级缓存中得到对象 A,并将对象 A 放入二级缓存中,同时删除三级缓存中的对象 A。(注意,此时的 A 还是一个半成品,并没有完成属性填充和执行初始化方法)
  7. 将对象 A 注入到对象 B 中。
  8. 对象 B 完成属性填充,执行初始化方法,并放入到一级缓存中,同时删除二级缓存中的对象 B。(此时对象 B 已经是一个成品)
  9. 对象 A 得到对象 B,将对象 B 注入到对象 A 中。(对象 A 得到的是一个完整的对象 B)
  10. 对象 A 完成属性填充,执行初始化方法,并放入到一级缓存中,同时删除二级缓存中的对象 A。

利用三级缓存获取Bean源码,通过一个 getSingleton() 方法去获取所需要的 Bean 的。

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);if (singletonObject == null && allowEarlyReference) {// 三级缓存ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {// Bean 工厂中获取 BeansingletonObject = singletonFactory.getObject();// 放入到二级缓存中this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject;
}

当 Spring 为某个 Bean 填充属性的时候,它首先会寻找需要注入对象的名称,然后依次执行 getSingleton() 方法得到所需注入的对象,而获取对象的过程就是先从一级缓存中获取,一级缓存中没有就从二级缓存中获取,二级缓存中没有就从三级缓存中获取,如果三级缓存中也没有,那么就会去执行 doCreateBean() 方法创建这个 Bean。

doCreateBean() 方法:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException {BeanWrapper instanceWrapper = null;if (instanceWrapper == null) {// 1.实例化对象instanceWrapper = this.createBeanInstance(beanName, mbd, args);}final Object bean = instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null;Class<?> beanType = instanceWrapper != null ? instanceWrapper.getWrappedClass() : null;//2. 判断是否允许提前暴露对象,如果允许,则直接添加一个 ObjectFactory 到三级缓存boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {// 添加三级缓存的方法详情在下方addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}// 3. 填充属性this.populateBean(beanName, mbd, instanceWrapper);// 4. 执行初始化方法,并创建代理exposedObject = initializeBean(beanName, exposedObject, mbd);return exposedObject;
}

添加三级缓存的方法如下:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) { // 判断一级缓存中不存在此对象this.singletonFactories.put(beanName, singletonFactory); // 添加至三级缓存this.earlySingletonObjects.remove(beanName); // 确保二级缓存没有此对象this.registeredSingletons.add(beanName);}}
}@FunctionalInterface
public interface ObjectFactory<T> {T getObject() throws BeansException;
}

3. 任何循环依赖都可以被Spring解决吗

如果是原型 bean的循环依赖,Spring无法解决:

@Service
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class BookService {@AutowiredAuthorService authorService;
}
@Service
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class AuthorService {@AutowiredBookService bookService;
}

如果是构造参数注入的循环依赖,Spring无法解决:

@Service
public class AuthorService {BookService bookService;public AuthorService(BookService bookService) {this.bookService = bookService;}
}
@Service
public class BookService {AuthorService authorService;public BookService(AuthorService authorService) {this.authorService = authorService;}
}

为什么无法解决原型、构造方法注入的循环依赖

如果bean是单例的,而且是字段注入(setter注入)的,单例意味着只需要创建一次对象,后面就可以从缓存中取出来,字段注入,意味着我们无需调用构造方法进行注入。

  • 如果是原型bean,那么就意味着每次都要去创建对象,无法利用缓存;
  • 如果是构造方法注入,那么就意味着需要调用构造方法注入,也无法利用缓存。(在对象已实例化后,会将对象存入三级缓存中。在调用对象的构造函数时,对象还未完成初始化,所以也就无法将对象存放到三级缓存中。)

4. 两级缓存能够解决循环依赖吗

可以。在2级缓存中,已经提前暴露了半成品的Bean,二级缓存的确可以解决循环依赖问题。二级缓存存储的是已经实例化但未完全初始化的bean,这些“半成品”可以通过依赖注入解决循环依赖。

但是,如果只使用二级缓存,在某些情况下,bean可能会被注入到其他对象中作为原始对象,而不是最终的代理对象。这好办,在实例化完成之后,就为其创建代理对象,这样我们就不需要第三级缓存了。这样的话,每次实例化完 Bean 之后就直接去创建代理对象,并添加到二级缓存中。

既然如此,那么为什么还需要三级缓存?

上面说了,如果使用二级缓存,这样的话,每次实例化完 Bean 之后就直接去创建代理对象,并添加到二级缓存中。但是 在Spring中,AOP代理的创建是基于bean完全初始化之后的,即所有的属性注入、生命周期方法(如@PostConstruct)执行完毕之后。这是Spring的设计原则之一。

三级缓存提供了一个延迟创建AOP代理的机制,确保所有bean都经过AOP代理后再被注入并且符合Spring的设计原则。


参考:

1. https://juejin.cn/post/6882266649509298189

2. Spring 帮助你更好的理解Spring循环依赖

3. https://juejin.cn/post/7099745254743474212#heading-15

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词