spring5.1+SmartInstantiationAwareBeanPostProcessor 解决循环依赖

Posted 洪宏鸿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring5.1+SmartInstantiationAwareBeanPostProcessor 解决循环依赖相关的知识,希望对你有一定的参考价值。

SmartInstantiationAwareBeanPostProcessor 解决循环依赖的过程, 例如上面的 A依赖B, B依赖A

SmartInstantiationAwareBeanPostProcessor 是 Spring 中的一个接口,它扩展了 InstantiationAwareBeanPostProcessor 接口并提供了对 Bean 的实例化和属性填充的更高级的控制。在 Spring Boot 项目中,SmartInstantiationAwareBeanPostProcessor 通过三级缓存机制解决了循环依赖的问题。以 A 依赖 B,B 依赖 A 为例,我们来了解下这个过程:

  1. 当容器开始实例化 A 时,首先会调用 SmartInstantiationAwareBeanPostProcessor#predictBeanType 方法来预测 Bean 的类型。如果可以预测到类型,Spring 会优先使用这个类型来实例化对象。

  2. 接下来会调用 SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference 方法获取 A 的提前暴露的引用。这个引用通常是一个代理对象,它可以处理 A 对 B 的依赖。

  3. 将 A 的提前暴露引用放入三级缓存。三级缓存分别是 singletonObjects、earlySingletonObjects 和 singletonFactories。其中,singletonObjects 用于存放完全初始化好的单例 Bean,earlySingletonObjects 用于存放提前暴露的 Bean 引用,singletonFactories用于存放原始 Bean 的工厂对象。在这个例子中,A 的提前暴露引用会被放入 earlySingletonObjects 缓存。

  4. 接下来,容器开始实例化 B。同样地,会调用 SmartInstantiationAwareBeanPostProcessor#predictBeanType 和 SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference 方法获取 B 的提前暴露引用,并将其放入 earlySingletonObjects 缓存。

  5. 当 B 的实例化完成后,开始填充属性。这时会发现 B 依赖 A,于是会从三级缓存中获取 A 的引用。由于 A 正在创建过程中,所以从 earlySingletonObjects 缓存中获取到了 A 的提前暴露引用。将 A 的引用设置到 B 的属性上,完成 B 的属性填充。

  6. B 的实例化和属性填充完成后,将 B 从 earlySingletonObjects 移动到 singletonObjects 缓存中。

  7. 接下来,开始填充 A 的属性。由于 A 依赖 B,会从三级缓存中获取 B 的引用。此时,B 已经完全初始化完成,所以从 singletonObjects 缓存中获取到了 B 的引用。将 B 的引用设置到 A 的属性上,完成 A 的属性填充。

  8. A 的实例化和属性填充完成后,将 A 从 earlySingletonObjects 移动到 singletonObjects 缓存中。至此,A 和 B 的循环依赖解决了。

  9. 容器会继续调用各个 BeanPostProcessor 的 postProcessAfterInitialization 方法,对 A 和 B 进行后置处理。这些后置处理可能包括代理对象的创建等操作。

  10. 最后,A 和 B 的实例化和属性填充完成,循环依赖被成功解决。容器将 A 和 B 的引用提供给其他需要它们的 Bean。

通过以上流程,Spring Boot 利用 SmartInstantiationAwareBeanPostProcessor 和三级缓存机制成功地解决了 A 和 B 之间的循环依赖问题。这种处理方式不仅保证了 Bean 实例化的正确性,还确保了容器在解决循环依赖时的高性能。

总结

SmartInstantiationAwareBeanPostProcessor 是一个 Spring 容器中的扩展点,它本身并不直接解决循环依赖问题。Spring 容器在解决循环依赖时,主要依赖于 DefaultSingletonBeanRegistry 类中的 getSingleton() 方法,该方法使用了三级缓存来处理循环依赖。
上述过程仅适用于属性注入的情况
SmartInstantiationAwareBeanPostProcessor,它提供了一些扩展点,允许我们在 Spring 容器创建和初始化 Bean 实例的过程中进行干预。例如,我们可以使用它来改变实例化策略、解析依赖关系或者修改属性值等。然而,SmartInstantiationAwareBeanPostProcessor 本身并不直接参与解决循环依赖问题。

总之,Spring 容器通过三级缓存解决属性注入的循环依赖问题。然而,对于构造器注入的循环依赖,我们需要调整代码结构或使用其他技术手段来解决。SmartInstantiationAwareBeanPostProcessor 是一个扩展点,允许我们在 Bean 实例创建和初始化的过程中进行干预,但它本身并不直接解决循环问题

在实际项目中,我们应尽量避免产生循环依赖,以降低代码的复杂度。当循环依赖确实需要解决时,可以尝试以下方法:

使用属性注入而非构造器注入,让 Spring 容器自动处理循环依赖问题。
提取接口,将 A 和 B 的公共功能抽取为接口,然后让 A 和 B 分别依赖这些接口,而不是直接依赖彼此。
使用事件驱动或消息队列,将 A 和 B 之间的交互转移到事件驱动或消息队列中,这样可以避免直接的循环依赖。
调整代码结构,如使用中介者模式、模块化设计等方法,将循环依赖拆分为线性依赖

Spring框架系列 - Spring和Spring框架组成

Spring是什么?它是怎么诞生的?有哪些主要的组件和核心功能呢? 本文通过这几个问题帮助你构筑Spring和Spring Framework的整体认知。@pdai


什么是Spring?


首先,Spring是什么?它是怎么诞生的?它的诞生是为了解决什么问题?@pdai


Spring的起源


百度百科中关于Spring的起源介绍如下:


要谈Spring的历史,就要先谈J2EE。J2EE应用程序的广泛实现是在1999年和2000年开始的,它的出现带来了诸如事务管理之类的核心中间层概念的标准化,但是在实践中并没有获得绝对的成功,因为开发效率,开发难度和实际的性能都令人失望。

曾经使用过EJB开发JAVA EE应用的人,一定知道,在EJB开始的学习和应用非常的艰苦,很多东西都不能一下子就很容易的理解。EJB要严格地实现各种不同类型的接口,类似的或者重复的代码大量存在。而配置也是复杂和单调,同样使用JNDI进行对象查找的代码也是单调而枯燥。虽然有一些开发工作随着xdoclet的出现,而有所缓解,但是学习EJB的高昂代价,和极低的开发效率,极高的资源消耗,都造成了EJB的使用困难。而Spring出现的初衷就是为了解决类似的这些问题。

Spring的一个最大的目的就是使JAVA EE开发更加容易。同时,Spring之所以与Struts、Hibernate等单层框架不同,是因为Spring致力于提供一个以统一的、高效的方式构造整个应用,并且可以将单层框架以最佳的组合揉和在一起建立一个连贯的体系。可以说Spring是一个提供了更完善开发环境的一个框架,可以为POJO(Plain Ordinary Java Object)对象提供企业级的服务。

Spring的形成,最初来自Rod Jahnson所著的一本很有影响力的书籍《​​Expert One-on-One J2EE Design and Development​​》,就是在这本书中第一次出现了Spring的一些核心思想,该书出版于2002年。

Spring的特性和优势


Spring Framework有哪些特性,用了这个框架对开发而言有什么好处呢?


从Spring 框架的特性来看:

  • 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
  • 控制反转:IOC——Inversion of Control,指的是将对象的创建权交给 Spring 去创建。使用 Spring 之前,对象的创建都是由我们自己在代码中new创建。而使用 Spring 之后。对象的创建都是给了 Spring 框架。
  • 依赖注入:DI——Dependency Injection,是指依赖的对象不需要手动调用 setXX 方法去设置,而是通过配置赋值。
  • 面向切面编程:Aspect Oriented Programming——AOP
  • 容器:Spring 是一个容器,因为它包含并且管理应用对象的生命周期
  • 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
  • 一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上 Spring 自身也提供了表现层的 SpringMVC 和持久层的 Spring JDBC)

从使用Spring 框架的好处看:

  • Spring 可以使开发人员使用 POJOs 开发企业级的应用程序。只使用 POJOs 的好处是你不需要一个 EJB 容器产品,比如一个应用程序服务器,但是你可以选择使用一个健壮的 servlet 容器,比如 Tomcat 或者一些商业产品。
  • Spring 在一个单元模式中是有组织的。即使包和类的数量非常大,你只要担心你需要的,而其它的就可以忽略了。
  • Spring 不会让你白费力气做重复工作,它真正的利用了一些现有的技术,像 ORM 框架、日志框架、JEE、Quartz 和 JDK 计时器,其他视图技术。
  • 测试一个用 Spring 编写的应用程序很容易,因为环境相关的代码被移动到这个框架中。此外,通过使用 JavaBean-style POJOs,它在使用依赖注入注入测试数据时变得更容易。
  • Spring 的 web 框架是一个设计良好的 web MVC 框架,它为比如 Structs 或者其他工程上的或者不怎么受欢迎的 web 框架提供了一个很好的供替代的选择。MVC 模式导致应用程序的不同方面(输入逻辑,业务逻辑和UI逻辑)分离,同时提供这些元素之间的松散耦合。模型(Model)封装了应用程序数据,通常它们将由 POJO 类组成。视图(View)负责渲染模型数据,一般来说它生成客户端浏览器可以解释 HTML 输出。控制器(Controller)负责处理用户请求并构建适当的模型,并将其传递给视图进行渲染。
  • Spring 对 JavaEE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。
  • 轻量级的 IOC 容器往往是轻量级的,例如,特别是当与 EJB 容器相比的时候。这有利于在内存和 CPU 资源有限的计算机上开发和部署应用程序。
  • Spring 提供了一致的事务管理接口,可向下扩展到(使用一个单一的数据库,例如)本地事务并扩展到全局事务(例如,使用 JTA)

Spring有哪些组件?

Spring Framework有哪些组件呢?

下图来自,​​官方文档 Spring-framework 5.0​​;需要注意的是,虽然这个图来源于Spring Framwork5.0 M4 版本,但是它依然是V4版本的图,比如Spring 5版本中的web模块已经去掉了Portlet模块,新增了WebFlux模块等。

Spring框架系列(1)

上图中包含了 Spring 框架的所有模块,这些模块可以满足一切企业级应用开发的需求,在开发过程中可以根据需求有选择性地使用所需要的模块。下面分别对这些模块的作用进行简单介绍(并且结合SpringFramework5.x源码模块帮助你对应好各模块关系)。

Core Container(Spring的核心容器)

Spring 的核心容器是其他模块建立的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 SpEL 表达式语言模块组成,没有这些核心容器,也不可能有 AOP、Web 等上层的功能。具体介绍如下。

  • Beans 模块:提供了框架的基础部分,包括控制反转和依赖注入。
  • Core 核心模块:封装了 Spring 框架的底层部分,包括资源访问、类型转换及一些常用工具类。
  • Context 上下文模块:建立在 Core 和 Beans 模块的基础之上,集成 Beans 模块功能并添加资源绑定、数据验证、国际化、Java EE 支持、容器生命周期、事件传播等。ApplicationContext 接口是上下文模块的焦点。
  • SpEL 模块:提供了强大的表达式语言支持,支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从 Spring 容器获取 Bean,它也支持列表投影、选择和一般的列表聚合等。

对应的源码模块如下:

Spring框架系列(1)

Data Access/Integration(数据访问/集成)

数据访问/集成层包括 JDBC、ORM、OXM、JMS 和 Transactions 模块,具体介绍如下。

  • JDBC 模块:提供了一个 JBDC 的样例模板,使用这些模板能消除传统冗长的 JDBC 编码还有必须的事务控制,而且能享受到 Spring 管理事务的好处。
  • ORM 模块:提供与流行的“对象-关系”映射框架无缝集成的 API,包括 JPA、JDO、Hibernate 和 MyBatis 等。而且还可以使用 Spring 事务管理,无需额外控制事务。
  • OXM 模块:提供了一个支持 Object /XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。将 Java 对象映射成 XML 数据,或者将XML 数据映射成 Java 对象。
  • JMS 模块:指 Java 消息服务,提供一套 “消息生产者、消息消费者”模板用于更加简单的使用 JMS,JMS 用于用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
  • Transactions 事务模块:支持编程和声明式事务管理。

对应的源码模块如下:

Spring框架系列(1)

Web模块

Spring 的 Web 层包括 Web、Servlet、WebSocket 和 Webflux 组件,具体介绍如下。

  • Web 模块:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IOC 容器初始化以及 Web 应用上下文。
  • Servlet 模块:提供了一个 Spring MVC Web 框架实现。Spring MVC 框架提供了基于注解的请求资源注入、更简单的数据绑定、数据验证等及一套非常易用的 JSP 标签,完全无缝与 Spring 其他技术协作。
  • WebSocket 模块:提供了简单的接口,用户只要实现响应的接口就可以快速的搭建 WebSocket Server,从而实现双向通讯。
  • Webflux 模块: Spring WebFlux 是 Spring Framework 5.x中引入的新的响应式web框架。与Spring MVC不同,它不需要Servlet API,是完全异步且非阻塞的,并且通过Reactor项目实现了Reactive Streams规范。Spring WebFlux 用于创建基于事件循环执行模型的完全异步且非阻塞的应用程序。

此外Spring4.x中还有Portlet 模块,在Spring 5.x中已经移除

  • Portlet 模块:提供了在 Portlet 环境中使用 MVC 实现,类似 Web-Servlet 模块的功能。

对应的源码模块如下:

Spring框架系列(1)

AOP、Aspects、Instrumentation和Messaging

在 Core Container 之上是 AOP、Aspects 等模块,具体介绍如下:

  • AOP 模块:提供了面向切面编程实现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态的把这些功能添加到需要的代码中,这样各司其职,降低业务逻辑和通用功能的耦合。
  • Aspects 模块:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。
  • Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。
  • messaging 模块:Spring 4.0 以后新增了消息(Spring-messaging)模块,该模块提供了对消息传递体系结构和协议的支持。
  • jcl 模块: Spring 5.x中新增了日志框架集成的模块。

对应的源码模块如下:

Spring框架系列(1)

Test模块

Test 模块:Spring 支持 Junit 和 TestNG 测试框架,而且还额外提供了一些基于 Spring 的测试功能,比如在测试 Web 框架时,模拟 Http 请求的功能。

包含Mock Objects, TestContext Framework, Spring MVC Test, WebTestClient。

对应的源码模块如下:

Spring框架系列(1)

为什么用Spring?


那么为什么用Spring呢?来看看​​官网对这个问题的回答​


最重要的体现在它能做什么,这是Spring的核心所在

Spring框架系列(1)

且官方对此专门对此做了详细介绍,感兴趣可以看下

Spring框架系列(1)

学习Spring时参考哪些资料呢?


非常负责任的告诉你,最好最全的资料在Spring的官网,Spring能成为最主要的企业开发框架,文档和生态体系也做的很好;这里介绍下如何获取官方的学习资源。@pdai


Spring 的官方项目和教程

官方的项目和教程,地址在​​这里​​,在学习Spring时,一定要把它当做生态体系,而是不是一个简单的开发框架。

Spring框架系列(1)

Spring 的归档文档

官方提供了系统性的文档的FTP,你可以在​​这里​​找到所有历史版本的PDF/HTML版本。

Spring框架系列(1)

可以看到很多系统性的文档,包括上面引用的图,

Spring框架系列(1)Spring框架系列(1)

Spring 的官方Github

Spring官方的GitHub在​​这里​​,它包含着Spring-framework的源码,如果你感兴趣,可以从这里clone代码进行阅读。

Spring框架系列(1)

更多文章


首先, 从Spring框架的整体架构和组成对整体框架有个认知。


  • Spring是什么?它是怎么诞生的?有哪些主要的组件和核心功能呢? 本文通过这几个问题帮助你构筑Spring和Spring Framework的整体认知。


其次,通过案例引出Spring的核心(IoC和AOP),同时对IoC和AOP进行案例使用分析。


  • 上文中我们简单介绍了Spring和Spring Framework的组件,那么这些Spring Framework组件是如何配合工作的呢?本文主要承接上文,向你展示Spring Framework组件的典型应用场景和基于这个场景设计出的简单案例,并以此引出Spring的核心要点,比如IOC和AOP等;在此基础上还引入了不同的配置方式, 如XML,Java配置和注解方式的差异。


基于Spring框架和IOC,AOP的基础,为构建上层web应用,需要进一步学习SpringMVC。


  • 前文我们介绍了Spring框架和Spring框架中最为重要的两个技术点(IOC和AOP),那我们如何更好的构建上层的应用呢(比如web 应用),这便是SpringMVC;Spring MVC是Spring在Spring Container Core和AOP等技术基础上,遵循上述Web MVC的规范推出的web开发框架,目的是为了简化Java栈的web开发。 本文主要介绍SpringMVC的请求流程和基础案例的编写和运行。


Spring进阶 - IoC,AOP以及SpringMVC的源码分析


  • 在对IoC有了初步的认知后,我们开始对IOC的实现原理进行深入理解。本文将帮助你站在设计者的角度去看IOC最顶层的结构设计
  • 上文,我们看了IOC设计要点和设计结构;紧接着这篇,我们可以看下源码的实现了:Spring如何实现将资源配置(以xml配置为例)通过加载,解析,生成BeanDefination并注册到IoC容器中的
  • 上文,我们看了IOC设计要点和设计结构;以及Spring如何实现将资源配置(以xml配置为例)通过加载,解析,生成BeanDefination并注册到IoC容器中的;容器中存放的是Bean的定义即BeanDefinition放到beanDefinitionMap中,本质上是一个ConcurrentHashMap<String, Object>;并且BeanDefinition接口中包含了这个类的Class信息以及是否是单例等。那么如何从BeanDefinition中实例化Bean对象呢,这是本文主要研究的内容?
  • 前文,我们分析了Spring IOC的初始化过程和Bean的生命周期等,而Spring AOP也是基于IOC的Bean加载来实现的。本文主要介绍Spring AOP原理解析的切面实现过程(将切面类的所有切面方法根据使用的注解生成对应Advice,并将Advice连同切入点匹配器和切面类等信息一并封装到Advisor,为后续交给代理增强实现做准备的过程)。
  • 上文我们介绍了Spring AOP原理解析的切面实现过程(将切面类的所有切面方法根据使用的注解生成对应Advice,并将Advice连同切入点匹配器和切面类等信息一并封装到Advisor)。本文在此基础上继续介绍,代理(cglib代理和JDK代理)的实现过程。
  • 我们在前文中已经介绍了SpringAOP的切面实现和创建动态代理的过程,那么动态代理是如何工作的呢?本文主要介绍Cglib动态代理的案例和SpringAOP实现的原理。
  • 上文我们学习了SpringAOP Cglib动态代理的实现,本文主要是SpringAOP JDK动态代理的案例和实现部分。
  • 前文我们有了IOC的源码基础以及SpringMVC的基础,我们便可以进一步深入理解SpringMVC主要实现原理,包含DispatcherServlet的初始化过程和DispatcherServlet处理请求的过程的源码解析。本文是第一篇:DispatcherServlet的初始化过程的源码解析。
  • 前文我们有了IOC的源码基础以及SpringMVC的基础,我们便可以进一步深入理解SpringMVC主要实现原理,包含DispatcherServlet的初始化过程和DispatcherServlet处理请求的过程的源码解析。本文是第二篇:DispatcherServlet处理请求的过程的源码解析。

以上是关于spring5.1+SmartInstantiationAwareBeanPostProcessor 解决循环依赖的主要内容,如果未能解决你的问题,请参考以下文章

你有一份来自春天的信—Spring5.x 源码分析 : 从容器启动开始

使用 ssl 的 Spring 5 WebClient

在 Spring 框架中使用 Dubbo

使用 Spring WebClient 解码内容编码 gzip

在 Spring JPA 中删除实体

最新的dubbo和zookeeper整合的问题