一、报错
Could not extract response: no suitable HttpMessageConverter found for response type [xxx] and content type [application/octet-stream] feign.codec.DecodeException: Could not extract response: no suitable HttpMessageConverter found for response type [xxx] and content type [application/octet-stream]
解释一下:就是Feign自带的转换器中,无法把[class XXX]转换成content type [XXX;XXX]
在使用SpringCloud FeignClient的时候,经常的情况是我们希望返回的是一个application/json类型的返回,当返回是[application/octet-stream]类型或者其他自带转换器中没有的类型时,feign调用就会解析错误。
二、具体源码分析
(1)在 Springboot 默认处理器为 MappingJackson2HttpMessageConverter
用于将 Java 对象转换为 JSON 格式。
但是MappingJackson2HttpMessageConverter,
只支持"application/json"类型;因此springboot没有找到合适的HttpMessageConverter
,于是报出了上面的异常。
(2)feign默认的Decoder为SpringDecoder
SpringDecoder的decode的过程:
判断目标类型合法的利用Spring框架的消息转换器messageConverters来执行这个任务,并可以通过自定义器来customizers自定义转换器的行为创建一个HttpMessageConverterExtractor对象,最终通过调用该对象的extractData方法将Feign的响应Response对象转化为Java对象
二、解决措施
解决该问题,可以自定义一个Feign的Decoder,只要添加MediaType.APPLICATION_OCTET_STREAM_VALUE类型的支持即可。
第一步:自定义一个Decoder:
import com.fasterxml.jackson.databind.ObjectMapper;import feign.Response;import feign.codec.Decoder;import lombok.extern.slf4j.Slf4j;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;import org.springframework.util.StreamUtils;import java.io.ByteArrayInputStream;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Type;import java.nio.charset.StandardCharsets;import java.util.Collection;import java.util.Collections;@Configuration@Slf4jpublic class FeignConfiguration { @Bean public Decoder feignDecoder() { return new CustomFeignDecoder(); } public class CustomFeignDecoder implements Decoder { private final ObjectMapper objectMapper = new ObjectMapper(); private final HttpMessageConverter<Object> messageConverter = new MappingJackson2HttpMessageConverter(objectMapper); @Override public Object decode(Response response, Type type) throws IOException { if (response.status() == HttpStatus.NO_CONTENT.value() || response.body() == null) { return null; } Collection<String> contentTypeHeader = response.headers().getOrDefault("Content-Type", Collections.singletonList(MediaType.APPLICATION_OCTET_STREAM_VALUE)); log.info("Response headers:{}", response.headers()); String contentType; if (contentTypeHeader != null && !contentTypeHeader.isEmpty()) { contentType = contentTypeHeader.iterator().next(); } else { contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE; } MediaType mediaType = MediaType.parseMediaType(contentType); String body = StreamUtils.copyToString(response.body().asInputStream(), StandardCharsets.UTF_8); return messageConverter.read((Class<?>) type, new FakeHttpInputMessage(body, mediaType)); } } public static class FakeHttpInputMessage implements org.springframework.http.HttpInputMessage { private final String body; private final MediaType mediaType; public FakeHttpInputMessage(String body, MediaType mediaType) { this.body = body; this.mediaType = mediaType; } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)); } @Override public org.springframework.http.HttpHeaders getHeaders() { org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders(); headers.setContentType(mediaType); return headers; } }}
首先获取默认的Content-Type值,如果没有,就返回一个MediaType.APPLICATION_OCTET_STREAM_VALUE类型的。
第二步:在Feign接口上加上:
configuration = FeignConfiguration.class
import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestHeader;@FeignClient(url = "${xxx.url:}", name = "TestFeignApi", configuration = FeignConfiguration.class)public interface TestFeignApi { @PostMapping(path = "/aaa/bbb/checkUserTicket") UserResponse checkUserTicket(@RequestBody UserReq req, @RequestHeader("x-token") String token, @RequestHeader("x-id") String id);}
这样问题就解决了~
========================================================================