本系列代码地址: https://github.com/JoJoTec/spring-cloud-parent
首先,我们给出官方文档中的组件结构图:
官方文档中的组件,是以实现功能为维度的,我们这里是以源码实现为维度的(因为之后我们使用的时候,需要根据需要定制这些组件,所以需要从源码角度去拆分分析),可能会有一些小差异。
负责解析类元数据的 Contract
OpenFeign 是通过代理类元数据来自动生成 HTTP API 的,那么到底解析哪些类元数据,哪些类元数据是有效的,是通过指定 Contract 来实现的,我们可以通过实现这个 Contract 来自定义一些类元数据的解析,例如,我们自定义一个注解:
//仅可用于方法上 @java.lang.annotation.Target(METHOD) //指定注解保持到运行时 @Retention(RUNTIME) @interface Get { //请求 uri String uri(); }
这个注解很简单,标注了这个注解的方法会被自动封装成 GET 请求,请求 uri 为 uri()
的返回。
然后,我们自定义一个 Contract 来处理这个注解。由于 MethodMetadata
是 final 并且是 package private 的,所以我们只能继承 Contract.BaseContract
去自定义注解解析:
//外部自定义必须继承 BaseContract,因为里面生成的 MethodMetadata 的构造器是 package private 的 static class CustomizedContract extends Contract.BaseContract { @Override protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) { //处理类上面的注解,这里没用到 } @Override protected void processAnnotationOnMethod(MethodMetadata data, Annotation annotation, Method method) { //处理方法上面的注解 Get get = method.getAnnotation(Get.class); //如果 Get 注解存在,则指定方法 HTTP 请求方式为 GET,同时 uri 指定为注解 uri() 的返回 if (get != null) { data.template().method(Request.HttpMethod.GET); data.template().uri(get.uri()); } } @Override protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) { //处理参数上面的注解,这里没用到 return false; } }
然后,我们来使用这个 Contract:
interface HttpBin { @Get(uri = "/get") String get(); } public static void main(String[] args) { HttpBin httpBin = Feign.builder() .contract(new CustomizedContract()) .target(HttpBin.class, "http://www.httpbin.org"); //实际上就是调用 http://www.httpbin.org/get String s = httpBin.get(); }
一般的,我们不会使用这个 Contract,因为我们业务上一般不会自定义注解。这是底层框架需要用的功能。比如在 spring-mvc 环境下,我们需要兼容 spring-mvc 的注解,这个实现类就是 SpringMvcContract
。
编码器 Encoder 与解码器 Decoder
编码器与解码器接口定义:
public interface Decoder { Object decode(Response response, Type type) throws IOException, DecodeException, FeignException; } public interface Encoder { void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException; }
OpenFeign 可以自定义编码解码器,我们这里使用 FastJson 自定义实现一组编码与解码器,来了解其中使用的原理。
/** * 基于 FastJson 的反序列化解码器 */ static class FastJsonDecoder implements Decoder { @Override public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException { //读取 body byte[] body = response.body().asInputStream().readAllBytes(); return JSON.parseObject(body, type); } } /** * 基于 FastJson 的序列化编码器 */ static class FastJsonEncoder implements Encoder { @Override public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { if (object != null) { //编码 body template.header(CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()); template.body(JSON.toJSONBytes(object), StandardCharsets.UTF_8); } } }
然后,我们通过 http://httpbin.org/anything
来测试,这个链接会返回我们发送的请求的一切元素。
interface HttpBin { @RequestLine("POST /anything") Object postBody(Map<String, String> body); } public static void main(String[] args) { HttpBin httpBin = Feign.builder() .decoder(new FastJsonDecoder()) .encoder(new FastJsonEncoder()) .target(HttpBin.class, "http://www.httpbin.org"); Object o = httpBin.postBody(Map.of("key", "value")); }
查看响应,可以看到我们发送的 json body 被正确的接收到了。