乐趣区

关于java:太强大了Feign对接YAPI实现自动Mock

原创:猿天地(微信公众号 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 微服务 入门 实战与进阶》作者, 公众号 猿天地 发起人。

退出移动版