SpringCloud升级之路2020。0。x版29。SCOpenFeign的解析(2)
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent
在使用云原生的很多微服务中,比较小规模的可能直接依靠云服务中的负载均衡器进行内部域名与服务映射,通过健康检查接口判断实例健康状态,然后直接使用 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 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 formWriterProvider) { return springEncoder(formWriterProvider, encoderProperties); } private Encoder springEncoder(ObjectProvider 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 { //判断 clazz 类型是否可以被当前 HttpMessageConverter 所读取 boolean canRead(Class<?> clazz, @Nullable MediaType mediaType); //判断 clazz 类型是否可以被当前 HttpMessageConverter 写 boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType); //获取所有支持的 MediaType List getSupportedMediaTypes(); //通过 clazz 类型获取该 HttpMessageConverter 支持的 MediaType //默认实现是,如果该类型可以被当前 HttpMessageConverter 读或者写,那么返回 getSupportedMediaTypes 所有支持的 MediaType default List 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 { @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 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> 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。这里我们就不再赘述。
2K屏加5G网络!华为Mate50将于年底发布华为手机在今年的表现可以说是一波多折,其发布的华为P50系列无法支持5G网络功能,这也使得其销量有所下降。但为了弥补这一缺憾,华为计划在今年稍晚时发布全新的华为Mate50系列,这
谷歌安卓上新功能用脸就能控制手机智东西(公众号zhidxcom)编译杨畅编辑Panken智东西8月18日消息,据外媒报道,谷歌最近更新了其安卓无障碍(辅助功能)套件(AndroidAccessibilitySui
独家他加入神童李一男的公司后被并到华为却靠极米创造神话运营商财经实习生李思缘文近日,极米科技携手保时捷设计共同推出极米RSAIR保时捷设计智能投影。纵观整个投影仪市场,极米科技在产品数量质量和品种方面,都已逐步走向成熟。作为行业领军企
不用再担心手机空间小了!aigo国民好物固态U盘U393刷新你的认知在移动互联网高速发展的时代,我们使用手机的频率也越来越高,甚至有些人手机不离身,因此,这也导致了我们手机里的东西越来越多,即便是手机厂商不断扩展内存,从原先的8G16G32G升级到
一起事故,蔚来从众人追捧到今日说法Q2财报发布之后,蔚来本应凭借财报再度成为业界关于新能源市场分析的重要案例之一,但近期的两起事故让刚发酵起来的蔚来前途话题迅速沉寂,甚至转变成了今日说法使用自动驾驶时如果发生事故,
今日热点曝大批骁龙898旗舰!小米12首发科技行业新鲜趣事一文速览,在这里你可以了解科技热点获悉行业动态,话不多说让我们一起来看看吧曝大批骁龙898旗舰小米12首发日前,4nm工艺的高通骁龙898曝光了,有消息称目前有一大
100公里路程消耗续航也是100吗?高德新能源导航推出续航预估算法电动汽车跑100公里需要有多少续航才安心?开到目的地预计要掉多少电?跑高速和走省道哪条路耗电更少?针对新能源车主的痛点需求,高德地图新能源导航快速迭代更新,近日创新推出了续航预估智
适老化版APP受热捧,并非年轻人老了文严奇想不到,我一个90后能把老年模式用得这么香。记者采访发现,不少适老版APP在成功被老年群体接受之前,却率先收获了一批年轻粉丝。随着APP适老化逐渐推进,页面简洁操作简单的适老
第11天鸿蒙App开发实战,相对布局相对布局DependentLayout,也是比较常用的一个布局管理器,在它里面,组件的排列方式是相对于其他同级组件或者父组件的位置进行布局。相对同级组件组件B神,想躲在组件A卡的后
即将来袭的苹果13到底如何?刚刚看了一条消息,万众期待的iPhone13预计可能在今年的秋季9月14上线,这让我们这些错过了12的小伙伴有了一些期待,毕竟我还是一个听劝的人,身边的朋友都告诉我十三香的告诫,那
京东旗舰店和自营店的区别,哪一个更好?现在网络购物的环境越来越好了,以前从不网购的小伙伴也纷纷开始网购。京东商城相对来说是大家使用比较多的一个购物软件,但是即便是在同一个平台购物,不同的店铺商品质量也不会一样。今天Pi