乐趣区

关于spring-cloud:SpringCloud升级之路20200x版29Spring-Cloud-OpenFeign-的解析2

本系列代码地址:https://github.com/JoJoTec/sp…

在应用云原生的很多微服务中,比拟小规模的可能间接依附云服务中的负载均衡器进行外部域名与服务映射,通过健康检查接口判断实例衰弱状态,而后间接应用 OpenFeign 生成对应域名的 Feign Client。Spring Cloud 生态中,对 OpenFeign 进行了封装,其中的 Feign Client 的各个组件,也是做了肯定的定制化,能够实现在 OpenFeign Client 中集成服务发现与负载平衡。在此基础上,咱们还联合了 Resilience4J 组件,实现了微服务实例级别的线程隔离,微服务办法级别的断路器以及重试。

咱们先来剖析下 Spring Cloud OpenFeign

Spring Cloud OpenFeign 解析

HTTP 编码解码器,与 spring-boot 中的编码解码器相结合

Spring Cloud 中的任何组件,都是基于 Spring Boot 而实现的。因为 Spring Boot 中曾经有了 HTTP 编码解码器,就能够不必独自给 OpenFeign 独自再实现 HTTP 编码解码器了,而是思考将 OpenFeign 的编码解码器接口用 Spring Boot 的 HTTP 编码解码器实现。

在 FeignClientsConfiguration 中,提供了默认的实现:

// 因为初始化程序以及 NamedContextFactory 的 Configuration 初始化的起因,这里须要注入 ObjectFactory 而不是间接注入 HttpMessageConverters 避免找不到 Bean
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;

@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}

@Bean
@ConditionalOnMissingBean
// 咱们这里疏忽 Pageable 类存在的状况
// 针对 Spring Data 的分页包装 Pageable 的兼容实现也比较简单,这里疏忽
@ConditionalOnMissingClass("org.springframework.data.domain.Pageable")
public Encoder feignEncoder(ObjectProvider<AbstractFormWriter> formWriterProvider) {return springEncoder(formWriterProvider, encoderProperties);
}

private Encoder springEncoder(ObjectProvider<AbstractFormWriter> formWriterProvider,
    FeignEncoderProperties encoderProperties) {AbstractFormWriter formWriter = formWriterProvider.getIfAvailable();
    
    if (formWriter != null) {return new SpringEncoder(new SpringPojoFormEncoder(formWriter), this.messageConverters, encoderProperties);
    }
    else {return new SpringEncoder(new SpringFormEncoder(), this.messageConverters, encoderProperties);
    }
}

基于 SpringDecoder 的解码器

通过源码能够看出,默认的 Decoder 是通过几层包装的 Decoder,别离包含:

  • OptionalDecoder:用于解决 Java JDK 中的 Optional 封装类的解码器
  • ResponseEntityDecoder:用于解决 spring-web 中对于申请响应封装类 HttpEntity 的解码器
  • SpringDecoder:应用 Spring 的解码器实现的 Feign 的 Decoder

传入 SpringDecoder 的 HttpMessageConverters 对象,是 spring-web 的所有 HttpMessageConverter 汇合。HttpMessageConverter 是 spring-web 中对于 HTTP 申请和响应的 body 进行编码解码的工具。其接口构造是:

public interface HttpMessageConverter<T> {
    // 判断 clazz 类型是否能够被以后 HttpMessageConverter 所读取
    boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
    // 判断 clazz 类型是否能够被以后 HttpMessageConverter 写
    boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
    // 获取所有反对的 MediaType
    List<MediaType> getSupportedMediaTypes();
    // 通过 clazz 类型获取该 HttpMessageConverter 反对的 MediaType
    // 默认实现是,如果该类型能够被以后 HttpMessageConverter 读或者写,那么返回 getSupportedMediaTypes 所有反对的 MediaType
    default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {return (canRead(clazz, null) || canWrite(clazz, null) ?
                getSupportedMediaTypes() : Collections.emptyList());
    }

    // 从 inputMessage 中读取并解析出 clazz 类型的对象,当申请的 Content-Type 为反对的 MediaType 时
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException;

    // 将对象 t 序列化写入 HttpOutputMessage,当申请的 accept 为反对的 MediaType 时
    void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException;

}

spring boot 内置了很多 HttpMessageConverter,咱们也能够实现本人的 HttpMessageConverter,去实现咱们自定义 MediaType,例如咱们这里定义一个:

public class CustomizedHttpMessageConverter implements HttpMessageConverter<Student> {
    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {return clazz.equals(Student.class);
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {return clazz.equals(Student.class);
    }
    
    public static class StudentMediaType extends MediaType {public StudentMediaType() {super("application", "student", StandardCharsets.UTF_8);
        }
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {return List.of(new StudentMediaType());
    }

    @Override
    public Student read(Class<? extends Student> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {String temp = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
        String[] split = temp.split(",");
        return new Student(Long.parseLong(split[0]),
                split[1]);
    }

    @Override
    public void write(Student student, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {outputMessage.getBody().write((student.getId() + "," + student.getName()).getBytes(StandardCharsets.UTF_8));
    }
}

之后,与后面相似,将其配置到 spring boot 兼容 MVC 配置中:

@Configuration(proxyBeanMethods = false)
public class TestConfig implements WebMvcConfigurer {
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(new CustomizedHttpMessageConverter());
    }
}

编写 Controller,测试:

@RestController
@RequestMapping("/test")
public class TestController {@PostMapping("/post-to-student")
    public Student postToStudent(@RequestBody Student student) {return student;}
}

应用 postman 相似的工具,指定 HTTP 申请头:

Content-Type:application/student
Accept:application/student

Body 是:

1,zhx

申请后,就会走到 CustomizedHttpMessageConverter 的 read 解析成 Student 对象,之后响应的 student 也会被 CustomizedHttpMessageConverter 的 write 写入响应 Body

由此可见, 因为 SpringEncoder 的存在,咱们能够复用 Spring 内置的 HttpMessageConverter,同时也能扩大自定义咱们本人的 HttpMessageConverter,十分不便

ResponseEntityDecoder 的代码比较简单,实现的成果就是解码的时候,疏忽 HttpEntity 这个 spring-web 对于 HTTP 响应的包装类:

@Override
public Object decode(final Response response, Type type) throws IOException, FeignException {
    // 是否是带有形参的 HttpEntity<?> 
    if (isParameterizeHttpEntity(type)) {
        // 将形参类型取出
        type = ((ParameterizedType) type).getActualTypeArguments()[0];
        // 应用形参类型,解析 response
        Object decodedObject = this.decoder.decode(response, type);
        // 填充 HttpEntity 其中的返回对象,状态码等信息
        return createResponse(decodedObject, response);
    }
    else if (isHttpEntity(type)) {
        // 空形参,代表没有 body 或者疏忽 body,仅填充 HttpEntity 状态码等信息
        return createResponse(null, response);
    }
    else {
        // 如果不是 HttpEntity,间接解码
        return this.decoder.decode(response, type);
    }
}

这个其实为了和 RestTemplate 的响应兼容,RestTemplate 能够返回 HttpEntity,然而底层 HTTP 申请返回的 body 其实并没有包装这个类型。

同理,JDK 中的 Optional 包装类,也须要做同样的事件,这个就是通过 OptionalDecoder 实现的。

基于 SpringEncoder 的编码器

SpringEncoder 编码器也非常简单,也是基于 spring 中的 HttpMessageConverter。这里咱们就不再赘述。

微信搜寻“我的编程喵”关注公众号,每日一刷,轻松晋升技术,斩获各种 offer

退出移动版