• Spirng 循环依赖报错:Requested bean is currently in creation: Is there an unresolvable circular reference?


    1:前言

    最近在项目中遇到了一次循环依赖报错的问题,虽然解决的很快,但是有些不明白的地方,特此记录。
    在此我把 bean 的结构和 注入方式单独拎出来进行演示

    1.1:报错提示

    1.2:错误日志

    Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'brokenComponent': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'tubunComponent' defined in class path resource [com/dev/config/sprngcache3/TwConfig.class]: Unsatisfied dependency expressed through method 'tubunComponent' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'denpaComponent': Unsatisfied dependency expressed through field 'tolenComponent'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'tolenComponent': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'tubunComponent': Requested bean is currently in creation: Is there an unresolvable circular reference?

    1.3:stack overflow问题描述

    Requested bean is currently in creation: Is there an unresolvable circular reference?

    点击进入 stack overflow 问题描述

    1.4:bean 依赖结构

    BrokenComponent:

    @Component
    public class BrokenComponent {
    
        @Autowired
        private TolenComponent tolenComponent;
        @Resource
        private TubunComponent tubunComponent;
    }
    

    TolenComponent:

    @Transactional
    @Component
    public class TolenComponent {
    
        @Resource
        private TubunComponent tubunComponent;
    
    }
    

    TubunComponent:

    public class TubunComponent {
    
        private DenpaComponent denpaComponent;
    
        public TubunComponent(DenpaComponent denpaComponent) {
            this.denpaComponent = denpaComponent;
        }
    }
    

    TwConfig:

    @Configuration
    public class TwConfig {
    
        @Bean
        public TubunComponent tubunComponent(DenpaComponent denpaComponent) {
            return new TubunComponent(denpaComponent);
        }
    }
    

    DenpaComponent:

    @Component
    public class DenpaComponent {
    
        @Autowired
        private TolenComponent tolenComponent;
    }
    

    除了 tubunComponent 是构造器注入,其他的bean 都是set 注入。
    分析bean依赖图可以发现确实是循环依赖的问题。

    2:问题分析

    由于本人对spring bean 的加载机制不是很清晰,这次特意花了几天时间做了梳理。

    2.1:spring 的 bean 加载过程

    2.2:spring 的三级缓存

    解决了什么问题?

    推荐博客:https://cloud.tencent.com/developer/article/1497692

    2.3:Spring 容器加载过程中比较重要的类/属性

    类名称 方法 作用
    DefaultListableBeanFactory preInstantiateSingletons Trigger initialization of all non-lazy singleton beans.../ 触发所有非懒加载的单例bean进行初始化
    AbstractBeanFactory getBean/doGetBean 从容器中获取bean
    DefaultSingletonBeanRegistry getSingleton 从缓存中获取单例实例,没有则走单例的创建流程
    DefaultSingletonBeanRegistry beforeSingletonCreation 创建单例之前会将单例放在set集合(singletonsCurrentlyInCreation)中,有检查bean是否被循环依赖的作用
    DefaultSingletonBeanRegistry beforeSingletonCreation 创建单例之后会将单例从set集合(singletonsCurrentlyInCreation)中移除
    AbstractAutowireCapableBeanFactory createBean/doCreateBean 创建单例bean
    AbstractAutowireCapableBeanFactory populateBean 对bean进行属性赋值(往往是二级缓存中的bean)
    AbstractAutowireCapableBeanFactory initializeBean 对属性赋值之后的bean进行初始化,加载RootBeanDefinition信息,这一步做完才是一个完整的可用的bean
    public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    
    	/** Cache of singleton objects: bean name to bean instance.(一级缓存,存放已初始化完毕的bean) */
    	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    	/** Cache of singleton factories: bean name to ObjectFactory.(三级缓存,存放未进行过初始化的beanFactory) */
    	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    
    	/** Cache of early singleton objects: bean name to bean instance. (二级缓存,存放已实例化,但未进行装配过属性的bean)*/
    	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
    
    	/** Set of registered singletons, containing the bean names in registration order.(存放已注册了的单例集合 = singletonObjects.size + earlySingletonObjects)*/
    	private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
    
    	/** Names of beans that are currently in creation.(存放正在被创建的实例,用于检查是否有循环依赖) */
    	private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
    
            ....
    }
    

    2.4:注册后处理器->BeanPostProcessor

    作用:对通过 BeanFactory 创建的 bean 进行属性填充。

    这是AbstractAutowireCapableBeanFactory#populateBean 方法中的某一段代码:

    for (BeanPostProcessor bp : getBeanPostProcessors()) {
    	if (bp instanceof InstantiationAwareBeanPostProcessor) {
    		InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
    		PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
    		if (pvsToUse == null) {
    			if (filteredPds == null) {
    				filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
    			}
    			pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
    			if (pvsToUse == null) {
    				return;
    			}
    		}
    		pvs = pvsToUse;
    	}
    }
    

    我打出了所有要进行轮询校验的 BeanPostProcessor

    brokenComponent 中有两个bean:

    一个是通过@Resource 注解注入的,一个是通过@Autowire 进行注入的,奉劝各位同学千万不要两种混用啊!!!


    2.4.1:CommonAnnotationBeanPostProcessor

    /**
     * ... 
     *<p>The central element is the {@link javax.annotation.Resource} annotation
     * for annotation-driven injection of named beans, by default from the containing
     * Spring BeanFactory, with only {@code mappedName} references resolved in JNDI.
     * The {@link #setAlwaysUseJndiLookup "alwaysUseJndiLookup" flag} enforces JNDI lookups
     * equivalent to standard Java EE 5 resource injection for {@code name} references
     * and default names as well. The target beans can be simple POJOs, with no special
     * requirements other than the type having to match.
     *
     * <p>The JAX-WS {@link javax.xml.ws.WebServiceRef} annotation is supported too,
     * analogous to {@link javax.annotation.Resource} but with the capability of creating
     * specific JAX-WS service endpoints. This may either point to an explicitly defined
     * resource by name or operate on a locally specified JAX-WS service class. Finally,
     * this post-processor also supports the EJB 3 {@link javax.ejb.EJB} annotation,
     * analogous to {@link javax.annotation.Resource} as well, with the capability to
     * specify both a local bean name and a global JNDI name for fallback retrieval.
     * The target beans can be plain POJOs as well as EJB 3 Session Beans in this case.
     * ...
     */
    public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor
    		implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {      
            ...
    	@Override
    	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    		InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
    		try {
    			metadata.inject(bean, beanName, pvs);
    		}
    		catch (Throwable ex) {
    			throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
    		}
    		return pvs;
    	}  
    }
    

    通过翻译及反复debug验证,这个类将对某个bean中所有被 @Resource 注解修饰的属性进行填充。


    2.4.2:AutowiredAnnotationBeanPostProcessor

    /**
     * {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation
     * that autowires annotated fields, setter methods and arbitrary config methods.
     * Such members to be injected are detected through a Java 5 annotation: by default,
     * Spring's {@link Autowired @Autowired} and {@link Value @Value} annotations.
     *
     * <p>Also supports JSR-330's {@link javax.inject.Inject @Inject} annotation,
     * if available, as a direct alternative to Spring's own {@code @Autowired}.
     * ...
     * @see Autowired
     * @see Value
     */
    public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
    		implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
            ...
    	@Override
    	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    		InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    		try {
    			metadata.inject(bean, beanName, pvs);
    		}
    		catch (BeanCreationException ex) {
    			throw ex;
    		}
    		catch (Throwable ex) {
    			throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    		}
    		return pvs;
    	}
    }
    

    这个类将对某个bean中所有被 @Autowire@Value 注解修饰的属性进行填充。


    2.4.3:@Autowire VS @Resource

    1:无论使用@Resource 还是 @Resource,都不影响bean的初始化顺序。

    2:@Resource修饰的属性 要优先于 @Autowire修饰的属性 进行初始化。

    2.5:报错流程

    1: getBean("brokenComponent") -> getSingleton("brokenComponent") -> beforeSingletonCreation("brokenComponent") -> createBean("brokenComponent") -> 
    
    addSingletonFactory("brokenComponent") ->  inject() ->
    
    2: getBean("tubunComponent",TubunComponent.class) -> getSingleton("tubunComponent") -> beforeSingletonCreation("tubunComponent") -> 
    
    3:getBean("twConfig") -> getSingleton("twConfig") -> beforeSingletonCreation("twconfig") -> addSingletonFactory("twConfig") -> afterSingletonCreation("twConfig")
    
    4:getBean("denpaComponent") -> getSingleton("denpaComponent") -> beforeSingletonCreation("denpaComponent") -> createBean("denpaComponent") ->
    
    addSingletonFactory("denpaComponent") -> inject() ->
    
    5:getBean("tolenComponent") -> getSingleton("tolenComponent") -> beforeSingletonCreation("tolenComponent") -> createBean("tolenComponent") ->
    
    addSingletonFactory("tolenComponent") -> inject() ->
    
    6:getBean("tubunComponent",TubunComponent.class) -> getSingleton("tubunComponent") -> beforeSingletonCreation("tubunComponent") -> 报错
    
    7:afterSingletonCreation("tolenComponent") -> afterSingletonCreation("denpaComponent") -> afterSingletonCreation("tubunComponent") -> afterSingletonCreation("brokenComponent")
    

    可以看到 tubunComponent 进行了两次 getSingleton,经过循环依赖检查的时候报错了。


    2.6:错误流程分析归纳

    1:Spring 容器先加载 brokenComponent 这个 bean。

    2:brokenComponent依赖了tubunComponent(@Resource修饰),因此优先填充 tubunComponent,因此去初始化tubunComponent 这个bean,并将tubunComponent 存放在 DefaultSingletonBeanRegistry.singletonsCurrentlyInCreation 集合中。

    3:tubunComponent依赖了depenComponent,因此去初始化depenComponent 这个bean,并将depenComponent 存放在 DefaultSingletonBeanRegistry.singletonsCurrentlyInCreation 集合中。

    4:depenComponent依赖了tolenComponent,因此去初始化depenComponent 这个bean,并将tolenComponent 存放在 DefaultSingletonBeanRegistry.singletonsCurrentlyInCreation 集合中。

    5:tolenComponent依赖了tubunComponent,因此去初始化tubunComponent 这个bean,然而当将tubunComponent 存放在 DefaultSingletonBeanRegistry.singletonsCurrentlyInCreation 集合中时发现 tubunComponent 已经存在了,
    从而判断这几个bean 产生了循环依赖并跑出异常。


    beforeSingletonCreation(String beanName) 方法源码:

    /**
     * Callback before singleton creation.
     * <p>The default implementation register the singleton as currently in creation.
     * @param beanName the name of the singleton about to be created
     * @see #isSingletonCurrentlyInCreation
     */
    protected void beforeSingletonCreation(String beanName) {
    	if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
    		throw new BeanCurrentlyInCreationException(beanName);
    	}
    }
    

    2.7:解决方式

    1:使用@Lazy 修饰。

    2:这其实是一个操作不当造成的问题,因为spring的三级缓存已经解决了set注入导致的循环依赖,而这几个bean并不是全部都是使用构造器注入。

    在 brokeComponent 中使用@Autowired修饰了tolenComponent,有用@Resource 修饰了tubunComponent,
    导致tubunComponent要优先于tolenComponent优先加载,而tolenComponent 又依赖了tubunComponent,因此报错。
    我们只需要让tolenComponent 优先于 tubunComponent 优先加载就可以了。

    实验证明:

    1:@Resource 修饰 tolenComponent + @Resource 修饰 tubunComponent 的组合不会报错。
    2:@Resource 修饰 tolenComponent + @Autowired 修饰 tubunComponent 的组合不会报错。
    3:@Autowired 修饰 tolenComponent + @Autowired 修饰 tubunComponent 的组合不会报错。

    偏偏使用了会报错的一种组合......

  • 相关阅读:
    0916 编程实验一 词法分析程序 总结
    0916 编程实验一 词法分析程序
    0909编译
    C语言文法
    词法分析编译感想
    词法分析
    0909 编译原理
    0429团队3.0
    0428 团队项目合作2.0作业
    0422 数学口袋精灵app
  • 原文地址:https://www.cnblogs.com/zgq7/p/14031910.html
Copyright © 2020-2023  润新知