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:
@Componentpublic class BrokenComponent { @Autowired private TolenComponent tolenComponent; @Resource private TubunComponent tubunComponent;}TolenComponent:
@Transactional@Componentpublic class TolenComponent { @Resource private TubunComponent tubunComponent;}TubunComponent:
public class TubunComponent { private DenpaComponent denpaComponent; public TubunComponent(DenpaComponent denpaComponent) { this.denpaComponent = denpaComponent; }}TwConfig:
@Configurationpublic class TwConfig { @Bean public TubunComponent tubunComponent(DenpaComponent denpaComponent) { return new TubunComponent(denpaComponent); }}DenpaComponent:
@Componentpublic 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 { private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); private final Set<String> registeredSingletons = new linkedHashSet<>(256); 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
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
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) 方法源码:
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 的组合不会报错。
偏偏使用了会报错的一种组合......
