1.Bean之注销与动态注册实现服务mock(应用篇)

前面一篇博文介绍了动态注册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. 项目