解码器就是用于Response
public interface Decoder {
// response:代表请求响应
// type:代表方法的返回值类型
// 它还有个特点:抛出了三种异常
// 但其实除了IOException,其它两种都是unchecked异常
Object decode(Response response, Type type) throws IOException, DecodeException, FeignException;
}
将Http响应feign.response
解码为指定的单一对象,触发前提:
feign.response
。支持的返回值类型是Java的java.util.stream.Stream
类型的返回值
public final class StreamDecoder implements Decoder {
/**
内部还依赖一个Decoder,可迭代的解码器
**/
private final Decoder iteratorDecoder;
StreamDecoder(Decoder iteratorDecoder) {
this.iteratorDecoder = iteratorDecoder;
}
@Override
public Object decode(Response response, Type type)
throws IOException, FeignException {
// 不是参数化类型类型就会抛出异常
if (!(type instanceof ParameterizedType)) {
throw new IllegalArgumentException("StreamDecoder supports only stream: unknown " + type);
}
ParameterizedType streamType = (ParameterizedType) type;
// 不是Stream类型也会抛出错,可见这个解码器只支持是feign接口返回值是,Stream类型
// getRawType: 返回最外层<>前面那个类型,即Map的Map。
if (!Stream.class.equals(streamType.getRawType())) {
throw new IllegalArgumentException("StreamDecoder supports only stream: unknown " + type);
}
// 这个可迭代的解码器返回值就是一个迭代器
Iterator<?> iterator =
(Iterator) iteratorDecoder.decode(response, new IteratorParameterizedType(streamType));
// 将可迭代的解码器返回的迭代器转换为stream流
return StreamSupport.stream(
Spliterators.spliteratorUnknownSize(iterator, 0), false)
.onClose(() -> {
if (iterator instanceof Closeable) {
ensureClosed((Closeable) iterator);
} else {
ensureClosed(response);
}
});
}
/**
提供了一个静态方法创建自己的实例
**/
public static StreamDecoder create(Decoder iteratorDecoder) {
return new StreamDecoder(iteratorDecoder);
}
...
}
要点:
@RestController
@RequestMapping("/feign/provider")
public class StreamDecoderController {
@GetMapping("streamDecoder/test01")
public List<String> test01(){
return Arrays.asList("java", "python", "javaScript");
}
}
@RequestLine("GET /feign/provider/streamDecoder/test01")
Stream<String> test01();
public class StreamDecoderClientTest {
public static String HOST = "http://localhost:8001";
public StreamDecoderClient build(){
return Feign.builder()
// 输出日志到控制台
.logger(new Logger.ErrorLogger()).logLevel(Logger.Level.FULL)
// 关闭重试
.retryer(Retryer.NEVER_RETRY)
// 404进行编码,404的时候就不会抛出异常了
.decode404()
// 注册StreamDecoder解码器,主要需要提供一个iteratorDecoder
.decoder(StreamDecoder.create((response,type)->{
// 获取响应,rest风格返回值是个json字符串
Response.Body body = response.body();
// 解析出来的字符传数据就是json格式的字符串:["java","python","javaScript"]
String json = Util.toString(body.asReader());
// 就简单处理,使用,号分割
// 返回一个迭代器
return Arrays.stream(json.split(",")).iterator();
}))
.target(StreamDecoderClient.class,HOST);
}
@Test
public void test01(){
StreamDecoderClient client = build();
Stream<String> stream = client.test01();
stream.forEach(System.out::println);
}
}
测试输出:
[StreamDecoderClient#test01] ---> GET http://localhost:8001/feign/provider/streamDecoder/test01 HTTP/1.1
[StreamDecoderClient#test01] ---> END HTTP (0-byte body)
[StreamDecoderClient#test01] <--- HTTP/1.1 200 (44ms)
[StreamDecoderClient#test01] connection: keep-alive
[StreamDecoderClient#test01] content-type: application/json
[StreamDecoderClient#test01] date: Tue, 23 Mar 2021 01:09:26 GMT
[StreamDecoderClient#test01] keep-alive: timeout=60
[StreamDecoderClient#test01] transfer-encoding: chunked
[StreamDecoderClient#test01]
[StreamDecoderClient#test01] ["java","python","javaScript"]
[StreamDecoderClient#test01] <--- END HTTP (30-byte body)
["java"
"python"
"javaScript"]
在streamDecoder的核心方法decode方法都打个断点,观察一下具体的参数:
支持java8Optionall类型
// 内部依旧持有一个解码器,该解码器的返回值就得是一个Optional类型
final Decoder delegate;
public OptionalDecoder(Decoder delegate) {
Objects.requireNonNull(delegate, "Decoder must not be null. ");
this.delegate = delegate;
}
@Override
public Object decode(Response response, Type type) throws IOException {
// 如果feign接口的返回值不是Optional类型
if (!isOptional(type)) {
// 那就返回持有的解码器返回的结果
return delegate.decode(response, type);
}
if (response.status() == 404 || response.status() == 204) {
return Optional.empty();
}
Type enclosedType = Util.resolveLastTypeParameter(type, Optional.class);
// 如果接口的返回值是Optional,就会把持有的解码器的数据用Optional包装起来
return Optional.ofNullable(delegate.decode(response, enclosedType));
}
要点:
@RestController
@RequestMapping("/feign/provider")
public class OptionalDecoderController {
@GetMapping("optionalDecoder/test01")
public List<String> test01(){
return Arrays.asList("java", "python", "javaScript");
}
}
public interface OptionalDecoderClient {
/**
* 返回值是Optional类型
* @return
*/
@RequestLine("GET /feign/provider/optionalDecoder/test01")
Optional<List<String>> test01();
/**
* 返回值不是Optional类型
* @return
*/
@RequestLine("GET /feign/provider/optionalDecoder/test01")
List<String> test02();
}
public class OptionalDecoderClientTest {
public static String HOST = "http://localhost:8001";
public OptionalDecoderClient build(){
return Feign.builder()
// 输出日志到控制台
.logger(new Logger.ErrorLogger()).logLevel(Logger.Level.FULL)
// 关闭重试
.retryer(Retryer.NEVER_RETRY)
// 404进行编码,404的时候就不会抛出异常了
.decode404()
.decoder(new OptionalDecoder(((response, type)->{
// 获取响应,rest风格返回值是个json字符串
Response.Body body = response.body();
// 解析出来的字符传数据就是json格式的字符串:["java","python","javaScript"]
String json = Util.toString(body.asReader());
ObjectMapper objectMapper = new ObjectMapper();
// 反序列化为List
List<String> list = objectMapper.readValue(json, new TypeReference<List<String>>(){});
return Optional.of(list);
})))
.target(OptionalDecoderClient.class,HOST);
}
@Test
public void test01(){
OptionalDecoderClient client = build();
// 这个时候OptionalDecoder就会直接把上面自己实现的编码器的返回的类型(List)使用optional
// 包装起来返回给feign接口
Optional<List<String>> optional = client.test01();
if(optional.isPresent()){
List<String> strings = optional.get();
System.out.println(strings);
}
}
@Test
public void test02() {
OptionalDecoderClient client = build();
// 这个时候OptionalDecoder就会直接把上面自己实现的编码器的返回的类型(List)返回到feign接口的返回值
List<String> strings = client.test02();
System.out.println(strings);
}
}
public class StringDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException {
// 1、如果body为null,那就return null喽
Response.Body body = response.body();
if (body == null) {
return null;
}
// 2、仅处理String类型:把body流转换为String
// 注意:这里asReader()没有指定编码,默认使用的是UTF8哦~~~~
if (String.class.equals(type)) {
return Util.toString(body.asReader());
}
// 3、若不是String类型,报错...
throw new DecodeException(response.status(), format("%s is not a type supported by this decoder.", type), response.request());
}
}
还有一个子类:是Feign默认解码器
public class Default extends StringDecoder {
@Override
public Object decode(Response response, Type type) throws IOException {
// 404状态码和204的特殊处理:返回值只和type类型有关,和Response无关
if (response.status() == 404 || response.status() == 204)
return Util.emptyValueOf(type);
if (response.body() == null)
return null;
// 不仅支持String,同时也支持到了返回值为字节数组的情况...
// 说明:字节流和编码无关,字符流才有关
if (byte[].class.equals(type)) {
return Util.toByteArray(response.body().asInputStream());
}
// 处理字符串
return super.decode(response, type);
}
}
可见feign默认解码器只能将响应处理为String或者字节数组。这也就是为何前几节的案例很多返回值都是定义为字符串
测试一下:将上面的改一下,不指定解码器,默认的解码器就是这个Defaut解码器
package study.wyy.feign.java.test;
import feign.*;
import feign.stream.StreamDecoder;
import org.junit.Test;
import study.wyy.feign.java.consumer.StreamDecoderClient;
import javax.lang.model.element.VariableElement;
import java.util.Arrays;
import java.util.stream.Stream;
/**
* @author wyaoyao
* @date 2021/3/23 8:38
*/
public class StreamDecoderClientTest {
public static String HOST = "http://localhost:8001";
public StreamDecoderClient build(){
return Feign.builder()
// 输出日志到控制台
.logger(new Logger.ErrorLogger()).logLevel(Logger.Level.FULL)
// 关闭重试
.retryer(Retryer.NEVER_RETRY)
// 404进行编码,404的时候就不会抛出异常了
.decode404()
// .decoder(StreamDecoder.create((response,type)->{
// // 获取响应,rest风格返回值是个json字符串
// Response.Body body = response.body();
// // 解析出来的字符传数据就是json格式的字符串:["java","python","javaScript"]
// String json = Util.toString(body.asReader());
// // 就简单处理,使用,号分割
// return Arrays.stream(json.split(",")).iterator();
//
// }))
.target(StreamDecoderClient.class,HOST);
}
@Test
public void test01(){
StreamDecoderClient client = build();
Stream<String> stream = client.test01();
stream.forEach(System.out::println);
}
}
测试就会报错:
feign.codec.DecodeException: java.util.stream.Stream<java.lang.String> is not a type supported by this decoder.
at feign.codec.StringDecoder.decode(StringDecoder.java:34)
at feign.codec.Decoder$Default.decode(Decoder.java:92)
static class ResponseMappingDecoder implements Decoder {
private final ResponseMapper mapper;
// 代理
private final Decoder delegate;
ResponseMappingDecoder(ResponseMapper mapper, Decoder decoder) {
this.mapper = mapper;
this.delegate = decoder;
}
@Override
public Object decode(Response response, Type type) throws IOException {
// 最终的解码是委托给内部delegate这个解码器处理的
// 只是在这之前做了一次映射,也可以在这之前做了一层拦截
return delegate.decode(mapper.map(response, type), type);
}
}
// 函数式接口
public interface ResponseMapper {
Response map(Response response, Type type);
}
package study.wyy.feign.provider.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import study.wyy.feign.model.User;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author wyaoyao
* @date 2021/3/23 14:07
*/
@RestController
@RequestMapping("/feign/provider")
public class JsonDecoderController {
@GetMapping("jsonDecoder/test01")
public List<String> test01(){
return Arrays.asList("java", "python", "javaScript");
}
@GetMapping("jsonDecoder/test02")
public List<User> test02(){
User user = new User();
user.setAge(11);
user.setName("wade");
ArrayList<String> hobby = new ArrayList<>();
hobby.add("basketball");
hobby.add("football");
user.setHobby(hobby);
ArrayList arrayList = new ArrayList<User>();
arrayList.add(user);
arrayList.add(user);
return arrayList;
}
@GetMapping("jsonDecoder/test03")
public User test03(){
User user = new User();
user.setAge(11);
user.setName("wade");
ArrayList<String> hobby = new ArrayList<>();
hobby.add("basketball");
hobby.add("football");
user.setHobby(hobby);
return user;
}
}
package study.wyy.feign.java.consumer;
import feign.RequestLine;
import study.wyy.feign.model.User;
import java.util.List;
import java.util.stream.Stream;
/**
* @author wyaoyao
* @date 2021/3/23 8:37
*/
public interface JsonDecoderClient {
@RequestLine("GET /feign/provider/jsonDecoder/test01")
List<String> test01();
@RequestLine("GET /feign/provider/jsonDecoder/test02")
List<User> test02();
@RequestLine("GET /feign/provider/jsonDecoder/test03")
User test03();
}
package study.wyy.feign.java.spi;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.FeignException;
import feign.Response;
import feign.Util;
import feign.codec.DecodeException;
import feign.codec.Decoder;
import feign.codec.StringDecoder;
import lombok.SneakyThrows;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* @author wyaoyao
* @date 2021/3/23 13:32
*/
public class JsonDecoder implements Decoder {
private final ObjectMapper objectMapper = new ObjectMapper();
@SneakyThrows
@Override
public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
if (response.status() == 404 || response.status() == 204) {
return Util.emptyValueOf(type);
}
Response.Body body = response.body();
if (body == null) {
return null;
}
if (byte[].class.equals(type)) {
return Util.toByteArray(body.asInputStream());
}
if (String.class.equals(type)) {
return Util.toString(body.asReader());
}
String json = Util.toString(body.asReader());
if(isJson(json)){
Object o = objectMapper.readValue(json, new TypeReference<Object>() {
@Override
public Type getType() {
return type;
}
});
return o;
}
throw new DecodeException(response.status(),"not support data",response.request());
}
/**
* 简单判断一下是不是json
* @param str
* @return
*/
private boolean isJson(String str) {
boolean result = false;
if (null != str) {
str = str.trim();
if (str.startsWith("{") && str.endsWith("}")) {
result = true;
} else if (str.startsWith("[") && str.endsWith("]")) {
result = true;
}
}
return result;
}
}
package study.wyy.feign.java.test;
import feign.*;
import org.junit.Test;
import study.wyy.feign.java.consumer.JsonDecoderClient;
import study.wyy.feign.java.spi.JsonDecoder;
import study.wyy.feign.model.User;
import java.util.List;
/**
* @author wyaoyao
* @date 2021/3/23 8:38
*/
public class JsonDecoderClientTest {
public static String HOST = "http://localhost:8001";
public JsonDecoderClient build() {
return Feign.builder()
// 输出日志到控制台
.logger(new Logger.ErrorLogger()).logLevel(Logger.Level.FULL)
// 关闭重试
.retryer(Retryer.NEVER_RETRY)
// 404进行编码,404的时候就不会抛出异常了
.decode404()
.decoder(new JsonDecoder())
.target(JsonDecoderClient.class, HOST);
}
@Test
public void test01(){
JsonDecoderClient client = build();
List<String> strings = client.test01();
System.out.println(strings);
}
@Test
public void test02(){
JsonDecoderClient client = build();
List<User> strings = client.test02();
System.out.println(strings);
}
@Test
public void test3(){
JsonDecoderClient client = build();
User strings = client.test03();
System.out.println(strings);
}
}
[JsonDecoderClient#test01] ---> GET http://localhost:8001/feign/provider/jsonDecoder/test01 HTTP/1.1
[JsonDecoderClient#test01] ---> END HTTP (0-byte body)
[JsonDecoderClient#test01] <--- HTTP/1.1 200 (22ms)
[JsonDecoderClient#test01] connection: keep-alive
[JsonDecoderClient#test01] content-type: application/json
[JsonDecoderClient#test01] date: Tue, 23 Mar 2021 13:01:46 GMT
[JsonDecoderClient#test01] keep-alive: timeout=60
[JsonDecoderClient#test01] transfer-encoding: chunked
[JsonDecoderClient#test01]
[JsonDecoderClient#test01] ["java","python","javaScript"]
[JsonDecoderClient#test01] <--- END HTTP (30-byte body)
[java, python, javaScript]
[JsonDecoderClient#test02] ---> GET http://localhost:8001/feign/provider/jsonDecoder/test02 HTTP/1.1
[JsonDecoderClient#test02] ---> END HTTP (0-byte body)
[JsonDecoderClient#test02] <--- HTTP/1.1 200 (4ms)
[JsonDecoderClient#test02] connection: keep-alive
[JsonDecoderClient#test02] content-type: application/json
[JsonDecoderClient#test02] date: Tue, 23 Mar 2021 13:01:46 GMT
[JsonDecoderClient#test02] keep-alive: timeout=60
[JsonDecoderClient#test02] transfer-encoding: chunked
[JsonDecoderClient#test02]
[JsonDecoderClient#test02] [{"name":"wade","age":11,"hobby":["basketball","football"]},{"name":"wade","age":11,"hobby":["basketball","football"]}]
[JsonDecoderClient#test02] <--- END HTTP (119-byte body)
[User(name=wade, age=11, hobby=[basketball, football]), User(name=wade, age=11, hobby=[basketball, football])]
[JsonDecoderClient#test03] ---> GET http://localhost:8001/feign/provider/jsonDecoder/test03 HTTP/1.1
[JsonDecoderClient#test03] ---> END HTTP (0-byte body)
[JsonDecoderClient#test03] <--- HTTP/1.1 200 (3ms)
[JsonDecoderClient#test03] connection: keep-alive
[JsonDecoderClient#test03] content-type: application/json
[JsonDecoderClient#test03] date: Tue, 23 Mar 2021 13:01:46 GMT
[JsonDecoderClient#test03] keep-alive: timeout=60
[JsonDecoderClient#test03] transfer-encoding: chunked
[JsonDecoderClient#test03]
[JsonDecoderClient#test03] {"name":"wade","age":11,"hobby":["basketball","football"]}
[JsonDecoderClient#test03] <--- END HTTP (58-byte body)
User(name=wade, age=11, hobby=[basketball, football])
可见这个解码器,是发生错误,异常的时候执行的,允许用户对异常进行特殊处理,并提供了一个默认实现
public static class Default implements ErrorDecoder {
...
@Override
public Exception decode(String methodKey, Response response) {
// 根据状态码提取出异常类型,并且同意包装为FeignException
// 4xx:FeignClientException
// 5xx:FeignServerException
// 其它:new FeignException( ... )
FeignException exception = errorStatus(methodKey, response);
// 检查,是否需要把异常类型包装为RetryableException(也就是看看是重试失败的,还是非重试失败的...)
Date retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers(), RETRY_AFTER));
if (retryAfter != null) {
return new RetryableException(
response.status(),
exception.getMessage(),
response.request().httpMethod(),
exception,
retryAfter,
response.request());
}
return exception;
}
...
}