1. 背景介绍
首先大家应该都明白SpringBoot中循环依赖是怎么产生的,简单来说就是 A引入B, B引入A
故事的发生是这样的,我有一位同事是写了类似于下面这样的代码
java
@Component
public class OrdersService implements ApplicationContextAware {
@Resource
private OrdersGoodsService ordersGoodsService;
}
@Component
public class OrdersGoodsService {
@Resource
private UserService userService;
}
@Component
public class UserService {
@Resource
private OrdersService ordersService;
}
代码很简单,明显就是 OrdersService
-> OrdersGoodsService -> UserService -> OrdersService
,由于项目是关闭了处理循环依赖的功能,所以就爆出来了以下的错误
当然这个同事也是知道这是循环依赖,所以他去谷歌寻求了答案,于是将OrdersService
改造了一下,变成了下面这个样子
然后点击运行,离谱的事情就发生了,控制台没有打印循环依赖的标记,程序还是正常的启动,只是这个map中是没有OrdersGoodsService
的实例的
于是我同事就开始怀疑人生,然后就找上了我,命运的齿轮就此开始转动
2. 分析问题
由于此时我同事只给我说了通过ApplicationContext
获取不到OrdersGoodsService
实例,我初步就往实例没有注册到容器中去分析了
于是我自信的来到ConfigurationClassParser
的doProcessConfigurationClass
方法中开始分析,这个方法是判断@ComponentScans
注解有没有扫描到我们的实例
然后经过我一顿瞎分析后,发现我们的OrdersService
, OrdersGoodsService, UserService,都已经转为了BeanDefinition了
此时我眉头一皱,发现事情有点棘手了,我一想是不是这个OrdersService
本身有点问题,然后我去检查检查,发现除了业务代码,也就只有把applicationContext.getBeansOfType(OrdersGoodsService.class);
当成@Resource
来用,然后形成循环依赖
的可能了,但是控制台又没有提示
这个时候就没办法了,只能看OrdersService
是怎么注册的了
SpringBoot默认注册的Bean工厂是DefaultListableBeanFactory
,这个时候我们来到其doGetBean(...)
方法中,这个方法正是创建Bean的入口方法
此时我通过Debug + 堆栈分析:
- 程序是先初始化OrdersGoodsService发现有一个依赖需要注入也就是UserService
- 然后UserService发现也有一个依赖需要注入也就是
OrdersService
- 此时由于SpringBoot会将正在创建的Bean保存起来,也就是说
OrdersGoodsService
和UserService
对应Bean名称就会在下面的集合中了,这里就是重点 ,一定要记住
- 此时由于SpringBoot会将正在创建的Bean保存起来,也就是说
- 然后当
OrdersService
初始化完成后会执行ApplicationContextAwareProcessor的invokeAwareInterfaces(...)
方法
最终程序会到OrdersService
的setApplicationContext()
中去执行getBeansOfType(...)
方法了
然后我们看最底层的getBeansOfType(...)
方法,如果看过这块源码的观众就已经明白了原因了,这里竟然try catch 了一个BeanCreationException
异常
java
public <T> Map<String, T> getBeansOfType(@Nullable Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
throws BeansException {
String[] beanNames = getBeanNamesForType(type, includeNonSingletons, allowEagerInit);
Map<String, T> result = new LinkedHashMap<>(beanNames.length);
for (String beanName : beanNames) {
try {
Object beanInstance = getBean(beanName);
if (!(beanInstance instanceof NullBean)) {
result.put(beanName, (T) beanInstance);
}
}
catch (BeanCreationException ex) {
Throwable rootCause = ex.getMostSpecificCause();
if (rootCause instanceof BeanCurrentlyInCreationException) {
BeanCreationException bce = (BeanCreationException) rootCause;
String exBeanName = bce.getBeanName();
if (exBeanName != null && isCurrentlyInCreation(exBeanName)) {
if (logger.isTraceEnabled()) {
logger.trace("Ignoring match to currently created bean '" + exBeanName + "': " +
ex.getMessage());
}
onSuppressedException(ex);
// Ignore: indicates a circular reference when autowiring constructors.
// We want to find matches other than the currently created bean itself.
continue;
}
}
throw ex;
}
}
return result;
}
然后大家按照我写的方法,进入getBean(...)
-> doGetBean(...)
-> getSingleton(...)
-> beforeSingletonCreation(...)
方法
这个方法中的两个集合正是我上面讲的保存了正在创建Bean名称的集合,然后这里就会抛出BeanCurrentlyInCreationException
然后这个异常就会被getBeansOfType(...)
中的try catch捕获到,然后continue
, 最终我们得到一个没有实例的Map
分析到这里彻底真相大白了,原来就是这个getBeansOfType(...)
方法自己做主,自己捕获了异常,导致异常没有继续往上抛,也就无法在控制台打印日志了
3. 总结
由于Bean工厂的getBeansOfType(...)
方法会自己捕获异常,所以一般情况下就算我们要用Bean工厂获取实例,也可以使用getBean()
方法来获取,在这种情况下就会提示循环依赖了
当然就算是项目中关闭了处理循环依赖的机制,我们依旧可以操作
大家想想,循环依赖是怎么产生的,是在Bean初始化阶段出现的,那如果说当所有
Bean初始化完成后才开始进行手动注入呢?
我们可以借助SpringBoot的监听器机制,监听特定的事件,就像下面的代码一样,在容器初始化完毕后才注入OrdersGoodsService
注意:
- 循环依赖本身就是代码没有写好的情况,这种借助事件机制来
逃避
循环依赖本身就是一种能力不足的体现