@SpyBean可能产生的循环依赖

2,167 阅读2分钟

背景

团队最近在用Mockito写单测,原本他们都用@Mock@InjectMocks来注入成员属性, 这个写法脱离了SpringBoot test, 面对单测案例希望调用真正的Spring Bean(例如查数据库等)的场景无能为力.
SpringBoot官方文档中提到,test对Mockito做了支持,提供@MockBean@SpyBean两个注解,用途类似于@Mock@Spy, 但能够作为Spring context的一部分. 通过这种做法能够在单测里Mock掉部分逻辑,又能保持spring的功能.

问题

但是在使用@SpyBean时可能会遇到循环依赖的问题,复现代码如下:

@SpringBootTest
public class MockitoTest {
    @SpyBean
    private DevComponent devComponent;
    @Autowired
    private TestComponent testComponent;
    @Test
    public void testMockito() {
        // ...
    }
}
@Component
public class DevComponent {
    @Autowired
    private TestComponent testComponent;
}
@Component
public class TestComponent {
    @Autowired
    private DevComponent devComponent;
}

会出现以下的错误:

Error creating bean with name 'devComponent': Bean with name 'devComponent' has been injected into other beans [testComponent] 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.

分析

在之前的一篇文章里《Spring getBean是如何解决循环依赖和多次动态代理》提到了@Transactional@Async导致的循环依赖,先来复习一下.
@TransactionalAbstractAutoProxyCreator代理产生,在getEarlyBeanReference()postProcessAfterInitialization()里都会产生代理对象,即便一个Bean经历了这两个方法,都只会产生同一个代理对象.
@AsyncAbstractBeanFactoryAwareAdvisingPostProcessor代理产生,没有实现getEarlyBeanReference(),只有postProcessAfterInitialization()
如果存在循环依赖,通过getEarlyBeanReference()暴露了早期引用(例如@Transactional),而后postProcessAfterInitialization()又代理了一次Bean(例如@Async),产生了两个代理对象,就会报循环依赖异常.
在上文的例子中,MockitoPostProcessorSpyPostProcessor负责处理@MockBean@SpyBean两个注解,SpyPostProcessor的关键实现如下:

@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
   // 返回新的代理对象
   return this.mockitoPostProcessor.createSpyIfNecessary(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
   if (bean instanceof FactoryBean) {
      return bean;
   }
   // 返回新的代理对象
   return this.mockitoPostProcessor.createSpyIfNecessary(bean, beanName);
}

可以看到早期暴露(getEarlyBeanReference())和正常代理(postProcessAfterInitialization())都会产生不同的代理对象,如果两个Bean互相依赖,就会报循环依赖异常.

解决?

当然可以设allowRawInjectionDespiteWrapping=true来解决,但觉得不太优雅,容易产生问题
如果SpyPostProcessor也能像AbstractAutoProxyCreator利用缓存实现对同一个Bean只代理一次,是不是就可以了呢?我将这个疑问提交到StackOverflow,静候佳音. stackoverflow.com/questions/6…