05 Feign的解码器处理响应数据

解码器就是用于Response

1 Decoder

public interface Decoder {
	
	// response:代表请求响应
	// type:代表方法的返回值类型
	// 它还有个特点:抛出了三种异常
	// 但其实除了IOException,其它两种都是unchecked异常
	Object decode(Response response, Type type) throws IOException, DecodeException, FeignException;
}

将Http响应feign.response解码为指定的单一对象,触发前提:

  • 响应码是2xx
  • 方法返回值不是null或者void,也不是feign.response

1.1 实现一:StreamDecoder

支持的返回值类型是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);
  }
  ...
}

要点:

  • 内部依赖了一个迭代的解码器,该解码器返回值就是一个可迭代的对象
  • 提供了静态方法创建自己的实例
  • 这个解码器只支持是feign接口返回值是,Stream类型
  • ** 最后将可迭代的解码器返回的迭代器转换为stream流返回**
  • 实际开发中用的很少,一般不会把流作为接口的返回值的
1.1.1 demo
  1. 服务提供者:
@RestController
@RequestMapping("/feign/provider")
public class StreamDecoderController {

    @GetMapping("streamDecoder/test01")
    public List<String> test01(){
        return Arrays.asList("java", "python", "javaScript");
    }
}
  1. feign客户端
 @RequestLine("GET /feign/provider/streamDecoder/test01")
    Stream<String> test01();
  1. 测试
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方法都打个断点,观察一下具体的参数:
05 Feign的解码器处理响应数据_第1张图片

1.2 实现二:OptionalDecoder

支持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));
}

要点:

  • 内部持有一个解码器,需要自己实现,该解码器的返回值会被OptionalDecoder处理
  • 处理方式由两种:
    • 如果接口的返回值是Optional类型,最后就会被Optional包装起来
    • 如果接口的返回值不是Optional类型,就会直接接返回内部持有解码器返回的结果
1.2.1 demo
  1. 服务提供者
@RestController
@RequestMapping("/feign/provider")
public class OptionalDecoderController {

    @GetMapping("optionalDecoder/test01")
    public List<String> test01(){
        return Arrays.asList("java", "python", "javaScript");
    }
}
  1. feign客户端
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();
}
  1. 测试
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);
    }
}

1.3 实现三:StringDecoder

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)

1.4 实现四:ResponseMappingDecoder

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

1.5 自定义实现json解析

  1. 先提供几个rest接口
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;
    }
}

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

  1. json解码器
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;
    }
}

  1. 测试
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);
    }
}

  1. 日志输出结果
[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])

2 ErrorDecoder

可见这个解码器,是发生错误,异常的时候执行的,允许用户对异常进行特殊处理,并提供了一个默认实现

2.1 默认实现

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;
    }
	...
}

你可能感兴趣的:(Feign)