背景
项目里一般用Spring+Dubbo, 单测使用Test4+JMock。 对于Dubbo依赖的接口,一般有两种方式注入:
1. xml定义+Autowired注入
在dubbo-service.xml中定义Bean对象
<dubbo:reference id="helloService" check="false" interface="org.juejin.HelloService"/>
在服务中自动注入
public class HomeController {
/**
* 通过spring的依赖注入
*/
@Autowired
private HelloService helloService;
}
对于这种方式依赖的HelloService,可以在单测中不导入dubbo-service.xml,在单测配置类中定义mock类。 代码如下
@Configuration
public class MockBeanConfig {
@Bean
public HelloService helloService() {
return Mockitio.mock(HelloService.class);
}
}
2. 通过@Reference自动注入
dubbo已提供了通过注解自动注入依赖的方式, 如下:
public class HomeController {
/**
* 通过spring的依赖注入
*/
@Reference(check = false, protocol="dubbo")
private HelloService helloService;
}
对于通过@Reference注入的接口,在单测时需要通过从Spring容器中获取Mock类替换,而不是让dubbo通过远程代理注入。为实现对@Reference注解的扫描替换,可以继承Dubbo中的AnnotationInjectedBeanPostProcessor。该Processor可实现对指定注解扫描注入。
public class MockReferenceAnnotationBeanPostProcessor extends
AnnotationInjectedBeanPostProcessor<Reference>
implements ApplicationContextAware {
private ApplicationContext applicationContext;
/**
* 替换掉 AnnotationInjectedBeanPostProcessor 中
* 先buildInjectedObjectCacheKey获取key在本地缓存中找dubbo代理对象
* 然后再通过doGetInjectedBean 生成dubbo代理对象的过程
* 直接用springContext中mock的bean
* @param reference @Reference
* @param bean 注入的bean
* @param beanName 注入的bean
* @param injectedType 被注入的类型
* @param injectedElement 被注入的元素描述
* @return
* @throws Exception
*/
@Override
protected Object doGetInjectedBean(Reference reference, Object bean,
String beanName, Class<?> injectedType,
InjectionMetadata.InjectedElement injectedElement) throws Exception {
ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
// 优先按 名字+类型 找
Object injectedObj = null ;
String injectedBeanName = injectedElement.getMember().getName();
try {
injectedObj = beanFactory.getBean(injectedBeanName, injectedType);
} catch (BeansException beansException) {
log.debug("not found bean for name:{}", injectedBeanName);
}
if (null == injectedObj) {
// 按类型再取一次
injectedObj = beanFactory.getBean(injectedType);
}
return injectedObj;
}
/**
* 参照 ReferenceAnnotationBeanPostProcessor 复制一份
* 返回bean在本地缓存的唯一key
* @param reference
* @param bean
* @param beanName
* @param injectedType
* @param injectedElement
* @return
*/
@Override
protected String buildInjectedObjectCacheKey(Reference reference,
Object bean,
String beanName,
Class<?> injectedType,
InjectionMetadata.InjectedElement injectedElement) {
String serviceBeanName = ServiceBeanNameBuilder
.create(reference, injectedType, applicationContext.getEnvironment())
.build();
return serviceBeanName +
"#source=" + (injectedElement.getMember()) +
"#attributes=" + AnnotationUtils.getAttributes(reference, getEnvironment(), true);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}