文章目录

  • 1 Encoder接口
  • 1.1 默认实现
  • 1.2 何时生效
  • 都标注有@Param注解,并且都被模版使用了
  • 都标注有@Param注解,但模版只使用一个
  • 都标注有@Param注解,但模版都没有使用
  • 形参不标注@Pram注解,是String类型
  • 形参不标注@Pram注解,不是String类型
  • 标注@Pram注解,是POJO
  • 不标注@Pram注解,是POJO
  • 1.3 总结
  • 1.4 自定义编码器
  • 2 QueryMapEncoder接口
  • 实现类:BeanQueryMapEncoder



feign具有很强的扩展性,允许用户根据需要进行定制,如HTTP客户端OkHttp, HTTP/2 client, SLF4J日志的使用, 编解码,错误处理等。使用时可以通过Feign.builder()创建api客户端时配置自定义组件。

1 Encoder接口

这个接口的职责就将对象编码编码到Http请求体中。当参数没有标注@Param注解时候,编码器就会生效
添加链接描述

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;
}
@RequestLine("POST /")
String login(@Param("username") String username, @Param("password") String password);

1.1 默认实现

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()));
	  }
	}
}

1.2 何时生效

@PostMapping("/encoder1")
public String test1(String name,Integer num){
    return "name: " + name + " num: " + num;
}

在默认实现的方法入口处打个断点。

都标注有@Param注解,并且都被模版使用了

/****
 * 都标注有@Param注解,并且都被模版使用了
 */
@RequestLine("POST /feign/provider/encoder1?name={name}&num={num}")
String test1(@Param("name") String name, @Param("num") Integer num);

这种情况Encoder接口不会生效,断点没有进去

都标注有@Param注解,但模版只使用一个

/****
 * 都标注有@Param注解,但模版只使用一个
 */
@RequestLine("POST /feign/provider/encoder1?name={name}")
String test2(@Param("name") String name, @Param("num") Integer num);

测试

@Test
public void test2 (){
   String s = client.test2("wyy", 2020);
   System.out.println("========test2end==============");
}

会报错:

feign.codec.EncodeException: class java.util.LinkedHashMap is not a type supported by this encoder.

	at feign.codec.Encoder$Default.encode(Encoder.java:94)

encode转中文 python encoder.encode_ide

模板中没有用到的参数num,被封装到一个Map中但是默认编码器,不支持Map类型,默认的支持字符串和字节数组

都标注有@Param注解,但模版都没有使用

/****
 * 都标注有@Param注解,但模版都没有使用
 */
@RequestLine("GET /feign/provider/encoder1")
String test3(@Param("name") String name, @Param("num") Integer num);
@Test
public void test3 (){
    String s = client.test3("wyy", 2020);
    System.out.println(s);
    System.out.println("========tes3end=============");
}

错误也是如此:

feign.codec.EncodeException: class java.util.LinkedHashMap is not a type supported by this encoder.

	at feign.codec.Encoder$Default.encode(Encoder.java:94)

encode转中文 python encoder.encode_HTTP_02

和上一个原因是一样的。

总结1:
如果存在形参被@Param注解标注了,但是在@RequestLine模板中没有配置该key,这些未被模板使用的参数就会被收集到一个Map交给编码器处理。但是默认不支持处理Map

形参不标注@Pram注解,是String类型

/****
 * 不标注@Pram注解,是String类型
 */
@RequestLine("POST /feign/provider/encoder1")
String test4(String name);
@Test
public void test4 (){
    String s = client.test4("wyy");
    System.out.println(s);
    System.out.println("========test4end============");
}
[EncoderClient#test4] ---> POST http://localhost:8001/feign/provider/encoder1 HTTP/1.1
[EncoderClient#test4] Content-Length: 3
[EncoderClient#test4] 
[EncoderClient#test4] wyy   # 被放到了请求body中
[EncoderClient#test4] ---> END HTTP (3-byte body)
[EncoderClient#test4] <--- HTTP/1.1 200 (34ms)
[EncoderClient#test4] connection: keep-alive
[EncoderClient#test4] content-length: 20
[EncoderClient#test4] content-type: text/plain;charset=UTF-8
[EncoderClient#test4] date: Mon, 22 Mar 2021 06:32:10 GMT
[EncoderClient#test4] keep-alive: timeout=60
[EncoderClient#test4] 
[EncoderClient#test4] name: null num: null
[EncoderClient#test4] <--- END HTTP (20-byte body)
name: null num: null

encode转中文 python encoder.encode_User_03


这个没有使用Param注解的参数会被编码器处理器,由于是String类型,默认实现是支持该类型的,所以成功放到请求体中。

这个都没有使用param注解和body注解,就可以把String类型的数据放到请求体中,这就是编码器吃的功劳

所以这个请求是没有问题的,只是我们服务端使用SpringMVC接收URL参数的,所以放到body参数没有取处理,所以响应回来的数据是null

测试一下,将上面的controller改一下:

@PostMapping("/encoder1")
public String test1(@RequestBody String name,Integer num){
    return "name: " + name + " num: " + num;
}

在测试:

[EncoderClient#test4] ---> POST http://localhost:8001/feign/provider/encoder1 HTTP/1.1
[EncoderClient#test4] Content-Length: 3
[EncoderClient#test4] 
[EncoderClient#test4] wyy
[EncoderClient#test4] ---> END HTTP (3-byte body)
[EncoderClient#test4] <--- HTTP/1.1 200 (190ms)
[EncoderClient#test4] connection: keep-alive
[EncoderClient#test4] content-length: 20
[EncoderClient#test4] content-type: text/plain;charset=UTF-8
[EncoderClient#test4] date: Mon, 22 Mar 2021 12:07:24 GMT
[EncoderClient#test4] keep-alive: timeout=60
[EncoderClient#test4] 
[EncoderClient#test4] name: wyy= num: null
[EncoderClient#test4] <--- END HTTP (20-byte body)
name: wyy= num: null      发现wyy成功响应回来了
========test4end============

形参不标注@Pram注解,不是String类型

/****
* 不标注@Pram注解,不是String类型
*/
@RequestLine("POST /feign/provider/encoder1")
String test5(Integer num);

测试发现:又是这个熟悉的错误

feign.codec.EncodeException: class java.lang.Integer is not a type supported by this encoder.

	at feign.codec.Encoder$Default.encode(Encoder.java:94)

这个没有使用Param注解的参数会被编码器处理器,由于不是String类型或者Byte数组类型,所以报错。

标注@Pram注解,是POJO

@PostMapping("/encoder2")
public String test2( User user){
    return user.toString();
}
/****
 * 标注@Pram注解,是POJO
 */
@RequestLine("POST /feign/provider/encoder")
String test6(@Param("user") User user);
@Test
public void test6 (){
    User user = new User();
    user.setName("wyy");
    user.setAge(2020);
    String s = client.test6(user);
    System.out.println(s);
    System.out.println("========test5end===============");
}
feign.codec.EncodeException: class study.wyy.feign.model.User is not a type supported by this encoder.

	at feign.codec.Encoder$Default.encode(Encoder.java:94)

encode转中文 python encoder.encode_ide_04


这个使用Param注解的参数会被编码器处理器,由于不是String类型或者Byte数组类型,所以报错。

不标注@Pram注解,是POJO

/****
 * 不标注@Pram注解,是POJO
 */
@RequestLine("POST /feign/provider/encoder2")
String test7(User user);
feign.codec.EncodeException: class study.wyy.feign.model.User is not a type supported by this encoder.

	at feign.codec.Encoder$Default.encode(Encoder.java:94)

发现也是这个错误。

encode转中文 python encoder.encode_User_05


但是和上一个错误区别在于这里是User对象,而不是Map这里是因为不支持User对象所以抛错

1.3 总结

  • 编码器会处理没有使用Param注解的参数,如果支持该类型的参数(默认就是字符串和字节数组),放到请求体中,参数会被收集map中
  • 或者使用了Param注解的参数,但是没有模板引用该参数,如果支持该类型的参数(默认就是字符串和字节数组),放到请求体中。

1.4 自定义编码器

常用的编码器是不能满足我们常用的json的数据格式的,所以这里自定义一个编码器

  1. 简单的一个post请求rest接口
@Data
public class User implements Serializable {

    private String name;
    /**8
     * 年龄
     */
    private Integer age;

    private List<String> hobby;
@PostMapping("/encoder3")
public String test3(@RequestBody User user){
    return user.toString();
}
  1. 声明一个客户端,根据上面说的编码器生效的时机,提高了这么两个测试方法
/**
或者使用了Param注解的参数,但是没有模板引用该参数,如果支持该类型的参数(默认就是字符串和字节数组),放到请求体中。
**/
@RequestLine("POST /feign/provider/encoder3")
@Headers({"content-type:application/json"})
String test8(@Param("user")  User user);

/**
- 编码器会处理没有使用Param注解的参数,如果支持该类型的参数(默认就是字符串和字节数组),放到请求体中
**/
@RequestLine("POST /feign/provider/encoder3")
@Headers({"content-type:application/json"})
String test9(User user);
  1. 自定义json编码器
public class JsonEncoder implements Encoder {
    private final ObjectMapper MAPPER = new ObjectMapper();
    @SneakyThrows
    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
        if(object != null){
            String json = MAPPER.writer().writeValueAsString(object);
            System.out.println(json);
            // 放到body中
            template.body(Request.Body.bodyTemplate(json, StandardCharsets.UTF_8));
        }
    }
}
  1. 测试
@Test
public void test8 (){
    User user = new User();
    user.setName("wyy");
    user.setAge(2020);
    EncoderClient encoderClient = FeignClientBuilder.buildJsonEncoder(EncoderClient.class);
    String s = encoderClient.test8(user);
    System.out.println(s);
    System.out.println("========test6end==========");
}

@Test
public void test9 (){
    User user = new User();
    user.setName("wyy");
    user.setAge(2020);
    EncoderClient encoderClient = FeignClientBuilder.buildJsonEncoder(EncoderClient.class);
    String s = encoderClient.test9(user);
    System.out.println(s);
    System.out.println("========test6end==========");
}

第一个测试,会把参数收集到map中的,所以json数据不对,到服务提供者的时候无法反序列化为User对象。所以响应结果为null

{"user":{"name":"wyy","age":2020,"hobby":null}}
[EncoderClient#test8] ---> POST http://localhost:8001/feign/provider/encoder3 HTTP/1.1
[EncoderClient#test8] Content-Length: 47
[EncoderClient#test8] content-type: application/json
[EncoderClient#test8] 
[EncoderClient#test8] {"user":{"name":"wyy","age":2020,"hobby":null}}
[EncoderClient#test8] ---> END HTTP (47-byte body)
[EncoderClient#test8] <--- HTTP/1.1 200 (104ms)
[EncoderClient#test8] connection: keep-alive
[EncoderClient#test8] content-length: 37
[EncoderClient#test8] content-type: text/plain;charset=UTF-8
[EncoderClient#test8] date: Mon, 22 Mar 2021 13:42:42 GMT
[EncoderClient#test8] keep-alive: timeout=60
[EncoderClient#test8] 
[EncoderClient#test8] User(name=null, age=null, hobby=null)
[EncoderClient#test8] <--- END HTTP (37-byte body)
User(name=null, age=null, hobby=null)
========test6end==========

json数据格式:{“user”:{“name”:“wyy”,“age”:2020,“hobby”:null}}

第一个测试,就不会把参数收集到map中的,所以响应回来的json数据是对的。

{"name":"wyy","age":2020,"hobby":null}
[EncoderClient#test9] ---> POST http://localhost:8001/feign/provider/encoder3 HTTP/1.1
[EncoderClient#test9] Content-Length: 38
[EncoderClient#test9] content-type: application/json
[EncoderClient#test9] 
[EncoderClient#test9] {"name":"wyy","age":2020,"hobby":null}
[EncoderClient#test9] ---> END HTTP (38-byte body)
[EncoderClient#test9] <--- HTTP/1.1 200 (21ms)
[EncoderClient#test9] connection: keep-alive
[EncoderClient#test9] content-length: 36
[EncoderClient#test9] content-type: text/plain;charset=UTF-8
[EncoderClient#test9] date: Mon, 22 Mar 2021 13:44:12 GMT
[EncoderClient#test9] keep-alive: timeout=60
[EncoderClient#test9] 
[EncoderClient#test9] User(name=wyy, age=2020, hobby=null)
[EncoderClient#test9] <--- END HTTP (36-byte body)
User(name=wyy, age=2020, hobby=null)
========test6end==========

切记不能这么写,因为这么写,编码器就不生效了,这个写法,既使用了param注解,模板中也用了这个参数,不满足上面说的两个条件。

@RequestLine("POST /feign/provider/encoder3")
@Headers({"content-type:application/json"})
@Body("{user}")
String test10(@Param("user")User user);

2 QueryMapEncoder接口

实现类:BeanQueryMapEncoder

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

默认使用的是FieldQueryMapEncoder,效果和BeanQueryMapEncoder一样,只是现在推荐使用BeanQueryMapEncoder

/**
 * pojo
 *
 * @return
 */
@RequestLine("GET /feign/provider/get/pojo/")
String invokeTest05(@QueryMap User user);

最后生成的url:

GET http://localhost:8001/feign/provider/get/pojo/?name=Wade&age=13&hobby=basketball&hobby=football