背景
团队最近在用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导致的循环依赖,先来复习一下.
@Transactional由AbstractAutoProxyCreator代理产生,在getEarlyBeanReference()和postProcessAfterInitialization()里都会产生代理对象,即便一个Bean经历了这两个方法,都只会产生同一个代理对象.
@Async由AbstractBeanFactoryAwareAdvisingPostProcessor代理产生,没有实现getEarlyBeanReference(),只有postProcessAfterInitialization()
如果存在循环依赖,通过getEarlyBeanReference()暴露了早期引用(例如@Transactional),而后postProcessAfterInitialization()又代理了一次Bean(例如@Async),产生了两个代理对象,就会报循环依赖异常.
在上文的例子中,MockitoPostProcessor和SpyPostProcessor负责处理@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…