以下举例皆针对单例模式讨论
图解参考:https://www.processon.com/view/link/60e3b0ae0e3e74200e2478ce1、Spring 如何创建 Bean?
对于单例 Bean 来说,在 Spring 容器整个生命周期内,有且只有一个对象。Spring 在创建 Bean 过程中,使用到了三级缓存,即 DefaultSingletonBeanRegistry.java 中定义的:
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
以 com.gyh.general 包下的 OneBean 为例,debug springboot 启动过程,分析 spring 是如何创建 bean 的。
参考图中spring 创建 bean的过程。其中最关键的几步有:1.getSingleton(beanName, true)
依次从一二三级缓存中查找 bean 对象,如果缓存中存在对象,则直接返回 (early);2.createBeanInstance(beanName, mbd, args)
选一个合适的构造函数,new 实例对象 (instance),此时的 instance 中依赖的属性还都是 null,属于半成品;3.singletonFactories.put(beanName, oneSingletonFactory)
利用上一步的 instance,构建一个 singletonFactory,并将其放到三级缓存中;4.populateBean(beanName, mbd, instanceWrapper)
填充 bean:为该 bean 定义的属性创建对象或赋值;5.initializeBean("one",oneInstance, mbd)
初始化 bean:对 bean 进行初始化或其他加工,如生成代理对象 (proxy);6.getSingleton(beanName, false)
依次在一二级缓存中查找,检查是否有因循环依赖导致提前生成的对象,有的话与初始化后的对象是否一致;2、Spring 如何解决循环依赖?
以 com.gyh.circular.threeCache 包下的 OneBean 和 TwoBean 为例 ,两个 Bean 相互依赖(即形成闭环)。参考图中spring 解决循环依赖的过程可知,spring 利用三级缓中的 objectFactory 生成并返回一个 early 对象,提前暴露这个 early 地址,供其他对象依赖注入使用,以此解决循环依赖问题。3、Spring 不能解决哪些循环依赖?
3.1 循环中使用了@Async注解
3.1.1 为什么循环中使用了@Async 会报错?
以 com.gyh.circular.async 包下的 OneBean 和 TwoBean 为例,两个 bean 相互依赖,且 oneBean 中的方法使用了@Async 注解,此时启动 spring 失败,报错信息为:org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a.one': Bean with name 'a.one' has been injected into other beans [a.two] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.
并通过 debug 代码,发现报错位置在 AbstractAutowireCapableBeanFactory#doCreateBean 方法内,由于 earlySingletonReference != null 且 exposedObject != bean,导致报错。![5a2ee708-813f-11ed-8abf-dac502259ad0.png](https://file1.elecfans.com//web2/M00/9E/5E/wKgZomToFoaAG83hAANm4GzZvlc111.png)
3.1.2 循环中使用了@Async 一定会报错吗?
依然以 com.gyh.circular.async 包下的 OneBean 和 TwoBean 为例,两个 bean 相互依赖,使 TwoBean (非 OneBean) 中的方法使用了@Async 注解,此时启动 spring 成功,并未报错。debug 代码可知:虽然 TwoBean 使用了 @Async 注解,但其 earlySingletonReference = null; 故不会引起报错。![5a6698f6-813f-11ed-8abf-dac502259ad0.png](https://file1.elecfans.com//web2/M00/9E/5E/wKgZomToFoaALTC2AAPlZVy8gPs458.png)
3.1.3 为什么循环中使用了 @Transactional 不会报错?
已知使用了 @Transactional 注解的 Bean,Spring 也会为其生成代理对象,但为什么这种 Bean 在循环里时不会产生报错呢?以 com.gyh.circular.transactional 包下的 OneBean 和 TwoBean 为例,两个 Bean 相互依赖,且 OneBean 中的方法使用了 @Transactional 注解,启动 Spring 成功,并不会报错。debug 代码可知,生成 OneBean 过程中,虽然 earlySingletonReference != null,但 initializeBean 之后的 exposedObject 和 原始实例的地址相同(即 initializeBean 步骤中,并未对实例生成代理),所以不会产生报错。![5ad3f7c0-813f-11ed-8abf-dac502259ad0.png](https://file1.elecfans.com//web2/M00/9E/5E/wKgZomToFoaAGUdgAAO7wLJSKnw152.png)
![5afa0d48-813f-11ed-8abf-dac502259ad0.png](https://file1.elecfans.com//web2/M00/9E/5E/wKgZomToFoaAJBrVAAPk-GbudT8054.png)
![5b3be77c-813f-11ed-8abf-dac502259ad0.png](https://file1.elecfans.com//web2/M00/9E/5E/wKgZomToFoeAa1_9AAN1hqp6fTg593.png)
![5b6c1d7a-813f-11ed-8abf-dac502259ad0.png](https://file1.elecfans.com//web2/M00/9E/5E/wKgZomToFoeAaP6yAAM97HMwCi0088.png)
3.1.5 为什么 @Async 在 getEarlyBeanReference 时不能返回一个 advice?
在 AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean 方法内,其主要做的事情有:1.找到当前 spring 容器中所有的 Advisor2.返回适配当前 bean 的所有 Advisor第一步返回的 Advisor 有 BeanFactoryCacheOperationSourceAdvisor 和 BeanFactoryTransactionAttributeSourceAdvisor,并无处理 Async 相关的 Advisor.刨根问底,追查为什么第一步不会返回处理 Async 相关的 Advisor?已知使用 @Async @Transactional @Cacheable 需要提前进行开启,即提前标注 @EnableAsync、@EnableTransactionManagement、@EnableCaching 。以 @EnableTransactionManagement、@EnableCaching 为例,在其注解定义中,引入了 Selector 类,Selector 中又引入了 Configuration 类,在 Configuration 类中,创建了对应 Advisor 并放到了 spring 容器中,所以第一步才能得到这两个 Advisor.而 @EnableAsync 的定义中引入的 Configuration 类,创建的是 AsyncAnnotationBeanPostProcessor 并非一个 Advisor,所以第一步不会得到它,所以 @Async 的 bean 不会在这一步被代理。3.2 构造函数引起的循环依赖
以 com.gyh.circular.constructor 包下的 OneBean 和 TwoBean 为例,两个类的构造函数中各自依赖对方,启动 spring,报错:org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'c.one': Requested bean is currently in creation: Is there an unresolvable circular reference?
debug 代码可知,两个 bean 在根据构造函数 new instance 时,就已经陷入的死循环,无法提前暴露可用的地址,所以只能报错。4、如何解决以上循环依赖报错?
1.不用 @Async,将需要异步操作的方法,放到线程池中执行。(推荐)2.提出 @Async 标注的方法。(推荐)3.将使用 @Async 的方法提出到单独的类中,该类只做异步处理,不做其他业务依赖,即避免形成循环依赖,从而解决报错问题。参考 com.gyh.circular.async.extract 包。4.尽量不使用构造函数依赖对象。(推荐)5.破坏循环(不推荐)即不形成闭环,在开发之前,规划好对象依赖,方法调用链,尽量做到不使用循环依赖。(较难,随着迭代开发不断变化,很可能产生循环)6.破坏创建顺序(不推荐)7.由于使用 @Async 注解的所在类,比循环依赖内其他类先创建时才会报错,那么想办法使该类不先于其他类先创建,也可解决该问题,如:@DependsOn、@Lazy审核编辑 :李倩
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。
举报投诉
-
源码
+关注
关注
8文章
613浏览量
28794 -
spring
+关注
关注
0文章
334浏览量
14221
原文标题:从源码层面深度剖析 Spring 循环依赖
文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
Spring中依赖注入的四种方式
在Spring框架中,依赖注入是一种核心的概念和机制。通过依赖注入,我们可以让对象之间的依赖关系更加松散,并且能够方便地进行单元测试和模块化开发。在
Spring MVC的工作原理
Spring MVC是一种基于Java的Web应用程序框架,它采用了Model-View-Controller(MVC)设计模式来分离应用程序的不同方面。Spring MVC的工作原理涉及多个关键
Spring依赖注入的方式
Spring 是一个开源的轻量级框架,可以用于构建企业级应用程序。其最重要的特性之一是依赖注入(Dependency Injection,DI),这是一种设计模式,它可以帮助我们解耦代码、提高
Spring Boot 的设计目标
,这样我们就可以尽快的上手。 使用 Spring Boot 来不仅可以创建基于 war 方式部署的传统Java应用程序,也可以通过创建独立的不依赖任何容器(如 tomcat 等)
![<b class='flag-5'>Spring</b> Boot 的设计目标](https://file1.elecfans.com/web2/M00/A8/04/wKgaomUo6gmAEQN0AAESfDw9EW8049.jpg)
Spring Boot的启动原理
可能很多初学者会比较困惑,Spring Boot 是如何做到将应用代码和所有的依赖打包成一个独立的 Jar 包,因为传统的 Java 项目打包成 Jar 包之后,需要通过 -classpath 属性
![<b class='flag-5'>Spring</b> Boot的启动原理](https://file1.elecfans.com/web2/M00/A9/C0/wKgZomUovNCAdZmWAADhZidr2zI277.jpg)
Spring Boot启动 Eureka流程
在上篇中已经说过了 Eureka-Server 本质上是一个 web 应用的项目,今天就来看看 Spring Boot 是怎么启动 Eureka 的。 Spring Boot 启动 Eureka
![<b class='flag-5'>Spring</b> Boot启动 Eureka流程](https://file1.elecfans.com/web2/M00/A7/86/wKgaomUkx7OAdMOGAAIBWIj8ao0506.jpg)
Spring Boot Actuator快速入门
使用的框架基本上都要升级到 2.x了吧。 什么是 Actuator ? 从本质上讲, Spring Boot Actuator 为我们的应用程序带来了生产就绪的功能。监控我们的应用程序,收集指标,了解流量,或者是
Spring Boot Starter需要些什么
前面我们简单介绍了如何使用消息中间件 Apache Pulsar ,但是在项目中那样使用,显然是不太好的,不管从易用性和扩展性来看,都是远远不够, 为了和springboot项目集成,写一个
![<b class='flag-5'>Spring</b> Boot Starter需要些什么](https://file1.elecfans.com/web2/M00/A7/C2/wKgZomUQ_7qARxhjAAAxV9nBodk577.jpg)
Spring AOP如何破解java应用
前面我们看过javaassit是如何破解java应用,核心都是AOP相关的知识,今天我们看下Spring AOP是怎么回事! Spring-AOP spring 5.x版本 AOP面向切面编程,通过
![<b class='flag-5'>Spring</b> AOP如何破解java应用](https://file1.elecfans.com/web2/M00/A7/C1/wKgZomUQ-1iAAb4oAAGQeDYxI9k272.jpg)
Faster Transformer v2.1版本源码解读
和优化技巧进行了深度剖析,有兴趣的读者可以移步——【CUDA编程】Faster Transformer v1.0 源码详解。 在 FasterTransformer v2.0 中,Nvidia 添加了一个高度优化的 Decode
![Faster Transformer v2.1版本<b class='flag-5'>源码</b>解读](https://file1.elecfans.com/web2/M00/A3/B5/wKgZomUJF9eAXWDQAAA5V2i2udk464.png)
Faster Transformer v1.0源码详解
写在前面:本文将对 Nvidia BERT 推理解决方案 Faster Transformer 源码进行深度剖析,详细分析作者的优化意图,并对源码中的加速技巧进行介绍,希望对读者有所帮
![Faster Transformer v1.0<b class='flag-5'>源码</b>详解](https://file1.elecfans.com/web2/M00/A3/88/wKgaomT6hVGAD2UQAAAt8fz1YhU857.png)
Spring容器原始Bean是如何创建的?Spring源码中方法的执行顺序
这个话题其实非常庞大,我本来想从 getBean 方法讲起,但一想这样讲完估计很多小伙伴就懵了,所以我们还是一步一步来,今天我主要是想和小伙伴们讲讲 Spring 容器创建 Bean 最最核心的 createBeanInstance 方法,这个方法专门用来创建一个原始 Bean 实例。
![<b class='flag-5'>Spring</b>容器原始Bean是如何创建的?<b class='flag-5'>Spring</b><b class='flag-5'>源码</b>中方法的执行顺序](https://file1.elecfans.com/web2/M00/8F/4D/wKgZomTMXxiALi1kAAAg9m1Iz1U688.png)
评论