专栏首页技术问题总结Spring循环依赖问题修复
原创

Spring循环依赖问题修复

Spring循环依赖问题修复

拆分的时候,把错误都处理完后,准备把工程起起来,发现弹簧的循环依赖问题。具体问题如下

Bean with name 'userManager' has been injected into other beans [daoAuthenticationProvider] 
in its raw version as part of a circular reference, but has eventually been wrapped
(for example as part of auto-proxy creation). 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 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.

1.怀疑配置文件的问题

但是在原工程中并没有这个问题,所以一开始怀疑是配置文件的配置不一样,百度了一下这个错误

beanFactory.setAllowRawInjectionDespiteWrapping(true);

看网上说这个配置了,对于循环依赖的这个错误就会解决掉。但是在两个工程里搜索了一下都没有发现这个配置过。

于是只能调试进去看看

2.调查查看分析

2.1 spring引用的bean和注入的bean不一致导致的这个错误

由于在原工程里是可以循环引用的,所以对工程和新工程都在初始化这两个循环引用的位置进行了调试

然后发现最后两边走的逻辑不一样的在以下的代码里:

AbstractAutowireCapableBeanFactory.doCreateBean()final String beanName, 
final RootBeanDefinition mbd, final Object[] args:

...
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
    if (exposedObject == bean) {
        // 原工程走到了这里
        exposedObject = earlySingletonReference;
    }
    else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
        // 新的有error的bean走到里这里
        String[] dependentBeans = getDependentBeans(beanName);
        Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
        for (String dependentBean : dependentBeans) {
            if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                actualDependentBeans.add(dependentBean);
            }
        }
        if (!actualDependentBeans.isEmpty()) {
            throw new BeanCurrentlyInCreationException(beanName,
                    "Bean with name '" + beanName + "' has been injected into other beans [" +
                    StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                    "] 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 " +
                    "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
        }
    }
}

从这里已经可以看到,是新工程中的exposedObject和bean不一样导致的

而这两者的关系如下面的代码

// Initialize the bean instance.
    Object exposedObject = bean;
    try {
        populateBean(beanName, mbd, instanceWrapper);
        if (exposedObject != null) {
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
    }
    catch (Throwable ex) {
        if (ex instanceof BeanCreationException && beanName.equals
        (((BeanCreationException) ex).getBeanName())) {
            throw (BeanCreationException) ex;
        }
        else {
            throw new BeanCreationException(mbd.getResourceDescription(),
             beanName, "Initialization of bean failed", ex);
        }
    }

也就是说exposedObject在initializeBean函数之后变掉了

2.2 AnnotationAwareAspectJAutoProxyCreator把返回值修改了

然后发现在applyBeanPostProcessorsAfterInitialization函数中,AnnotationAwareAspectJAutoProxyCreator修改了返回的结果

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (!this.earlyProxyReferences.contains(cacheKey)) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

逻辑如上,也就是说earlyProxyReferences这个里不存在这个cacheKey

2.3怀疑是自定义的annotaion修改导致

因为函数中,AnnotationAwareAspectJAutoProxyCreator是处理annotaion的相关。需要预处理代理。

往远工程里加了这个annatation,但是调试发现原工程里的这样的annotaion也没有问题

2.4配置文件里起了两个AnnotationAwareAspectJAutoProxyCreator,才导致了这个问题

看了一下earlyProxyReferences会在哪一步进行put进入。

发现在Factory.getObject()的时候会调用。然后断点到put的地方,也确实put进入了

但是再调试到postProcessAfterInitialization的时候,发现包就是不对

然后看了下看了一下earlyProxyReferences的值,发现居然有两个AnnotationAwareAspectJAutoProxyCreator

然后干掉之后确实是可以的

3.两个AnnotationAwareAspectJAutoProxyCreator导致这个问题的原因

因为调用actory.getObject()时。调用下面的方法,

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp =
                     (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                    if (exposedObject == null) {
                        return exposedObject;
                    }
                }
            }
        }
        return exposedObject;
    }

就会导致两个的AnnotationAwareAspectJAutoProxyCreator的earlyProxyReferences中含有不一样的代理对象

而在最后匹配时的逻辑

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
            throws BeansException {

        Object result = existingBean;
        for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
            result = beanProcessor.postProcessAfterInitialization(result, beanName);
            if (result == null) {
                return result;
            }
        }
        return result;
    }

第二个AnnotationAwareAspectJAutoProxyCreator发现earlyProxyReferences不存在第一个的代理对象的值,返回自己的代理对象,结果导致不一样

解决方法

干掉一个AnnotationAwareAspectJAutoProxyCreator,这个循环依赖的错误,就处理了

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • LeetCode 74. Search a 2D Matrix题目分析代码

    本题主要考察的是二分法,但是这里如何使用二分法又有不同的策略,但本质都是一样的 方法一,我们二分,先从每列的最后一个数开始,确定它的列再搜索行 方法二,就是...

    desperate633
  • Spring Ioc 之 Bean的加载(一)

    当我们显示或者隐式地调用 BeanFactory#getBean(String name) 方法时,则会触发加载 Bean 阶段。代码如下:

    大王叫下
  • 深入理解ConcurrentHashMap

    前言 本文的分析的源码是JDK8的版本,上一节我们介绍了HashMap,提到了多线程避免扩容时出现死循环,时要使用ConcurrentHashMap,下面我来讲...

    胖虎
  • DSL 系列(2) - 插件的论述与实现

    本文主要探讨基于 DSL(domain specific language) 之上的插件设计,他们是领域的附属,为领域提供额外的服务,但领域不依赖于他们。

    捷义
  • Spring生命周期

    主要的作用就是添加了一个后置处理器ServletContextAwareProcessor

    爱撒谎的男孩
  • 统计UPD丢包工具

    下载位置:https://github.com/eyjian/libmooon/tree/master/shell

    一见
  • 记录下多个BeanPostProcessor代理同个Bean的问题

    在去年研发XX项目时,需要一种字节码增强机制,用于增强HSF、Tair、TDDL等相关类,用于信息采集。当时考虑了好几种方案,也踩到了一些坑,特别是关于Spri...

    LNAmp
  • 潜在语义分析(Latent Semantic Analysis,LSA)

    非负矩阵分解(non-negative matrix factorization,NMF)是另一种矩阵的因子分解方法,其特点是分解的矩阵非负。非负矩阵分解也可以...

    Michael阿明
  • 手撕OpenCV源码之高斯模糊

    从上述代码的大致分析中可以知道,OpenCV的GaussianBlur本质上依然是filter2D,只是针对一些特殊情况进行了GPU和CPU版本的优化,如果输入...

    小白学视觉
  • Java基础语法总结

    https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151....

    Noneplus

扫码关注云+社区

领取腾讯云代金券