RestClientException:无法提取响应。找不到合适的 HttpMessageConverter

RestClientException: Could not extract response. no suitable HttpMessageConverter found

使用 curl 命令:

curl -u 591bf65f50057469f10b5fd9:0cf17f9b03d056ds0e11e48497e506a2 https://backend.tdk.com/api/devicetypes/59147fd79e93s12e61499ffe/messages

我收到 JSON 回复:

{"data":[{"device":"18SE62","time":1494516023,"data":"3235","snr":"36.72",...

我将响应保存在 txt 文件中并使用 jackson 对其进行解析,一切正常

ObjectMapper mapper = new ObjectMapper();
        File f = new File(getClass().getResource
                    ("/result.json").getFile());
        MessageList messageList = mapper.readValue(f, MessageList.class);

我想我应该使用 RestTemplate 得到相同的结果,但事实并非如此

RestTemplate restTemplate = new RestTemplate();
        MessageList messageList = 
                restTemplate.getForObject("http://592693f43c87815f9b8145e9:f099c85d84d4e325a2186c02bd0caeef@backend.tdk.com/api/devicetypes/591570373c87894b4eece34d/messages", MessageList.class);

我得到了一个错误

Exception in thread "main" org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.tdk.domain.backend.MessageList] and content type [text/html;charset=iso-8859-1]
    at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:109)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:655)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
    at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:287)
    at com.tdk.controllers.restful.client.RestTemplateExample.main(RestTemplateExample.java:27)

我尝试设置内容类型:

HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);


        MessageList messageList = 
                restTemplate.getForObject(url, entity, MessageList.class);

但是我遇到了编译错误

The method getForObject(String, Class<T>, Object...) in the type RestTemplate is not applicable for the arguments (String, HttpEntity<String>, 
 Class<MessageList>)

我也试过添加 Jackson Message 转换器

  List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();        
            //Add the Jackson Message converter
            messageConverters.add(new MappingJackson2HttpMessageConverter());    
            //Add the message converters to the restTemplate
            restTemplate.setMessageConverters(messageConverters); 

            MessageList messageList = 
                    restTemplate.getForObject(url, MessageList.class);

但后来我得到了这个错误:

Exception in thread "main" org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.tdk.domain.backend.MessageList] and content type [text/html;charset=iso-8859-1]
    at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:109)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:655)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
    at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:287)
    at com.tdk.controllers.restful.client.RestTemplateExample.main(RestTemplateExample.java:51)

我也尝试添加 class

@Configuration
@EnableWebMvc
public class MvcConf extends WebMvcConfigurationSupport {

    protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(converter());
        addDefaultHttpMessageConverters(converters);
    }

    @Bean
    MappingJackson2HttpMessageConverter converter() {

        MappingJackson2HttpMessageConverter converter 
                    = new MappingJackson2HttpMessageConverter();
        return converter;
    }

}

但我得到了错误:

org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.tdk.domain.backend.MessageList] and content type [text/html;charset=iso-8859-1]
    at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:109)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:655)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
    at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:287)

这里的主要问题是从服务接收到的内容类型[text/html;charset=iso-8859-1],但是真正的内容类型应该是application/json;charset=iso-8859-1

为了克服这个问题,您可以引入自定义消息转换器。并为所有类型的响应注册它(即忽略响应内容类型 header)。就这样

List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();        
//Add the Jackson Message converter
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();

// Note: here we are making this converter to process any kind of response, 
// not only application/*json, which is the default behaviour
converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));        
messageConverters.add(converter);  
restTemplate.setMessageConverters(messageConverters); 

如果 @Ilya Dyoshin 的上述回复仍未检索到, 尝试将响应放入字符串对象中。

(我自己认为错误已被 Ilya 的代码片段解决,检索到的响应是来自服务器的故障(错误)。)

HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded");
ResponseEntity<String> st = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class); 

并转换为 ResponseObject DTO (Json)

Gson g = new Gson();
DTO dto = g.fromJson(st.getBody(), DTO.class); 

我遇到了一个非常相似的问题,结果很简单;我的客户没有包含 Jackson 依赖项,即使所有代码都 编译 正确,JSON 的自动魔术转换器没有被包含在内。参见 this RestTemplate-related solution.

简而言之,我在我的 pom.xml 中添加了一个 Jackson 依赖项并且它起作用了:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.5.1</version>
</dependency>

在我的例子中,@Ilya Dyoshin 的解决方案不起作用:不允许使用媒体类型“*”。 我通过在 MockRestServiceServer 初始化期间以这种方式向 restTemplate 添加一个新转换器来修复此错误:

  MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = 
                      new MappingJackson2HttpMessageConverter();
  mappingJackson2HttpMessageConverter.setSupportedMediaTypes(
                                    Arrays.asList(
                                       MediaType.APPLICATION_JSON, 
                                       MediaType.APPLICATION_OCTET_STREAM));
  restTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);
  mockServer = MockRestServiceServer.createServer(restTemplate);

(基于 Yashwant Chavan 在名为 technicalkeeda 的博客上提出的解决方案)

JN 热博

虽然接受的答案解决了 OP 的原始问题,但大多数通过 Google 搜索找到此问题的人可能遇到完全不同的问题,恰好抛出相同的 no suitable HttpMessageConverter发现 异常。

在幕后发生的事情是 MappingJackson2HttpMessageConverter 吞下了其 canRead() 方法中发生的任何异常,该方法应该自动检测有效负载是否适合 json 解码.异常被一个简单的布尔值 return 取代,它基本上传达 抱歉,我不知道如何将此消息 解码到更高级别 APIs (RestClient).只有在所有其他转换器的 canRead() 方法 return false 之后, no suitable HttpMessageConverter found 异常才被更高级别 API 抛出,完全掩盖了 true问题。

对于没有找到根本原因的人(比如你我,但不是OP),解决这个问题的方法是在onMappingJackson2HttpMessageConverter.canRead()上放置一个调试器断点,然后启用一个通用断点出现任何异常,然后点击继续。下一个异常是真正的根本原因。

我的具体错误恰好是其中一个 bean 引用了一个缺少正确的反序列化注释的接口。

未来更新

事实证明,这在我的许多项目中都是一个反复出现的问题,因此我开发了一个更主动的解决方案。每当我需要专门处理 JSON(没有 XML 或其他格式)时,我现在用以下实例替换我的 RestTemplate bean:

public class JsonRestTemplate extends RestTemplate {

    public JsonRestTemplate(
            ClientHttpRequestFactory clientHttpRequestFactory) {
        super(clientHttpRequestFactory);

        // Force a sensible JSON mapper.
        // Customize as needed for your project's definition of "sensible":
        ObjectMapper objectMapper = new ObjectMapper()
                .registerModule(new Jdk8Module())
                .registerModule(new JavaTimeModule())
                .configure(
                        SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

        List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
        MappingJackson2HttpMessageConverter jsonMessageConverter = new MappingJackson2HttpMessageConverter() {

            public boolean canRead(java.lang.Class<?> clazz,
                    org.springframework.http.MediaType mediaType) {
                return true;
            }    
            public boolean canRead(java.lang.reflect.Type type,
                    java.lang.Class<?> contextClass,
                    org.springframework.http.MediaType mediaType) {
                return true;
            }
            protected boolean canRead(
                    org.springframework.http.MediaType mediaType) {
                return true;
            }
        };

        jsonMessageConverter.setObjectMapper(objectMapper);
        messageConverters.add(jsonMessageConverter);
        super.setMessageConverters(messageConverters);

    }
}

这种自定义使得 RestClient 除了 JSON 之外无法理解任何其他内容。好处是任何可能出现的错误消息都会更明确地说明问题所在。

在我的例子中,这是由于运行时类路径中缺少 jackson-core、jackson-annotations 和 jackson-databind jar 造成的。 它并没有像人们期望的那样抱怨通常的 ClassNothFoundException,而是抱怨原始问题中提到的错误。

您需要创建自己的转换器并在发出 GET 请求之前实现它。

RestTemplate  restTemplate = new RestTemplate();

List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();        

MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));         
messageConverters.add(converter);  
restTemplate.setMessageConverters(messageConverters);    

其他可能的解决方案:我尝试将 restTemplate.getForObject 的结果映射到私有 class 实例(在我的工作 class 中定义)。它没有工作,但如果我将对象定义为 public,在它自己的文件中,它工作正常。

当响应缺少该字段时,

Spring 将默认内容类型设置为 octet-stream。您需要做的就是添加一个消息转换器来解决这个问题。

调试此问题的一种方法是首先将响应作为 String.class 然后使用

Gson().fromJson(StringResp.body(), MyDTO.class)

它很可能仍然会失败,但这次它会首先抛出导致错误的字段。修改后,我们可以像以前一样继续使用以前的方法。

ResponseEntity<String> respStr = restTemplate.exchange(URL,HttpMethod.GET, entity,  String.class);  
Gson g = new Gson();
    

以下步骤将生成导致问题的字段的错误。

MyDTO resp = g.fromJson(respStr.getBody(), MyDTO.class);

我没有收到错误消息,但它会指出有问题的字段并解释原因。解决这些问题并再次尝试以前的方法。

我尝试使用 Feign,但遇到了同样的问题,据我所知,HTTP 消息转换器会有所帮助,但我想了解如何实现这一点。

@FeignClient(name = "mobilesearch", url = "${mobile.search.uri}" ,
        fallbackFactory = MobileSearchFallbackFactory.class,
        configuration = MobileSearchFeignConfig.class)
public interface MobileSearchClient {

    @RequestMapping(method = RequestMethod.GET)
    List<MobileSearchResponse> getPhones();
}

您必须为解码器使用客户配置,MobileSearchFeignConfig,

public class MobileSearchFeignConfig {

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }


    @Bean
    public Decoder feignDecoder() {
        return new ResponseEntityDecoder(new SpringDecoder(feignHttpMessageConverter()));
    }

    public ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
        final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(new MappingJackson2HttpMessageConverter());
        return new ObjectFactory<HttpMessageConverters>() {
            @Override
            public HttpMessageConverters getObject() throws BeansException {
                return httpMessageConverters;
            }
        };
    }

    public class MappingJackson2HttpMessageConverter extends org.springframework.http.converter.json.MappingJackson2HttpMessageConverter {
        MappingJackson2HttpMessageConverter() {
            List<MediaType> mediaTypes = new ArrayList<>();
            mediaTypes.add(MediaType.valueOf(MediaType.TEXT_HTML_VALUE + ";charset=UTF-8"));
            setSupportedMediaTypes(mediaTypes);
        }
    }

}

请添加具有 jackson databind 包的共享依赖项。希望这会解决问题。

  <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.12.1</version>
  </dependency>

这样您就可以使用 resttemplate 获取对象响应并使用 MediaType.APPLICATION_JSON

设置 contentType
public List<Employee> getListofEmployee()
 {
    HttpHeaders headers = new HttpHeaders();
    headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
    HttpEntity<String> entity = new HttpEntity<String>(headers);
    ResponseEntity<List<Employee>> response = restTemplate.exchange("http://hello-server/rest/employees",
    HttpMethod.GET,entity, new ParameterizedTypeReference<List<Employee>>() {});
    return response.getBody(); //this returns List of Employee 
  }