Spring中BeanPostProcessor与循环依赖的问题

news2024/11/16 21:58:57

Spring中后置处理器与循环依赖的问题

在Spring的bean生命周期中, 我们可以通过实现BeanPostProcessor来对bean初始化前后做操作, 在网上的许多帖子博客中, 不乏可以看见说Spring的AOP是在后置处理器中实现的, 这个理解显然有失偏颇, 很容易误导一般人在后置处理器中对原先的bean进行代理增强再返回替换掉原来的bean引用, 但这极容易引起一个如下error:

二月 13, 2023 9:14:43 下午 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bean1': Bean with name 'bean1' has been injected into other beans [bean2] 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.
Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bean1': Bean with name 'bean1' has been injected into other beans [bean2] 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.
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:643)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:509)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:248)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:901)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:907)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:561)
	at com.yadong.demo01.AnnotationConfigApplicationContextTest.main(AnnotationConfigApplicationContextTest.java:20)

这个错误只有在循环依赖产生时, 并且在后置处理替换掉了原来的bean并重新返回一个新的bean时会产生。

以下是demo示例:

目录结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tHuOViA1-1676773262083)(C:\Users\YadongTan\AppData\Roaming\Typora\typora-user-images\image-20230213212546416.png)]

Bean1.java
package com.yadong.demo01;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Bean1 {

   private String name = "bean1";

   @Autowired
   Bean2 bean2;

   public Bean1(){

   }
   public Bean1(String name){
      this.name = name;
   }


   public Bean2 getBean2() {
      return bean2;
   }

   public void setBean2(Bean2 bean2) {
      this.bean2 = bean2;
   }

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }

   @Override
   public String toString() {
      return "Bean1{" +
            "name='" + name + '\'' +
            ", bean2.name=" + bean2.getName() +
            '}';
   }
}
Bean2.java
package com.yadong.demo01;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

@Component
public class Bean2 {
   private String name = "bean2";

   @Autowired
   Bean1 bean1;

   public Bean2() {

   }

   public Bean2(String name) {
      this.name = name;
   }

   public Bean1 getBean1() {
      return bean1;
   }

   public void setBean1(Bean1 bean1) {
      this.bean1 = bean1;
   }

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }

   @Override
   public String toString() {
      return "Bean2{" +
            "name='" + name + '\'' +
            ", bean1.name=" + bean1.getName() +
            '}';
   }
}
BeanPostProcessor1.java
package com.yadong.demo01;


import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class BeanPostProcessor1 implements BeanPostProcessor {
   @Override
   public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      System.out.println("[" + beanName + "]" + "初始化 前处理方法");
      return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
   }

   @Override
   public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
      System.out.println("[" + beanName + "]" + "初始化 后处理方法");
      return doInsteadBean(bean, beanName);
      //return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
   }

   private Object doInsteadBean(Object bean, String beanName){
      if(beanName.equals("bean1")){
         Bean1 bean1 = new Bean1("替换后的对象bean1");
         Bean2 bean2 = new Bean2("替换后的对象bean1.bean2");
         bean1.setBean2(bean2);
         return bean1;
      }else{
         return bean;
      }
   }
}
AnnotationConfigApplicationContextTest.java
package com.yadong.demo01;

import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
* @author YadongTan
* @Description 循环依赖与bean生命周期测试
*/
public class AnnotationConfigApplicationContextTest {

   public static void main(String[] args) {

      DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
      AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(factory);

      context.register(Config.class);
      context.refresh();

      Bean1 bean1 = context.getBean(Bean1.class);
      System.out.println("bean1 = " + bean1);

      Bean2 bean2 = context.getBean(Bean2.class);
      System.out.println("bean2 = " + bean2);
   }

   @Configuration
   @ComponentScan("com.yadong.demo01")
   protected static class Config{
   }
}

因为1ean1与bean2有循环依赖, 因此创建bean1过程中, 逻辑是:

(bean1)getBean() -> doGetBean() -> createBean() -> doCreateBean() -> getSingleton() -> populateBean() ->… -> (bean2) getBean() -> doGetBean() -> … -> populateBean() ->对Bean2中的bean1字段进行属性填充,。

此时bean1这个bean会被调用getEarlyBeanReference()以提前将对象暴露出来, 在这里方法中, 会调用到WrapIfNecessary(), Spring会对这个bean判断是否需要被AOP增强从而创建代理, 一旦bean1的引用被bean2所持有, 后续在BeanPostProcessor替换掉原先的bean就会导致bean2属性中持有的bean1是旧版本的bean, 也就是没有持有最终版本的bean1(因为你在后置处理中替换掉了原来的bean)。

对于存在循环依赖又想对Bean进行手动代理的, 可以选择实现SmartInstantiationAwareBeanPostProcessor接口的getEarlyBeanReference()方法, 在这里返回代理后的bean。

package com.yadong.demo01;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class BeanPostProcessor3 implements SmartInstantiationAwareBeanPostProcessor {

   @Override
   public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
      return doInsteadBean(bean, beanName);
   }

   private Object doInsteadBean(Object bean, String beanName){
      if(beanName.equals("bean1")){
         Bean1 bean1 = new Bean1("替换后的对象bean1");
         Bean2 bean2 = new Bean2("替换后的对象bean1.bean2");
         bean1.setBean2(bean2);
         return bean1;
      }else{
         return bean;
      }
   }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/355978.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android 一帧绘制流程分析笔记

和你一起终身学习,这里是程序员Android经典好文推荐,通过阅读本文,您将收获以下知识点:一、显示一帧流程概览二、生产者,消费者 BufferQueue 流转图三、App ,SF Buffer 交互图四、SF 跟 HWC 交互图一、显示一帧流程概览…

16.CSS中使用颜色

使用颜色 在计算机中,传统的模型之一为RGB模型,所有颜色都是通过红色、蓝色、绿色三种颜色进行组合;我们通过数值去表示 例如: 红色(255.0.0)、蓝色(0.0.255)、绿色(0.…

在IDEA中配置jeesite-(文章链接汇总)

建议按照文章顺序操作 原文里可以下载geesite项目 jeesite-github原文链接 jeesite-gitee原文链接–国内推荐 可 直 接 跳 到 此 步 骤 环境准备:JDK 1.8 or 11、17、Maven 3.6、使用 MySQL 5.7 or 8.0 数据库 1-Maven的下载安装配置教程(详细图文&am…

Vue3之循环渲染

1.何为循环渲染 在Vue3中,当我们需要渲染一个数组中的数据到dom元素上时,就需要使用循环渲染。循环渲染可以节约我们大量重复冗余的工作,比如我们去渲染一个下拉菜单的时候,如果不使用循环渲染,那么我们需要手动一项一…

2023美赛C代码思路结果【全部更新完毕】注释详尽

C题已完成全部代码,注释详尽,并增加扰动项,保证大家的结果不会撞 需要全部问题的可以点击:https://www.jdmm.cc/file/2708697/ 下面贴出核心代码: -- coding: utf-8 -- TODO: 入口函数 import numpy as np from…

Mr. Cappuccino的第43杯咖啡——Kubernetes之Pod控制器(二)

Kubernetes之Pod控制器Horizontal Pod Autoscaler(HPA)安装metrics-server创建Pod创建HPA压力测试JobCronJobHorizontal Pod Autoscaler(HPA) 上篇文章中所说的ReplicaSet和Deployment,我们已经可以通过手动执行kubec…

Shiro1.9学习笔记

文章目录一、Shiro概述1、Shiro简介1.1 介绍1.2 Shiro特点2、Shiro与SpringSecurity的对比3、Shiro基本功能4、Shiro原理4.1 Shiro 架构(外部)4.2 shiro架构(内部)二、Shiro基本使用1、环境准备2、登录认证2.1 登录认证概念2.2 登录认证基本流程2.3 登录认证实例2.4 身份认证源…

WordPress网站伪静态及固定链接设置教程

WordPress网站伪静态及固定链接设置教程-解决404错误问题!搭建好WordPress网站之后我个人建议首先要做的就是设置好固定链接,WordPress的固定链接也就是网站各个页面的链接格式,默认的方式不太符合现代化网站,推荐使用自定义的方案。既然涉及…

良许翻天覆地的2022年

大家好,我是良许,新年快乐呀~ 在我女室友坚持不懈的努力之下,2022年的最后一天我终于被她传染了,阳了~ 此时的我,正顶着37多度的低烧写下这篇年终总结。 2022年,对于大多数人而言,封控是主旋…

Git使用小乌龟克隆与推送代码Gitee--零命令行(组员版本)

目录 上一篇文章(必读!!!) 概述 分支概念(权限分配): 总体操作思想: 操作步骤 一、组员第一天上班(云端仓库代码克隆到本地仓库进行代码编写&#xff…

网日志处理中的应用架构-《大数据时代的IT架构设计》

用户的上网行为中蕴含着大量的客户特征和客户需求信息,这些信息至关重要,这就要求用户的上网日志记录必须被保存,而且还需要进行数据分析挖掘处理,然后根据处理结果定义用户的行为习惯,为电信运营商实现精细化运营提供重要的营销依…

【数据库】Clickhouse 实践之路

文章目录背景Clickhouse简介为什么选择ClickhouseClickhouse特性Clickhouse建设整体架构数据接入层数据存储层数据服务层数据应用层Clickhouse运维管理平台配置文件结构元数据管理自动化运维用户管理集群操作监控与报警Clickhouse应用BI查询引擎核心诉求选型对比集群构建问题及…

基于jeecgboot的flowable流程设计器的几个bug修复

因为今天在用任务监听器的时候,出现几个bug,所以一并修复与处理。 一、建立任务监听器后,删除不了 主要有两个原因,一个是点击删除没反应,实际上是弹出框跑到后面去了,说明还是z-index问题,调整…

.Net Core中使用NEST简单操作Elasticsearch

C#中访问Elasticsearch主要通过两个包NEST和Elasticsearch.Net,NEST用高级语法糖封装了Elasticsearch.Net可以通过类Linq的方式进行操作,而Elasticsearch.Net相比之下更为原始直接非常自由。注意:ES的8.X以上的版本有新的包Elastic.Clients.E…

二极管的“几种”应用

不知大家平时有没有留意,二极管的应用范围是非常广的,下面我们来看看我想到几种应用,也可以加深对电路设计的认识: A,特性应用: 由于二极管的种类非常之多,这里这个大类简单罗列下&#xff1a…

SpringCloud Alibaba集成Dubbo实现远程服务间调用

SpringCloud Alibaba集成Dubbo实现远程服务间调用 工程创建 一、创建springBoot分模块项目,父工程:springcloud-alibaba以及子模块product-dubbo-provider、order-dubbo-consumer等 项目基本结构图如下所示: 二、依赖引入 在以上两个子模块…

数值方法笔记1:数字表示与误差分析

1 有意义数位概念与有意义数位损失1.1 怎么分析误差2 逼近阶 与渐近记法3 误差传播与稳定性1 有意义数位概念与有意义数位损失 整数的二进制表示使用短除法,网上有很多文章,这里就不再过多赘述了。 提一嘴小数的二进制表示。下面列举了一个例子。 在表…

【Spark分布式内存计算框架——Spark SQL】12. External DataSource(下)rdbms 数据

6.7 rdbms 数据 回顾在SparkCore中读取MySQL表的数据通过JdbcRDD来读取的,在SparkSQL模块中提供对应接口,提供三种方式读取数据: 方式一:单分区模式 方式二:多分区模式,可以设置列的名称,作为…

实例三:MATLAB APP design-多项式函数拟合

一、APP 界面设计展示 注:在左侧点击数据导入,选择自己的数据表,如果数据导入成功,在右侧的空白框就会显示数据导入成功。在多项式项数右侧框中输入项数,例如2、3、4等,点击计算按钮,右侧坐标框就会显示函数图像,在平均相对误差下面的空白框显示平均相对误差。

buu [NPUCTF2020]Classical Cipher 1

题目描述: 题目分析: 首先输入密码 {gsv_pvb_rh_zgyzhs} 后,得到:可以得知密码是错误的,再看看密码 {gsv_pvb_rh_zgyzhs} ,排列无序,那么尝试用凯撒与栅栏解密,发现还是解不出&…