web-dev-qa-db-ja.com

応答タイプに適したHttpMessageConverterが見つかりません

このコードでスプリングを使用する:

List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
for(HttpMessageConverter httpMessageConverter : messageConverters){
  System.out.println(httpMessageConverter);
}
ResponseEntity<ProductList> productList = restTemplate.getForEntity(productDataUrl,ProductList.class);

私は得る

org.springframework.http.converter.ByteArrayHttpMessageConverter@34649ee4
org.springframework.http.converter.StringHttpMessageConverter@39fba59b
org.springframework.http.converter.ResourceHttpMessageConverter@383580da
org.springframework.http.converter.xml.SourceHttpMessageConverter@409e850a
org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter@673074aa
org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter@1e3b79d3
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@52bb1b26

org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.mycopmany.ProductList] and content type [text/html;charset=UTF-8]

Pojoのスニペット:

@XmlRootElement(name="TheProductList")
public class ProductList {

@XmlElement(required = true, name = "date")
private LocalDate importDate;
35
NimChimpsky

Springの観点からすると、HttpMessageConverterに登録されているRestTemplateインスタンスは、text/htmlコンテンツをProductListオブジェクトに変換できません。対象のメソッドはHttpMessageConverter#canRead(Class, MediaType)です。上記のすべての実装は、Jaxb2RootElementHttpMessageConverterを含むfalseを返します。

HttpMessageConverterはHTTP応答を読み取ることができないため、処理は例外で失敗します。

サーバーの応答を制御できる場合は、Content-typeapplication/xmltext/xml、またはapplication/*+xmlに一致するものに設定するように変更します。

サーバーの応答を制御しない場合は、text/htmlを読み取りおよび変換できる独自のHttpMessageConverter(Springクラスを拡張できる、AbstractXmlHttpMessageConverterおよびそのサブクラスを参照)を作成および登録する必要があります。 。

35

サーバーのメディアタイプの応答を変更できない場合は、GsonHttpMessageConverterを拡張して追加のサポートタイプを処理できます

public class MyGsonHttpMessageConverter extends GsonHttpMessageConverter {
    public MyGsonHttpMessageConverter() {
        List<MediaType> types = Arrays.asList(
                new MediaType("text", "html", DEFAULT_CHARSET),
                new MediaType("application", "json", DEFAULT_CHARSET),
                new MediaType("application", "*+json", DEFAULT_CHARSET)
        );
        super.setSupportedMediaTypes(types);
    }
}
9
Vadim Zin4uk

Spring Bootを使用している場合は、クラスパスにJackson依存関係があることを確認する必要があります。これは、次の方法で手動で実行できます。

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

または、Webスターターを使用できます。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
7
Wim Deblauwe

RestTemplateを拡張するRestTemplateXMLクラスを作成できます。次に、doExecute(URI, HttpMethod, RequestCallback, ResponseExtractor<T>)をオーバーライドし、明示的にresponse-headersを取得し、content-typeapplication/xmlに設定します。

これで、Springはヘッダーを読み取り、それが「application/xml」であることを認識します。これは一種のハックですが、動作します。

public class RestTemplateXML extends RestTemplate {

  @Override
  protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback,
        ResponseExtractor<T> responseExtractor) throws RestClientException {

     logger.info( RestTemplateXML.class.getSuperclass().getSimpleName() + ".doExecute() is overridden");

     Assert.notNull(url, "'url' must not be null");
     Assert.notNull(method, "'method' must not be null");
     ClientHttpResponse response = null;
     try {
        ClientHttpRequest request = createRequest(url, method);
        if (requestCallback != null) {
           requestCallback.doWithRequest(request);
        }
        response = request.execute();

        // Set ContentType to XML
        response.getHeaders().setContentType(MediaType.APPLICATION_XML);

        if (!getErrorHandler().hasError(response)) {
           logResponseStatus(method, url, response);
        }
        else {
           handleResponseError(method, url, response);
        }
        if (responseExtractor != null) {
           return responseExtractor.extractData(response);
        }
        else {
           return null;
        }
     }
     catch (IOException ex) {
        throw new ResourceAccessException("I/O error on " + method.name() +
              " request for \"" + url + "\":" + ex.getMessage(), ex);
     }
     finally {
        if (response != null) {
           response.close();
        }
     }

  }

  private void logResponseStatus(HttpMethod method, URI url, ClientHttpResponse response) {
     if (logger.isDebugEnabled()) {
        try {
           logger.debug(method.name() + " request for \"" + url + "\" resulted in " +
                 response.getRawStatusCode() + " (" + response.getStatusText() + ")");
        }
        catch (IOException e) {
           // ignore
        }
     }
  }

  private void handleResponseError(HttpMethod method, URI url, ClientHttpResponse response) throws IOException {
     if (logger.isWarnEnabled()) {
        try {
           logger.warn(method.name() + " request for \"" + url + "\" resulted in " +
                 response.getRawStatusCode() + " (" + response.getStatusText() + "); invoking error handler");
        }
        catch (IOException e) {
           // ignore
        }
     }
     getErrorHandler().handleError(response);
  }
}
4
Chester Leung

すべての回答に加えて、何か他のもの(つまりtext/html)を期待しているときに応答application/jsonを受け取った場合、サーバー側でエラーが発生したことを示唆することがあります(たとえば404)データの代わりにエラーページが返されました。

それで私の場合はそうなった。それが誰かの時間を節約することを願っています。

3
user1913596

RestTemplateにすべてのメディアタイプを受け入れるように指示することもできます。

@Bean
public RestTemplate restTemplate() {
   return new RestTemplate();
   final RestTemplate restTemplate = new RestTemplate();

   List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
   MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
   converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
   messageConverters.add(converter);
   restTemplate.setMessageConverters(messageConverters);

   return restTemplate;
}
2
Markoorn

または、使用できます

public void setSupportedMediaTypes(List supportedMediaTypes)

AbstractHttpMessageConverter<T>に属するメソッドで、好きなContentTypesを追加します。この方法により、MappingJackson2HttpMessageConvertercanRead()に応答を許可し、それを目的のクラス(この場合はProductListクラス)に変換できます。

そして、このステップはSpring Contextの初期化に結び付けられるべきだと思います。たとえば、次を使用して

applicationListenerを実装します{...}

0
avidya

これを試して:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.6.0</version>
</dependency>
0
Leonardozm

Vadim Zin4uk's answer の改良点は、既存のGsonHttpMessageConverterクラスを使用するだけで、setSupportedMediaTypes()セッターを呼び出すことです。

スプリングブートアプリの場合、これは構成クラスに以下を追加することになります。

@Bean
public GsonHttpMessageConverter gsonHttpMessageConverter(Gson gson) {
    GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
    converter.setGson(gson);
    List<MediaType> supportedMediaTypes = converter.getSupportedMediaTypes();
    if (! supportedMediaTypes.contains(TEXT_PLAIN)) {
        supportedMediaTypes = new ArrayList<>(supportedMediaTypes);
        supportedMediaTypes.add(TEXT_PLAIN);
        converter.setSupportedMediaTypes(supportedMediaTypes);
    }
    return converter;
}
0