原创:猿天地(微信公众号 ID:cxytiandi),欢送分享,转载请保留出处。
后面咱们介绍了在微服务架构下如何解决单测时 Mock 的问题,通过 Mock 能够在单测时不依赖其余服务的实现。在文章最初我也给大家提供了一个思考题: 是不是能够模仿前端对后端的解决形式,走 Yapi 的 Mock 性能? 这样就不必本人手动的对每个接口去 Mock 了。
首先咱们须要定义一个 Mock 的配置类,用于配置哪些近程调用须要进行 Mock。
@Data
@Configuration
@ConfigurationProperties(prefix = "mock")
public class ApiMockProperties {
/**
* 资源:mock 地址
* 格局:GET:http://user-provider/user/{userId}##http://xxx.com/mock/api/1001
*/
private List<String> apis;
public String getMockApi(String resource) {if (CollectionUtils.isEmpty(apis)) {return null;}
Map<String, String> apiMap = apis.stream().collect(Collectors.toMap(s -> {return s.split("##")[0];
}, s -> s.split("##")[1]));
return apiMap.get(resource);
}
}
比方咱们的 Feign Client 定义如下:
@FeignClient(name = "kitty-cloud-user-provider")
public interface UserRemoteService {
/**
* 依据用户 ID 查问用户
* @param userId 用户 ID
* @return
*/
@GetMapping("/users/{userId}")
ResponseData<UserResponse> getUser(@PathVariable("userId") Long userId);
}
那么资源的格局就是 GET:http://kitty-cloud-user-provider/users/{userId},以 2 个 #作为分隔符,前面接上 Mock 的地址。
配置格局如下:
mock.apis[0]=GET:http://kitty-cloud-user-provider/users/{userId}##http://yapi.cxytiandi.com/mock/74/v1/user
配置好了后就须要想方法对 Feign 进行扩大,如果调用的接口在 Mock 配置中,就走 Mock 的地址。
对 Feign 扩大能够参考 Sleuth 中的做法,具体代码就不贴了,文末贴上残缺源码参考地址。
新增一个类,继承 LoadBalancerFeignClient,重写 execute 办法。须要判断以后执行的接口是否在 Mock 名单中,如果在就执行 Mock 操作。
public class MockLoadBalancerFeignClient extends LoadBalancerFeignClient {
private ApiMockProperties apiMockProperties;
public MockLoadBalancerFeignClient(Client delegate, CachingSpringLoadBalancerFactory lbClientFactory,
SpringClientFactory clientFactory, ApiMockProperties apiMockProperties) {super(delegate, lbClientFactory, clientFactory);
this.apiMockProperties = apiMockProperties;
}
@Override
public Response execute(Request request, Request.Options options) throws IOException {RequestContext currentContext = ContextHolder.getCurrentContext();
String feignCallResourceName = currentContext.get("feignCallResourceName");
String mockApi = apiMockProperties.getMockApi(feignCallResourceName);
if (StringUtils.hasText(feignCallResourceName) && StringUtils.hasText(mockApi)) {Request newRequest = Request.create(request.httpMethod(),
mockApi, request.headers(), request.requestBody());
return super.getDelegate().execute(newRequest, options);
} else {return super.execute(request, options);
}
}
}
feignCallResourceName 是通过 ThreadLocal 来传递的,如果没有 Restful 格调的 API 就不必这样做了,直接判断 url 即可。
Restful 的须要获取到原始的 uri 定义才行,不然就是 /users/1, /users/2 没方法判断是否在 Mock 名单中。
所以咱们得改改底层的代码,因为我的项目中用的是 Sentinel 做熔断,所以在接口调用的时候会先进入 Sentinel 的 SentinelInvocationHandler,咱们能够在这个类中进行资源名称的获取,而后通过 ThreadLocal 进行透传。
这样在 LoadBalancerFeignClient 中就能够获取资源名而后去 Mock 名单中判断了。
String resourceName = methodMetadata.template().method().toUpperCase()
+ ":" + hardCodedTarget.url() + methodMetadata.template().path();
RequestContext requestContext = ContextHolder.getCurrentContext();
requestContext.add("feignCallResourceName", resourceName);
Entry entry = null;
try {ContextUtil.enter(resourceName);
entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
result = methodHandler.invoke(args);
}
外围代码并不多,当然也省略了如何去替换 LoadBalancerFeignClient 的相干代码,残缺源码地址如下:https://github.com/yinjihuan/kitty/tree/feature/1.0/kitty-servicecall/kitty-servicecall-feign/src/main/java/com/cxytiandi/kitty/servicecall/feign
对于作者:尹吉欢,简略的技术爱好者,《Spring Cloud 微服务 - 全栈技术与案例解析》,《Spring Cloud 微服务 入门 实战与进阶》作者, 公众号 猿天地 发起人。