Dubbo Reference 单测Mock

549 阅读2分钟

背景

项目里一般用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;
    }
}