美文网首页Spring Cloud
Feign-可插拔的HTTP编码器和解码器

Feign-可插拔的HTTP编码器和解码器

作者: 小胖学编程 | 来源:发表于2020-05-28 12:59 被阅读0次

    Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口并插入注解,就可以完成HTTP请求的参数、格式、地址等信息的声明。Feign整合了Ribbon和Hystrix。

    Fegin特性:

    • 可插拔的注解支持,包括Fegin注解和JAX-RS注解。
    • 支持插拔的HTTP编码器和解码器。
    • 支持Hystrix和它的回退功能
    • 支持Ribbon的负载均衡
    • 支持HTTP请求和相应的压缩处理。

    Feign接口远程服务调用:

    Feign接口的远程服务调用,相当于RestTemplate发送请求。Feign会借助Spring容器的HttpMessageConverter对消息进行转换。

    Object executeAndDecode(RequestTemplate template) throws Throwable {
        Request request = targetRequest(template);
    
        if (logLevel != Logger.Level.NONE) {
          logger.logRequest(metadata.configKey(), logLevel, request);
        }
    
        Response response;
        long start = System.nanoTime();
        try {
          //请求发送。
          response = client.execute(request, options);
          // ensure the request is set. TODO: remove in Feign 10
          response.toBuilder().request(request).build();
        } catch (IOException e) {
          if (logLevel != Logger.Level.NONE) {
            logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
          }
          throw errorExecuting(request, e);
        }
        long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
    
        boolean shouldClose = true;
        try {
          if (logLevel != Logger.Level.NONE) {
            response =
                logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
            // ensure the request is set. TODO: remove in Feign 10
            response.toBuilder().request(request).build();
          }
          if (Response.class == metadata.returnType()) {
            if (response.body() == null) {
              return response;
            }
            if (response.body().length() == null ||
                    response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
              shouldClose = false;
              return response;
            }
            // Ensure the response body is disconnected
            byte[] bodyData = Util.toByteArray(response.body().asInputStream());
            return response.toBuilder().body(bodyData).build();
          }
          if (response.status() >= 200 && response.status() < 300) {
            if (void.class == metadata.returnType()) {
              return null;
            } else {
              //若是响应码为[200,300),那么使用解码器去反序列JSON为响应对象
              return decode(response);
            }
          } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
            return decode(response);
          } else {
            //异常解码器去解析响应数据
            throw errorDecoder.decode(metadata.configKey(), response);
          }
        } catch (IOException e) {
          if (logLevel != Logger.Level.NONE) {
            logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
          }
          throw errorReading(request, response, e);
        } finally {
          if (shouldClose) {
            ensureClosed(response.body());
          }
        }
      }
    

    Feign源码中配置编码器和解码器:

    @Configuration
    public class FeignClientsConfiguration {
        //获取Spring容器中所有的http信息转换器
        @Autowired
        private ObjectFactory<HttpMessageConverters> messageConverters;
    
        @Autowired(required = false)
        private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();
    
        @Autowired(required = false)
        private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>();
    
        @Autowired(required = false)
        private Logger logger;
        //设置http解码器。
        @Bean
        @ConditionalOnMissingBean
        public Decoder feignDecoder() {
            return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
        }
        //设置http编码器。
        @Bean
        @ConditionalOnMissingBean
        public Encoder feignEncoder() {
            return new SpringEncoder(this.messageConverters);
        }
       ...
    }
    

    Feign默认的编码器

    若Feign方法参数是对象,那么该对象会经过编码器序列化为JSON串,发送出去。

    public class SpringEncoder implements Encoder {
    
        private static final Log log = LogFactory.getLog(SpringEncoder.class);
    
        private ObjectFactory<HttpMessageConverters> messageConverters;
    
        public SpringEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
            this.messageConverters = messageConverters;
        }
        //设置编码器,填充RequestTemplate对象
        @Override
        public void encode(Object requestBody, Type bodyType, RequestTemplate request)
                throws EncodeException {
            // template.body(conversionService.convert(object, String.class));
            if (requestBody != null) {
                Class<?> requestType = requestBody.getClass();
                //此时获取到的contentTypes为空
                Collection<String> contentTypes = request.headers().get("Content-Type");
    
                MediaType requestContentType = null;
                if (contentTypes != null && !contentTypes.isEmpty()) {
                    String type = contentTypes.iterator().next();
                    requestContentType = MediaType.valueOf(type);
                }
                //使用合适的消息转换器去序列化对象(此处使用的是MappingJackson2HttpMessageConverter)。
                for (HttpMessageConverter<?> messageConverter : this.messageConverters
                        .getObject().getConverters()) {
                    if (messageConverter.canWrite(requestType, requestContentType)) {
                        if (log.isDebugEnabled()) {
                            if (requestContentType != null) {
                                log.debug("Writing [" + requestBody + "] as \""
                                        + requestContentType + "\" using ["
                                        + messageConverter + "]");
                            }
                            else {
                                log.debug("Writing [" + requestBody + "] using ["
                                        + messageConverter + "]");
                            }
    
                        }
    
                        FeignOutputMessage outputMessage = new FeignOutputMessage(request);
                        try {
                            @SuppressWarnings("unchecked")
                            HttpMessageConverter<Object> copy = (HttpMessageConverter<Object>) messageConverter;
                            //将对象序列化为JSON
                            copy.write(requestBody, requestContentType, outputMessage);
                        }
                        catch (IOException ex) {
                            throw new EncodeException("Error converting request body", ex);
                        }
                        // clear headers
                        request.headers(null);
                        // converters can modify headers, so update the request
                        // with the modified headers
                        request.headers(getHeaders(outputMessage.getHeaders()));
    
                        // do not use charset for binary data and protobuf
                        Charset charset;
                        if (messageConverter instanceof ByteArrayHttpMessageConverter) {
                            charset = null;
                        } else if (messageConverter instanceof ProtobufHttpMessageConverter &&
                                ProtobufHttpMessageConverter.PROTOBUF.isCompatibleWith(outputMessage.getHeaders().getContentType())) {
                            charset = null;
                        } else {
                            charset = StandardCharsets.UTF_8;
                        }
                        request.body(outputMessage.getOutputStream().toByteArray(), charset);
                        return;
                    }
                }
                String message = "Could not write request: no suitable HttpMessageConverter "
                        + "found for request type [" + requestType.getName() + "]";
                if (requestContentType != null) {
                    message += " and content type [" + requestContentType + "]";
                }
                throw new EncodeException(message);
            }
        }
    
        private class FeignOutputMessage implements HttpOutputMessage {
    
            private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    
            private final HttpHeaders httpHeaders;
    
            private FeignOutputMessage(RequestTemplate request) {
                httpHeaders = getHttpHeaders(request.headers());
            }
    
            @Override
            public OutputStream getBody() throws IOException {
                return this.outputStream;
            }
    
            @Override
            public HttpHeaders getHeaders() {
                return this.httpHeaders;
            }
    
            public ByteArrayOutputStream getOutputStream() {
                return this.outputStream;
            }
    
        }
    
    }
    
    方法进入时RequestTemplate对象.png

    Feign默认的解码器

    当Feign方法的响应对象中存在泛型时,若泛型对象中存在@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")配置,那么该配置不会生效。此时需要配置ObjectMapper配置。

    public class SpringDecoder implements Decoder {
    
        private ObjectFactory<HttpMessageConverters> messageConverters;
    
        public SpringDecoder(ObjectFactory<HttpMessageConverters> messageConverters) {
            this.messageConverters = messageConverters;
        }
        //将响应JSON串转换为对象。
        @Override
        public Object decode(final Response response, Type type)
                throws IOException, FeignException {
            if (type instanceof Class || type instanceof ParameterizedType
                    || type instanceof WildcardType) {
                @SuppressWarnings({ "unchecked", "rawtypes" })
                HttpMessageConverterExtractor<?> extractor = new HttpMessageConverterExtractor(
                        type, this.messageConverters.getObject().getConverters());
    
                return extractor.extractData(new FeignResponseAdapter(response));
            }
            throw new DecodeException(
                    "type is not an instance of Class or ParameterizedType: " + type);
        }
    
        private class FeignResponseAdapter implements ClientHttpResponse {
    
            private final Response response;
    
            private FeignResponseAdapter(Response response) {
                this.response = response;
            }
    
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.valueOf(this.response.status());
            }
    
            @Override
            public int getRawStatusCode() throws IOException {
                return this.response.status();
            }
    
            @Override
            public String getStatusText() throws IOException {
                return this.response.reason();
            }
    
            @Override
            public void close() {
                try {
                    this.response.body().close();
                }
                catch (IOException ex) {
                    // Ignore exception on close...
                }
            }
    
            @Override
            public InputStream getBody() throws IOException {
                return this.response.body().asInputStream();
            }
    
            @Override
            public HttpHeaders getHeaders() {
                return getHttpHeaders(this.response.headers());
            }
        }
    }
    

    相关文章

      网友评论

        本文标题:Feign-可插拔的HTTP编码器和解码器

        本文链接:https://www.haomeiwen.com/subject/wkkoahtx.html