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

91

使用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)

我试着设置contentType:

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消息转换器

  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)

我也尝试添加类。

@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)

如果您指定内容类型(请参见https://dev59.com/LJjga4cB1Zd3GeqPPMl_),会发生什么? - StanislavL
你能展示一下你的MessageList类吗?你的Controller呢? - zakaria amine
1
你确定响应头包含的响应内容类型是json吗?因为消息显示内容是text/html;charset.. - Zico
不好意思,我不确定。我还没有开发过RESTful Web服务 :-( - Nunyet de Can Calçada
private HttpEntity<MessageList> connectToRestService() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("http://592693f43c87815f9b8145e9:f099c85d84d4e325a2186c02bd0caeef@backend.tdk.com/api/devicetypes/591570373c87894b4eece34d/messages"); HttpEntity<MessageList> entity = new HttpEntity<>(headers); HttpEntity<MessageList> response = restTemplate.exchange(builder.build().encode().toUri(), HttpMethod.GET, entity, MessageList.class); return response; } 尝试一下这个代码? - harshavmb
16个回答

166

这里的主要问题是从服务中收到了内容类型[text/html;charset=iso-8859-1],但实际上正确的内容类型应该是应用程序/json;charset=iso-8859-1

为了解决这个问题,你可以引入自定义消息转换器,并将其注册为所有类型的响应(即忽略响应内容类型标头)。像这样

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

谢谢,你救了我的一天。我遇到了同样的问题,服务返回的是XML,但内容类型错误为Octet。 - user530158
2
这个答案并不是一个通用的解决方案... 我从服务端得到了 application/json; utf-8 的数据,并且已经正确地注册为 Jackson 的消息转换器,但它仍然无法工作。 - Alex R
4
我会将这行代码转换为converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL)); - JGleason
8
我收到了java.lang.IllegalArgumentException: 'Content-Type' cannot contain wildcard type '*'的错误信息。该错误通常表示Content-Type中包含了通配符"*",需要进行修改。 - user7294900
@user7294900 请尝试使用特定的MediaType,例如MediaType.APPLICATION_PDF,而不是MediaType.ALL。 - arnonuem
也许在使用Spring模板消费HTML响应时,上述解决方案对我不起作用的唯一原因是没有将以下代码添加到头部: Charset utf8 = Charset.forName("UTF-8"); MediaType mediaType = new MediaType("text", "html", utf8); headers.setContentType(mediaType); 在调用 ResponseEntity<String> objectResponseEntity = restTemplate.exchange(ApiUrl, HttpMethod.POST, httpEntity, String.class); 时。 - veritas

74

虽然被接受的答案解决了提问者最初的问题,但是通过谷歌搜索找到这个问题的大多数人可能遇到完全不同的问题,只是碰巧抛出了相同的“没有适合的HttpMessageConverter”异常。

在内部发生的情况是,MappingJackson2HttpMessageConverter吞噬了其canRead()方法中发生的任何异常,该方法应自动检测有效负载是否适用于json解码。 异常被简单的布尔返回值所替换,基本上向更高级别的API(RestClient)传达“对不起,我不知道如何解码此消息”的信息。只有在所有其他转换器的canRead()方法都返回false之后,高级别API才会抛出“未找到适当的HttpMessageConverter”异常,完全掩盖了真正的问题。

对于尚未找到根本原因的人(像你和我一样,但不是提问者),解决此问题的方法是在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以外的任何内容。好处是,任何可能出现的错误信息都会更明确地指出问题所在。


1
这是一个有价值的洞见。 - Arpit Agrawal
1
由于我使用了TestRestTemplate,所以我使用了一个字符串结果类型而不是bean,因为Spring将使用字符串转换器,您将获得至少更有意义的消息(对我来说是400 Bad Request)。 - growlingchaos
3
这个需要更多赞。为了找出具体问题,我在AbstractJackson2HttpMessageConverter.canRead()Exception [include Subclasses] (caught and uncaught)之间切换断点多次。最终我发现了一个IllegalArgumentException("Conflicting getter definitions for property <x> [...])",所以我必须通过@JsonIgnore来忽略其中一个不正确的属性值才能进行反序列化。 - aPonza
1
对于像我一样想将对象序列化以发送到某些服务器的人,需要调试canWrite()方法。在我的方面上,我发现了com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.hasSerializerFor()中真正的根本异常。尝试在那里设置一些断点。 - Christophe

53

我曾遇到一个非常类似的问题,最终发现问题很简单;我的客户端没有包含Jackson依赖,即使代码都编译正确,用于JSON的自动转换器也没有被包含。请参见这个与RestTemplate相关的解决方案

简而言之,我在我的pom.xml中添加了一个Jackson依赖项,问题就解决了:

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

1
在所提及的文章中,内容类型已经设置为application/json。原始帖子是针对内容类型text/html的。 - Net Dawg

15

调试此问题的一种方法是首先将响应作为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);

我手头没有错误信息,但它会指出有问题的字段并解释原因。解决这些问题后,再尝试之前的方法。


谢谢,这种方法对我有帮助。在我的情况下,我的后端CRM系统返回的是实际的html而不是json。在将响应作为字符串而不是期望的实际对象进行处理之后,我能够看到CRM以html形式抛出的错误。 - anand
这种方法很有帮助,因为问题出在我尝试反序列化的类上。 - Gabe Gates

10
如果@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); 

您也可以通过以下方式设置内容类型:requestHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - Suresh
1
我曾遇到类似的问题,使用了这个解决方案,并使用常规的ObjectMapper将对象还原。代码片段如下:`HttpHeaders headers = new HttpHeaders(); headers.add(HttpHeaders.CONTENT_TYPE, "text/json"); ResponseEntity resp = restTemplate.exchange(configs.getAddress(), HttpMethod.GET, new HttpEntity(headers), String.class); Jwks jwks = new ObjectMapper().readValue(resp.getBody(), Jwks.class);` - camposer

4

在我的情况下,@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 Gerbaux

这对我也起作用了。 - Stefan Falk
在Spring Boot中,你把它设置在哪里? - Capacytron

3

在发起 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);    

3

通过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 
  }

如果您正在使用Jackson库,请不要忘记在pom.xml中添加它。
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.14.1</version>
</dependency>

1
在这里添加一个简单的头文件解决了我的问题。 - Vokail

2
请添加共享依赖项,其中包含 jackson databind 包。希望这能解决问题。
  <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.12.1</version>
  </dependency>

谢谢,问题已解决。我之前遇到了错误:Restclientexception no httpmessageconverter。我想提一下添加“spring-boot-starter-web”也解决了我的问题,但是当我尝试使用jackson依赖时,我更喜欢它。它更精确简洁。 - M. Amer

1

当响应缺少该字段时,Spring将默认内容类型设置为 octet-stream 。您只需要添加一个消息转换器来解决此问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接