前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[享学Feign] 五、原生Feign的编码器Encoder、QueryMapEncoder

[享学Feign] 五、原生Feign的编码器Encoder、QueryMapEncoder

作者头像
YourBatman
发布2020-02-21 16:28:10
7.4K0
发布2020-02-21 16:28:10
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦

士不可不弘毅,任重而道远。

代码下载地址:https://github.com/f641385712/feign-learning

前言

编码和解码的概念最初是出现在硬件范畴,最后引申到软件领域,它和加密、解密很多时候是一组同义词。下面这段解释摘抄自百科:

  • 编码器:将信号(如比特流)或数据进行编制、转换为可用以通讯、传输和存储的信号形式的设备。
    • 功能:转换信号形式
  • 解码器:是一种能将数字视音频数据流解码还原成模拟视音频信号的硬件/软件设备。

很多框架都存在着编码、解码动作,Feign也不例外。本专栏将连续花两篇文章,就专注于讲解Feign的编码器Encoder、解码器Decoder,以及它们是如何工作的,它对后续理解如何整合Spring MVC是有非常多的帮助。


正文

编解码一般是一对逆操作,而对于Http的编码解码并不是这样的,因为他俩面向的对象不一样:

  • 编码器作用于请求Request阶段
  • 解码器作用域响应Response阶段

编码器Encoder

将对象编码到HTTP请求体中。功能类似于javax.websocket.Encoder。当方法参数没有标注@Param注解时,编码器会起作用。

所以说,如果你不给参数标注@Param注解,就可以通过Encoder编码器把POJO编码进Body体里(如果你需要JSON格式,可以借助JSON库)

代码语言:javascript
复制
public interface Encoder {

	// 变量输入到Map<String, ?>,表示要编码的对象是一个表单
	Type MAP_STRING_WILDCARD = Util.MAP_STRING_WILDCARD;
	
	// 唯一接口方法:object 需要被编码的对象(有可能是POJO,有可能是字符串)
	// bodyType:body类型
	// template:请求模版
  	void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;
}

需要注意的是:如果方法参数并没有被模版使用,那么他会被收集放到一个Map里,然后交给Encoder处理。形如这个例子:

代码语言:javascript
复制
@RequestLine("POST /")
Session login(@Param("username") String username, @Param("password") String password);

这里的username和password均没有被@RequestLine使用到,所以它会被收集起放进一个Map里(LinkedHashMap)交给编码器处理。


Default

Feign内置的唯一编码器实现,也就是缺省实现喽。

代码语言:javascript
复制
class Default implements Encoder {

	@Override
	public void encode(Object object, Type bodyType, RequestTemplate template) {
	
	 // 1、若bodyType是String类型,那就把object直接toString()后放进去即可  这是特殊的处理...
	 // 2、若是字节数组类型,那就强转放进去喽
	 // 3、否则就报错
	  if (bodyType == String.class) {
	    template.body(object.toString());
	  } else if (bodyType == byte[].class) {
	    template.body((byte[]) object, null);
	  } else if (object != null) {
	    throw new EncodeException(
	        format("%s is not a type supported by this encoder.", object.getClass()));
	  }
	}
}

这是一个非常"简陋"的编码器实现,它仅能处理String类型、byte[]类型,那么我们最最最最常用的类型:Object是个POJO,但bodyType是个String(JSONStr嘛)它就会报错,当然拉这是后面集成、定制的主要内容。

本文还是辅以案例,必须定位编码器Encoder到底是什么,搞清楚它何时生效~


使用示例

下面我造出了7个使用Demo案例,相信能覆盖你实际使用中99%的场景~

代码语言:javascript
复制
public interface EncoderClient {

    // 1、都标注有@Param注解,并且并且并且都被模版使用了
    @RequestLine("POST /{name}/{age}")
    String encoderDemo1(@Param("name") String name, @Param("age") Integer age);

    // 2、都标注有@Param注解,但模版只使用一个
    @RequestLine("POST /{name}")
    String encoderDemo2(@Param("name") String name, @Param("age") Integer age);

    // 3、都标注有@Param注解,但模版都没有使用
    @RequestLine("POST /")
    String encoderDemo3(@Param("name") String name, @Param("age") Integer age);

    // 4、不标注@Pram注解,是String类型
    @RequestLine("POST /")
    String encoderDemo4(String name);

    // 5、不标注@Pram注解,是Object类型,但实际传String类型
    @RequestLine("POST /")
    String encoderDemo5(Object name);

    // 6、不标注@Pram注解,是POJO
    @RequestLine("POST /")
    String encoderDemo6(Person person);

    // 6、标注@Pram注解,是POJO
    @RequestLine("POST /")
    String encoderDemo7(@Param("person") Person person);

}

测试程序代码:

代码语言:javascript
复制
@Test
public void fun1(){
    EncoderClient client = FeignClientFactory.create(EncoderClient.class);

    client.encoderDemo1("YourBatman1", 18);
    System.err.println(" ------------------------- ");

    try { client.encoderDemo2("YourBatman2", 18); }catch (Exception e) {e.printStackTrace();}
    System.err.println(" ------------------------- ");

    try { client.encoderDemo3("YourBatman3", 18); }catch (Exception e) {e.printStackTrace();}
    System.err.println(" ------------------------- ");

    try { client.encoderDemo4("YourBatman4"); }catch (Exception e) {e.printStackTrace();}
    System.err.println(" ------------------------- ");

    try { client.encoderDemo5("YourBatman5"); }catch (Exception e) {e.printStackTrace();}
    System.err.println(" ------------------------- ");

    try { client.encoderDemo6(new Person()); }catch (Exception e) {e.printStackTrace();}
    System.err.println(" ------------------------- ");

    try { client.encoderDemo7(new Person()); }catch (Exception e) {e.printStackTrace();}
}

Demo1

代码语言:javascript
复制
[EncoderClient#encoderDemo1] ---> POST http://localhost:8080/YourBatman1/18 HTTP/1.1
...

因为标注都标注有@Param且都被模版使用了,所以不会经过编码器Encoder处理。

Demo2

代码语言:javascript
复制
feign.codec.EncodeException: class java.util.LinkedHashMap is not a type supported by this encoder.
	at feign.codec.Encoder$Default.encode(Encoder.java:94)
	at feign.ReflectiveFeign$BuildFormEncodedTemplateFromArgs.resolve(ReflectiveFeign.java:345)
	...

还没进入到请求发送阶段就抛错,在构建RequestTemplate抛错的。这是因为:还剩有一个标注有@Param的属性没有用到,所以会被搜集到Map里,最终交给编码器处理:

在这里插入图片描述
在这里插入图片描述

而默认的编码器Default它处理不了Map,所以就抛错。

Demo3

代码语言:javascript
复制
feign.codec.EncodeException: class java.util.LinkedHashMap is not a type supported by this encoder.
	at feign.codec.Encoder$Default.encode(Encoder.java:94)
	...

同样报错,且原因完全同上。唯一区别是上面的Map装一个值,这里两个值都没被用到,所以装了两个值。

Demo4

代码语言:javascript
复制
[EncoderClient#encoderDemo4] ---> POST http://localhost:8080/ HTTP/1.1
[EncoderClient#encoderDemo4] Content-Length: 11
[EncoderClient#encoderDemo4] 
[EncoderClient#encoderDemo4] YourBatman4
[EncoderClient#encoderDemo4] ---> END HTTP (11-byte body)
...
// 这个错不要紧,是服务端抛出的,你忽略即可
// 但是这是Feign对Response响应报错的经典格式,留个映象不要觉得陌生即可
feign.FeignException$BadRequest: status 400 reading EncoderClient#encoderDemo4(String)
	at feign.FeignException.clientErrorStatus(FeignException.java:159)

非常正常的work,没有标注注解的参数被顺利放进了请求Body里面(因为缺省的编码器支持String类型)。

在这里插入图片描述
在这里插入图片描述

为何没有使用@Body注解,也没使用@Param注解它就能“自动”进入body体里,这就是编码器的功劳

Demo5

代码语言:javascript
复制
feign.codec.EncodeException: class java.lang.String is not a type supported by this encoder.
	at feign.codec.Encoder$Default.encode(Encoder.java:94)
	...

从报错信息中你或许觉得诧异:不是支持String类型麽?为嘛报错是不支持呢?

在这里插入图片描述
在这里插入图片描述

原因就出现在这:这里的bodyType是方法参数的静态类型所以是Object类型,但又不为null,所以就抛错喽。

Demo6

代码语言:javascript
复制
feign.codec.EncodeException: class com.youtbatman.java.beans.Person is not a type supported by this encoder.
	at feign.codec.Encoder$Default.encode(Encoder.java:94)
	...

这个报错非常容易理解,缺省编码器它编码不了Person所以抛错…

Demo7

代码语言:javascript
复制
feign.codec.EncodeException: class java.util.LinkedHashMap is not a type supported by this encoder.
	at feign.codec.Encoder$Default.encode(Encoder.java:94)
	...

注意这个抛错和Demo6的区别:这里Person标注有@Param但没用,会被装进Map里,所以它报的是不支持Map的错。


自定义编码器

我们知道缺省的编码器并不能解决最为常用的JSON字符串格式的通信形式,那么下面我将自定义一个编码器,让它成为可能。

说明:依赖于Jackson库实现,若对Jackson不太了解的,请务必参见我的全网最好专栏:[享学Jackson] 一站式搞定Jackson

代码语言:javascript
复制
public class MyEncoder implements Encoder {

    private static final JsonMapper MAPPER = new JsonMapper();

    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
        if (object != null) {
            try {
                template.body(Request.Body.bodyTemplate(MAPPER.writeValueAsString(object), StandardCharsets.UTF_8));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
    }
}

把自定义的编码器放进Feign里面:Feign.builder().encoder(new MyEncoder())

再次运行上面的Demo6和Demo7,结果均一切正常,日志如下:

代码语言:javascript
复制
[EncoderClient#encoderDemo6] ---> POST http://localhost:8080/ HTTP/1.1
[EncoderClient#encoderDemo6] Content-Length: 30
[EncoderClient#encoderDemo6] 
[EncoderClient#encoderDemo6] {"name":"YourBatman","age":18}
[EncoderClient#encoderDemo6] ---> END HTTP (30-byte body)
...

POJO到JSON串在Body体了,一切正常。 实际上Feign官方是提供了feign-jackson模块供以便捷使用的,留给后续章节讨论。


QueryMapEncoder

QueryMapEncoder负责将对象编码为Map查询参数名到值的映射。

代码语言:javascript
复制
public interface QueryMapEncoder {
	Map<String, Object> encode(Object object);
	
  // 这个Defualt实际就是	FieldQueryMapEncoder 本身
  // 说明:已被标注为@deprecated,希望用`BeanQueryMapEncoder`代替
  class Default extends FieldQueryMapEncoder {
  }
}
在这里插入图片描述
在这里插入图片描述

需要知道的是,虽然看似有3个子类,按其实可以认为只有一个。现在推荐使用BeanQueryMapEncoderFieldQueryMapEncoder效果一样,但已被标记为过期~)


BeanQueryMapEncoder

查询映射将使用POJO 可访问 的getter属性方法最后作为查询参数拼接上去,拼接的顺序并不保证,如果某个属性为null,将不会拼接。

说明:这里所谓的可访问的Get方法是基于JDK的内省:Introspector.getBeanInfo(type).getPropertyDescriptors()拿到所有方法,筛选出读方法既表示有效的属性,所以Get方法必须必须必须是public的才算有效

使用示例:

Feign.Builder默认使用的FieldQueryMapEncoder(效果同BeanQueryMapEncoder),当然你也可以手动指定一把Feign.builder()..queryMapEncoder(new BeanQueryMapEncoder())

代码语言:javascript
复制
@RequestLine("GET /")
String queryMapEncoderDemo1(@QueryMap Person person);


@Getter
@Setter
public class Person {
    private String name = "YourBatman";
    private Integer age = 18;
}


@Test
public void fun2(){
    EncoderClient client = FeignClientFactory.create(EncoderClient.class);
    client.queryMapEncoderDemo1(new Person());
}

执行后控制台打印:

代码语言:javascript
复制
// 这就是该编码器的作用:把POJO的属性拼接过来了
[EncoderClient#queryMapEncoderDemo1] ---> GET http://localhost:8080/?name=YourBatman&age=18 HTTP/1.1
...

请特别注意:@QueryMap注解不能省略,因为按照前面知识若没有此注解,那么Person就会被Defualt这个Encoder编码,从而抛错:feign.codec.EncodeException: class com.youtbatman.java.beans.Person is not a type supported by this encoder.

说明:所以说@QueryMap只能标注在Map类型前面,这是准确的~


总结

关于Feign的编码器Encoder部分到这就讲完了,本专栏第一篇文章早早已介绍了Feign的工作原理图,从图中知道Encoder是负责对请求Request(实际为RequestTemplate)进行编码,外后面发送Http请求做准备,所以有时候你把它理解为适配器也不为过~

声明

原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 正文
    • 编码器Encoder
      • Default
      • 使用示例
      • 自定义编码器
    • QueryMapEncoder
      • BeanQueryMapEncoder
  • 总结
    • 声明
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档