1.Bean之注销与动态注册实现服务mock(应用篇)
Categories:
少于1分钟
前面一篇博文介绍了动态注册Bean的姿势,看完之后难免会有个疑问,在我n年的业务开发中,还真没遇到过需要自己来注册bean的场景(常年的if-else, curd还真不可能遇到)那么这个东西到底有什么用,或者可以给我们打开哪些思路呢?
本篇博文将以应用的角度,简单的演示一下可以怎么用
I. 应用说明
1. 背景
在实际的业务开发中,一个需求来了,我需要依赖第三方提供的接口,但实际的情况可能是对方还没开发好,接口没法提供,这个时候我要测试自己的功能可以怎么做?
- 在依赖的接口上做特殊处理,不直接调用接口,直接返回mock的结果
- 测试用例中可以使用MockService来替换某些服务
上面两个可以说是比较常见的使用手段了,再把上面的case进行扩展下,假设我现在提供的一个web服务,正常访问接口是要求用户登录的;但是我希望在本地测试环境下,不登录也可以访问(即给一个默认的登录账号)
针对这个场景进行分析,一是要求本地正常启动服务;二是登录服务默认返回true
2. 方案
对上面的场景进行简单化,实例说明
即我有一个web服务,每次访问,都依赖了UserService根据用户名获取用户ID;
要求在本地环境下测试时,使用mock的UserService返回用户id,模拟已经登录的情况
在非本地环境,则通过rpc调用用户服务来走具体的业务流程
对于上面的这个case可以怎么实现呢?
结合主题,判断当前环境,如果是本地,则删除Spring容器中的UserService的Bean,然后将自己创建的模拟UserService类注册到Bean中,使其他对UserService的引用,替换为mock的UserService
3. 实现
根据上面的实现,首先是定义一个UserService的接口类
public interface IUserService {
Integer getUserId(String uname);
}
给它一个默认实现,表示在正常环境中,实际调用的都是 UserServiceImpl
@Service("userService")
public class UserServiceImpl implements IUserService {
@Override
public Integer getUserId(String uname) {
return 1;
}
}
给一个测试的服务
@RestController
@RequestMapping(path = "mock")
public class MockRest {
@Autowired
private IUserService userService;
@GetMapping(path = "id")
public String getId(@RequestParam String name) {
return userService.getUserId(name).toString();
}
}
正常情况下,上面的rest服务访问时,每次都应该返回1,即调用的是默认的UserServiceImpl
现在我们就需要加上一个逻辑,如果是本地环境时,使用自己创建的UserService来替换,也就是说这里涉及到了一个bean的注销和手动注册Bean,借助前面的知识也比较好实现了
@Configuration
public class UserServiceMockConfig implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory)
throws BeansException {
// 先删除容器中的Bean定义
((DefaultListableBeanFactory) factory).removeBeanDefinition("userService");
// 创建mock的Bean,并注册到Spring容器
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(IUserService.class, () -> uname -> {
Random random = new Random();
return random.nextInt(1024);
});
BeanDefinition beanDefinition = builder.getRawBeanDefinition();
((DefaultListableBeanFactory) factory).registerBeanDefinition("userService", beanDefinition);
}
}
上面手动注册的一个生成的匿名UserService类,内部返回的随机的userId, 因此在本地环境启用时,每次调用前面的rest服务时,返回随机的userId,而不是固定的1
4. 扩展
上面只是给出了一个简单的应用场景和实现,在实际的工程中有没有这样的case呢?
在使用SprigCloud的Feign时,就感觉到了这种思路,Feign封装了SpringCloud的RPC调用方式,定义一个接口,对于使用者而言,可以注入这个接口,然后像调用本地方法一样调用执行rpc调用
这里面必然就涉及到接口的代理类生成与注册的问题,而这个过程肯定不会是Spring框架来完成的,也就只有可能是FeignClient来包装的,目前还没有看Feign的源码,所以也不好下结论,也就只能直观的分析,这里面应该少不了Bean的动态注册手段了;关于底层是否如预期这般,静候后续源码分析
II. 其他
0. 项目
- 工程:spring-boot-demo
- module: 006-dynamicbean
反馈
这篇文章对您有帮助么?
Glad to hear it! Please tell us how we can improve/告诉作者如何改进.
Sorry to hear that. Please tell us how we can improve/告诉作者如何改进.