En utilisant un ressort, avec ce code:
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
for(HttpMessageConverter httpMessageConverter : messageConverters){
System.out.println(httpMessageConverter);
}
ResponseEntity<ProductList> productList = restTemplate.getForEntity(productDataUrl,ProductList.class);
Je reçois
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]
Le bout du pojo:
@XmlRootElement(name="TheProductList")
public class ProductList {
@XmlElement(required = true, name = "date")
private LocalDate importDate;
Du point de vue de Spring, aucune des instances HttpMessageConverter
inscrites avec RestTemplate
ne peut convertir le contenu text/html
en un objet ProductList
. La méthode d'intérêt est HttpMessageConverter#canRead(Class, MediaType)
. L'implémentation de tous les éléments ci-dessus renvoie false
, y compris Jaxb2RootElementHttpMessageConverter
.
Etant donné que HttpMessageConverter
ne peut pas lire votre réponse HTTP, le traitement échoue avec une exception.
Si vous pouvez contrôler la réponse du serveur, modifiez-la pour définir le Content-type
sur application/xml
, text/xml
ou un autre paramètre correspondant à application/*+xml
.
Si vous ne contrôlez pas la réponse du serveur, vous devrez écrire et enregistrer votre propre HttpMessageConverter
(qui peut étendre les classes Spring, voir AbstractXmlHttpMessageConverter
et ses sous-classes) pouvant lire et convertir text/html
.
Si vous ne pouvez pas modifier la réponse du type de média du serveur, vous pouvez étendre GsonHttpMessageConverter pour traiter d'autres types de support.
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);
}
}
Si vous utilisez Spring Boot, vous voudrez peut-être vous assurer que la dépendance de Jackson est dans votre chemin de classe. Vous pouvez le faire manuellement via:
<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>
Ou vous pouvez utiliser le démarreur Web:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Vous pouvez constituer une classe, RestTemplateXML, qui étend RestTemplate. Ensuite, remplacez doExecute(URI, HttpMethod, RequestCallback, ResponseExtractor<T>)
et obtenez explicitement response-headers
et définissez content-type
sur application/xml
.
Maintenant, Spring lit les en-têtes et sait qu'il s'agit de 'application/xml'. C'est un peu un bidouillage mais ça marche.
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);
}
}
En plus de toutes les réponses, si vous recevez dans la réponse text/html
alors que vous attendiez quelque chose d'autre (par exemple, application/json
), cela peut suggérer qu'une erreur s'est produite côté serveur (par exemple, 404) et que la page d'erreur a été renvoyée au lieu de vos données.
C'est donc arrivé dans mon cas. J'espère que cela fera gagner du temps à quelqu'un.
Ou vous pouvez utiliser
public void setSupportedMediaTypes (liste prise en chargeMediaTypes)
méthode qui appartient à AbstractHttpMessageConverter<T>
, pour ajouter de la ContentTypes
que vous aimez. De cette façon, vous pouvez laisser la réponse MappingJackson2HttpMessageConverter
canRead()
et la transformer en votre classe souhaitée, qui dans ce cas est ProductList Class.
et je pense que cette étape devrait être liée à l'initialisation du contexte de printemps. par exemple, en utilisant
implémente ApplicationListener { ... }
Essaye ça:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.6.0</version>
</dependency>
Un raffinement de La réponse de Vadim Zin4uk consiste simplement à utiliser la classe GsonHttpMessageConverter existante, mais à appeler le paramètre setSupportedMediaType ().
Pour les applications de démarrage printanier, cela revient à ajouter ce qui suit à vos classes de configuration:
@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;
}