一、需求分析
什么是 Mock?
RPC 框架的核心功能是调用其他远程服务。但是在实际开发和测试过程中,有时可能无法直接访问真实的远程服务,或者访问真实的远程服务可能会产生不可控的影响,例如网络延迟、服务不稳定等。在这种情况下,就需要使用 mock 服务来模拟远程服务的行为,以便进行接口的测试、开发和调试。
mock 是指模拟对象,通常用于测试代码中,特别是在单元测试中,便于我们跑通业务流程。
举个例子,用户服务要调用订单服务,伪代码如下:
1 2 3 4 5 6 7 8
| class UserServiceImpl {
void test() { doSomething(); orderService.order(); doSomething(); } }
|
如果订单服务还没上线,那么这个流程就跑不通,只能先把调用订单服务的代码注释掉。
但如果给 orderService 设置一个模拟对象,调用它的 order 方法时,随便返回一个值,就能继续执行后续代码,这就是 mock 的作用。
为什么要支持 Mock?
虽然 mock 服务并不是 RPC 框架的核心能力,但是它的开发成本并不高。而且给 RPC 框架支持 mock 后,开发者就可以轻松调用服务接口、跑通业务流程,不必依赖真实的远程服务,提高使用体验,何乐而不为呢?
我们希望能够用最简单的方式 —— 比如一个配置,就让开发者使用 mock 服务。
二、设计方案
前面也提到了,mock 的本质就是为要调用的服务创建模拟对象。
如何创建模拟对象呢?
之前提到了一种动态创建对象的方法 —— 动态代理。之前是通过动态代理创建远程调用对象。同理,我们可以通过动态代理创建一个 调用方法时返回固定值 的对象。
三、开发实现
1)我们可以支持开发者通过修改配置文件的方式开启 mock,那么首先给全局配置类 RpcConfig
新增 mock 字段,默认值为 false。
修改的代码如下:
1 2 3 4 5 6 7 8 9
| @Data public class RpcConfig { ...
private boolean mock = false; }
|
2)在 Proxy 包下新增 MockServiceProxy
类,用于生成 mock 代理服务。
在这个类中,需要提供一个根据服务接口类型返回固定值的方法。
完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
@Slf4j public class MockServiceProxy implements InvocationHandler {
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Class<?> methodReturnType = method.getReturnType(); log.info("mock invoke {}", method.getName()); return getDefaultObject(methodReturnType); }
private Object getDefaultObject(Class<?> type) { if (type.isPrimitive()) { if (type == boolean.class) { return false; } else if (type == short.class) { return (short) 0; } else if (type == int.class) { return 0; } else if (type == long.class) { return 0L; } } return null; } }
|
在上述代码中,通过 getDefaultObject
方法,根据代理接口的 class 返回不同的默认值,比如针对 boolean 类型返回 false、对象类型返回 null 等。
3)给 ServiceProxyFactory
服务代理工厂新增获取 mock 代理对象的方法 getMockProxy
。可以通过读取已定义的全局配置 mock
来区分创建哪种代理对象。
修改 ServiceProxyFactory,完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
public class ServiceProxyFactory {
public static <T> T getProxy(Class<T> serviceClass) { if (RpcApplication.getRpcConfig().isMock()) { return getMockProxy(serviceClass); }
return (T) Proxy.newProxyInstance( serviceClass.getClassLoader(), new Class[]{serviceClass}, new ServiceProxy()); }
public static <T> T getMockProxy(Class<T> serviceClass) { return (T) Proxy.newProxyInstance( serviceClass.getClassLoader(), new Class[]{serviceClass}, new MockServiceProxy()); } }
|