Nous pouvons rapidement développer l'interface de repos via Spring Boot, et nous pouvons également devoir appeler l'interface de repos interne et externe via Spring Boot pour terminer la logique métier lors de l'implémentation de l'interface.
Dans Spring Boot, il existe deux façons courantes d'appeler REST API, qui sont utilisés pour implémenter les appels de service via le RestTemplate intégré ou développer votre propre outil client HTTP.
RestTemplate a des fonctions de base très puissantes, mais dans certains scénarios spéciaux, nous pouvons être plus habitués à utiliser nos propres classes d'outils encapsulées, telles que le téléchargement de fichiers dans des systèmes de fichiers distribués, le traitement des demandes HTTPS avec des certificats, etc.
Cet article utilise RestTemplate comme exemple pour enregistrer plusieurs problèmes et solutions trouvés lors de l'utilisation de RestTemplate pour appeler l'interface.
1. Introduction à Restemplate
1. Qu'est-ce que RESTERMPLAT
Le httpclient que nous nous encapsulons a généralement un code de modèle, comme l'établissement d'une connexion, la construction d'un en-tête de demande et un corps de demande, puis d'analyser les informations de réponse en fonction de la réponse et enfin de fermeture de la connexion.
RestTemplate est une réamisulation de HTTPClient dans le printemps, ce qui simplifie le processus de lancement des demandes HTTP et de réponses de traitement, avec des niveaux d'abstraction plus élevés, une réduction du code de modèle de consommateur et une création de code moins redondant.
En fait, si vous pensez à de nombreuses classes XXXTemplates sous Spring Boot, elles fournissent également diverses méthodes de modèle, mais le niveau d'abstraction est plus élevé et plus de détails sont cachés.
Soit dit en passant, Spring Cloud a un appel de service déclaratif Feign, qui est implémenté en fonction de Netflix Feign, intègre le ruban Spring Cloud et Spring Cloud Hystrix et implémente une méthode de définition du client Web déclarative.
Essentiellement, Feign consiste à le résumer à nouveau sur la base de RestTemplate, ce qui nous aide à définir et à mettre en œuvre la définition des interfaces de service dépendantes.
2. Méthodes communes pour repos stand
Il existe de nombreuses méthodes de demande pour les services de repos communs, tels que Get, Post, Put, Supprimer, Head, Options, etc. Rest, implémente la méthode la plus courante, et les plus couramment utilisées sont Get et Post. Vous pouvez vous référer au code source lors de l'appel de l'API. Voici quelques définitions de méthode (obtenir, publier, supprimer):
méthodes
public <t> t getForObject (URL de chaîne, classe <T> ResponseType, objet ... urivariables) public <T> ResponseNtity <T> getForentity (String URL, classe <T> ResponseType, objet ... Urivariables) public <T> T PostForObject (URL de chaîne, @nullable Object Demande, Class <T> ResponseTyTy URL, @Nullable Object Demande, classe <T> ResponseType, objet ... Urivariables) public void Delete (URL de chaîne, objet ... Urivariables) public void Delete (URI URL)
Dans le même temps, vous devez faire attention à deux autres méthodes "flexibles" échangeant et exécutez.
L'échange exposé par Rest -mplate est différent des autres interfaces:
(1) Autoriser l'appelant à spécifier la méthode de la demande HTTP (obtenir, publier, supprimer, etc.)
(2) Des informations sur le corps et l'en-tête peuvent être ajoutées à la demande, et son contenu est décrit par le paramètre «Httpentity <?> RequestEntity»
(3) Exchange prend en charge les «paramètres de contenu du type» (c'est-à-dire la classe générique) en tant que type de retour, et cette fonctionnalité est décrite par «ParameteriséeTypereference <T> ResponseType».
RESTTEMPLAT ALL GET, POST ET AUTRES MÉTHODES, et l'appel final de la méthode EXECUTE est la méthode EXECUTE. L'implémentation interne de la méthode d'excuit consiste à convertir l'URI de format de chaîne en java.net.uri, puis la méthode Doexecure est appelée. La mise en œuvre de la méthode Doexecute est la suivante:
Doexecute
/ ** * Exécutez la méthode donnée sur l'URI fourni. * <p> Le {@link clientHttpRequest} est traité à l'aide du {@link requestCallback}; * La réponse avec le {@Link ResponseExtractor}. * @param URL L'URL entièrement expansée pour se connecter à * @param Méthode La méthode HTTP à exécuter (Get, Post, etc.) * @param requestCallback Objet qui prépare la demande (peut être {@code null}) * @param réponsextractor {@Link ResponseExtractor} * / @Nullable Protected <T> T DOEXECUTE (URL URL, @Nullable HttpMethod Method, @nullable requestCallback requestCallback, @nullable réponsextractor <Tl, "'url' ne doit pas être null"); Assert.notnull (méthode, "'méthode' ne doit pas être nul"); ClientHttpResponse Response = null; essayez {clientHttpRequest request = CreateResest (URL, méthode); if (requestCallback! = null) {requestCallback.DowithRequest (request); } réponse = request.execute (); HandlerResponse (URL, méthode, réponse); if (réponsextractor! = null) {return réponsextractor.extractData (réponse); } else {return null; }} catch (ioException ex) {String Resource = url.ToString (); String Query = url.getRawquery (); Ressource = (query! = null? Resource.SubString (0, Resource.Indexof ('?')): Resource); lancer une nouvelle ressourceAccessException ("Erreur d'E / S sur" + méthode.name () + "Demande pour /" "+ ressource +" / ":" + ex.getMessage (), ex); } enfin {if (réponse! = null) {réponse.close (); }}}La méthode Doexecute encapsule les méthodes de modèle, telles que la création de connexions, les demandes et les réponses de traitement, les connexions de fermeture, etc.
Quand la plupart des gens voient cela, ils pensent probablement que le résumer un roseur est comme ça, non?
3. Appel simple
Prenez un appel de poste comme exemple:
Marchandises
Package com.power.demo.restClient; import com.power.demo.common.appconst; import com.power.demo.restclient.clientRequest.clientGetGoodsByGooSiDequest; Importer com.powerSiSmo.restClient.ClientResponse.ClientGoodsbyGoodsIrSiSponse; Importer. org.springframework.beans.factory.annotation.autowired; import org.springframework.beans.factory.annotation.value; import org.springframework.sterreotype.component; import org.springframework.web.client.resttemlate; / ** * Product REST Client (pour Deco Test) CLASS GOODSSERVICECLIENT {// L'URL de l'interface appelée par le consommateur de service est la suivante: http: // localhost: 9090 @value ("$ {printemps.power.serviceUrl}") String privé _ServiceUrl; @Autowired Private RestTemplate RestTemplate; public ClientGetGoodsByGooDSidResponse GetGoodsByGoodSid (ClientGetGoodsByGooSiDrequest Request) {String svcurl = getGoodssvcurl () + "/ getInfoById"; ClientGetGoodsByGooDSidResponse Response = null; try {réponse = restTemplate.postForObject (svcurl, request, clientGetGoodsByGoodSidResponse.class); } catch (exception e) {e.printStackTrace (); Response = new ClientGetGoodsByGooDSidResponse (); Response.SetCode (AppConst.fail); réponse.SetMessage (e.toString ()); } Retour Response; } String privé getGoodssvcurl () {String url = ""; if (_ServiceUrl == null) {_ServiceUrl = ""; } if (_ServiceUrl.length () == 0) {return url; } if (_ServiceUrl.substring (_ServiceUrl.length () - 1, _ServiceUrl.length ()) == "/") {url = string.format ("% sapi / v1 / biens", _ServiceUrl); } else {url = string.format ("% s / api / v1 / biens", _ServiceUrl); } URL de retour; }}Dans la démo, la méthode RestTemplate.PostForObject est appelée directement, et l'entité de désérialisation est convertie en cette encapsulation interne de Rest -mplate.
2. Résumé des problèmes
1. Aucune HTTPMessageConverter appropriée trouvée pour exception de type de demande
Ce problème se produit généralement lorsqu'un objet entrant est passé dans PostForObject pour les appels.
Analysez le code source RestTemplate. Dans la méthode DowithRequest de la classe HttpentityRequestCallback, si les MessageConverters (ce champ continuera d'être mentionné plus tard) ne répond pas à la logique qui revient sautée (c'est-à-dire qu'il n'y a pas de correspondance httpMessageConverter), l'exception ci-dessus est expulsée:
HttpentityRequestCallback.DowithRequest
@Override @SuppressWarnings ("Unchecked") public void dowithrequest (clientHttpRequest httpRequest) lance ioException {super.dowithrequest (httprequest); Objet requestBody = this.requesttentiy.getBody (); if (requestbody == null) {httpheaders httpheaders = httprequest.getheaders (); Httpheaders requestHeaders = this.requestentity.getheaders (); if (! requestHelers.iSempty ()) {for (map.entry <string, list <string>> entrée: requestHelers.entrySet ()) {httpheaders.put (entrée.getKey (), new LinkedList <> (entry.getValue ())); }} if (httpheaders.getContentLength () <0) {httpheaders.setContentLength (0l); }} else {class <?> requestbodyClass = requestbody.getClass (); Type requestBodyType = (this.requesttity instanceof requestEntity? ((RequestEntity <?>) This.requestentity) .getType (): requestBodyClass); Httpheaders httpheaders = httpRequest.GetHeaders (); Httpheaders requestHeaders = this.requestentity.getheaders (); MediaType requestContentType = requestHelers.getContentType (); pour (httpMessageConverter <?> MessageConverter: GetMessageConverters ()) {if (MessageConverter Instance de générichttpMessageConverter) {generichttpmessageConverter <Object> genericConverter = (générichttpmesageConverter <Bobile>) MessagEConverter; if (genericConverter.canwrite (requestBodyType, requestBodyClass, requestContentType)) {if (! requestHelers.isempty ()) {for (map.entry <string, list <string> enty: requestHeders.EntrySet ()) {httpheaders.put (entrée.getKey (), new LinkedList <> (entrée.GetValie ()); } } if (logger.isDebugEnabled()) { if (requestContentType != null) { logger.debug("Writing [" + requestBody + "] as /" + requestContentType + "/" using [" + messageConverter + "]"); } else { logger.debug("Writing [" + requestBody + "] using [" + MessageConverter + "]");}} générique Entrée: requestHelers.EntrySet ()) {httpheaders.put (entrée.getKey (), new LinkedList <> (entrée.getValue ())); en utilisant ["+ MessageConverter +"] "); } else {logger.debug ("Writing [" + requestBody + "]"); }} ((HttpMessageConverter <Bject>) MessageConverter) .Write (requestBody, requestContentType, httpRequest); retour; }} String Message = "Impossible d'écrire la demande: Aucun httpMessageConverter approprié trouvé pour le type de demande [" + requestbodyClass.getName () + "]"; if (requestContentType! = null) {message + = "et contenu type [" + requestContentType + "]"; } Jetez une nouvelle restClientException (message); }} La solution la plus simple consiste à envelopper l'en-tête de demande HTTP et à sérialiser l'objet de demande dans une chaîne pour passer les paramètres. L'exemple de code de référence est le suivant:
postforObject
/ * * Post Request Call * * / public static String PostForObject (RestTemplate RestTemplate, URL de chaîne, params d'objet) {Httpheaders en-têtes = new httpheaders (); MediaType type = mediaType.PaSeMediaType ("application / json; charset = utf-8"); headers.setContentType (type); headers.add ("accepter", mediatype.application_json.toString ()); String JSON = serializeUtil.serialize (params); Httpentity <string> formentity = new httpentity <string> (json, en-têtes); String result = restTemplate.PostForObject (url, formentity, string.class); Résultat de retour; }Si nous voulons également renvoyer directement l'objet, nous pouvons désérialiser directement la chaîne retournée:
postforObject
/ * * Post Request Call * * / public static <T> T PostForObject (RestTemplate RestTemplate, URL de chaîne, paramètres d'objet, classe <T> Clazz) {t Response = NULL; String respStr = PostForObject (RestTemplate, URL, params); réponse = serializeUtil.deserialize (respstr, clazz); réponse de retour; }Parmi eux, il existe de nombreux outils pour la sérialisation et la désérialisation, tels que Fastjson, Jackson et Gson.
2. Aucune exception HTTPMessageConverter
Tout comme une exception se produit lors du lancement d'une demande, il y aura également des problèmes lors de la gestion de la réponse.
Quelqu'un a posé la même question sur Stackoverflow. La cause profonde est que le convertisseur de message HTTP HTTPMessageConverter manque de type MIME. C'est-à-dire que lorsque HTTP transmet le résultat de sortie au client, le client doit démarrer l'application appropriée pour traiter le document de sortie. Cela peut être fait via plusieurs types MIME (Protocols de l'augmentation du courrier Internet multifonctionnels).
Pour les réponses côté serveur, de nombreux HTTPMessageConverter prennent en charge différents types de supports (mimetypes) par défaut. La prise en charge par défaut de StringHttpMessageConverter est mediatype.text_plain, la prise en charge par défaut de sourcehttpmesageConverter est mediatype.text_xml, la prise en charge par défaut de formhttpmesageConverter est mediatype.application_form_urlencoded et mediatype.multipart_form_data. Dans le service de repos, le plus que nous utilisons est MappingJackson2HttpMessageConverter. , Il s'agit d'un convertisseur relativement général (hérité de l'interface générichttpmesageconverter). Selon l'analyse, le MIMETYPE qu'il prend en charge par défaut est MediaType.Application_json:
MappingJackson2httpMessageConverter
/ ** * Construisez un nouveau {@link mappingjackson2httpMessageConverter} avec une personnalité {@Link ObjectMapper}. * Vous pouvez utiliser {@link jackson2objectmapperbuilder} pour le construire facilement. * @see jackson2objectmapperbuilder # json () * / public mappingjackson2httpmessageConverter (objectMapper objectMapper) {super (objectMapper, mediaType.Application_json, new mediaType ("application", "* + json")); }Cependant, la réponse par défaut de certaines interfaces d'application MIMETYPE n'est pas d'application / JSON. Par exemple, si nous appelons une interface de prévision météorologique externe, si nous utilisons la configuration par défaut de RestTemplate, il n'est pas possible de renvoyer directement une réponse de chaîne:
String url = "http://wthrcdn.etouch.cn/weather_mini?city=shanghai"
Cependant, si nous voulons retourner directement un objet entité:
String url = "http://wthrcdn.etouch.cn/weather_mini?city=shanghai" ;clientweathererResultvo weatherresultvo = resttemplate.getForObject (URL, clientweatherrerResultvo.class);
Puis signaler une exception directement:
Impossible d'extraire la réponse: Aucun HTTPMessageConverter approprié trouvé pour le type de réponse [classe]
et type de contenu [Application / Octet-Stream]
Beaucoup de gens ont rencontré ce problème. La plupart d'entre eux sont confus lorsqu'ils le rencontrent pour la première fois. De nombreuses interfaces sont renvoyées au format JSON, XML ou texte brut. Qu'est-ce que l'application / l'octet-stream?
Vérifiez le code source RestTemplate et gardez-le tout au long, vous constaterez que la méthode ExtractData de la classe HTTPMessageConverterExtractor a une réponse de réponse et de désérialisation. S'il ne réussit pas, les informations d'exception jetées sont les mêmes que ci-dessus:
HttpMessageConverterExtractor.extractData
@Override @SuppressWarnings ({"Unchecked", "RawTypes", "Resource"}) public t ExtractData (ClientHttpResponse Response) lève ioException {messageBodyClientHttpResponseWrapper AnswerWrapper = new MessageBodyClientHttpResponseWrapper (Response); if (! ResponseWrapper.HasmesSageBody () || ResponseWrapper.HasempTyMessageBody ()) {return null; } MediaType contentType = getContentType (réponsewrapper); essayez {pour (httpMessageConverter <?> MessageConverter: this.MessageConverters) {if (MessageConverter Instance de générichttpMessageConverter) {générichttpmessageConverter <?> GenericMessageConverter = (générichttpmessageConverter <?> MessagCaRter; if (genericMessageConverter.canRead (this.ResponSetype, null, contentType)) {if (logger.isdebugeabled ()) {Logger.debug ("Reading [" + this.ResageSetype + "] as /" "+ contentType +" / "en utilisant [" + messageConverter + "]"); } return (t) genericMessageConverter.read (this.ResponSetype, null, réponsewrapper); }} if (this.responseclass! = null) {if (MessageConverter.canRead (this.ResponSeclass, contentType)) {if (Logger.isdebugeNabled ()) {Logger.debug ("Reading [" + this.Responseclass.getName () + "] As /" "+" + "/" MessageConverter + "]"); } return (t) MessageConverter.read ((classe) this.ResponSeclass, réponsewrapper); }}}} catch (ioException | httpMessageNotReadableException ex) {lancez new restClientException ("Erreur tout en extraction de la réponse pour type [" + this.responsetype + "] et contenu type [" ContentType + "]", ex); } Throw New RestClientException ("n'a pas pu extraire la réponse: Aucun HttpMessageConverter HttpMessageSonde approprié a trouvé" + "pour le type de réponse [" + this.ResponSetype + "] et le type de contenu [" + contentType + "]"); } L'exemple de code de la solution sur stackOverflow est acceptable, mais il n'est pas exact. Des mimétypes communs doivent être ajoutés. Je publierai le code, je pense, est correct:
Resttemplateconfig
package com.power.demo.restclient.config; import com.fasterxml.jackson.databind.objectmapper; import com.google.common.collect.lists; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.wotation.client.restterbleder; org.springframework.context.annotation.bean; import org.springframework.http.mediaType; import org.springframework.http.converter. *; import org.springframework.http.converter. org.springframework.http.converter.feed.atomfeedHttpMessageConverter; import org.springframework.http.converter.feed.rsschannelhttpmessageconverter; import org.springframework.http.converter.json.gsonhtpmesgagecond; org.springframework.http.converter.json.jsonBhttpMessageConverter; import org.springframework.http.converter.json.mappingjackson2httmessageConverter; import; org.springframework.http.converter.smile.mappingjackson2smileHttpMessageConverter; import org.springframework.http.converter.support.allencompassingFormhttpMessageConverter; Importer; org.springframework.http.converter.xml.jaxb2rootementhttpMessageConverter; import org.springframework.http.converter.xml.mappingjackson2xmlhttpmessageconverter; import; org.springframework.http.converter.xml.sourcehttpMessageConverter; import org.springframework.sterereotype. java.util.list; @ComponentPublic class restTemplateConfig {private static final booléen romepresent = classutils.ispresent ("com.rometools.rome.feed.wirefeed", restTemlate .class.getClassloader ()); Boolean final statique privé jaxb2present = classutils.ispresent ("javax.xml.bind.binder", restTEemplate.class.getClassLoader ()); Booléen final privé Jackson2Present = classutils.ispresent ("com.fasterxml.jackson.databind.objectmapper", resttemplate.class.getclassloader ()) && classutils.ispresent ("com.fasterxml.jackson.core.jsongenerator", resttemplate. Booléen final statique privé jackson2xmlpresent = classutils.ispresent ("com.fasterxml.jackson.dataformat.xml.xmlmapper", resttemplate.class.getclassloader ()); Booléen final statique privé jackson2smilepresent = classutils.ispresent ("com.fasterxml.jackson.dataformat.smile.smilefactory", resttemplate.class.getClassloader ()); Booléen final statique privé jackson2cborpresent = classutils.ispresent ("com.fasterxml.jackson.dataformat.cbor.cborfactory", resttemplate.class.getClassloader ()); Boolean final privé statique gsonpresent = classutils.ispresent ("com.google.gson.gson", restTEemplate.class.getClassLoader ()); Private Static Final Boolean JSONBPresent = classutils.ispresent ("javax.json.bind.jsonb", restTEmplate.class.getClassLoader ()); // Remarque lorsque nous démarrons, puisque nous injectons RestTemplate dans le service, nous devons instancier une instance de cette classe lors du démarrage de Builder privé RestTemplateBuilder @Autowired; @Autowired Private ObjectMapper ObjectMapper; // Utilisez RestTemplateBuilder pour instancier l'objet RestTemplate. Spring a injecté l'instance RestTemplateBuilder par défaut @Bean public restTemplate restTemplate () {restTemplate restTemplate = builder.build (); List <httpMessageConverter <? >> MessageConverters = lists.newArrayList (); Mappingjackson2httpMessageConverter Converter = new MappingJackson2HttpMessageConverter (); converter.setObjectMapper (objectMapper); // Exception apparaîtra sans ajouter // ne peut pas extraire la réponse: Aucune HTTPMessageConverter appropriée trouvée pour le type de réponse [class] mediaType [] mediaType = new MediaType [] {mediatype.application_json, mediaType.Application_octet_Tream, Mediatype.Application_Json_Utf8, Mediatype.Text_HtMl, Mediatype.text_plain, mediatype.text_xml, mediatype.application_stream_json, mediatype.application_atom_xml, mediatype.application_form_urlencoded, mediatype.application_pdf,}; converter.SetSupportedMediaTypes (arrays.aslist (MediaTypes)); //MessageConverters.add(Converter); if (jackson2present) {MessageConverters.add (convertisseur); } else if (gsonpresent) {MessageConverters.add (new gsonhttpMessageConverter ()); } else if (JSONBPresent) {MessageConverters.add (new JSONBHTTPMessageConverter ()); } MessageConverters.add (new FormHttpMessageConverter ()); MessageConverters.add (new StringHttpMessageConverter ()); MessageConverters.add (new StringHttpMessageConverter ()); MessageConverters.Add (New ResourceHttpMessageConverter (false)); MessageConverters.add (new SourceHttpMessageConverter ()); MessageConverters.Add (new ALENCOPASSIGFORMHTTPMESSAGECONverter ()); if (RomePresent) {MessageConverters.add (new atomFeedHttpMessageConverter ()); MessageConverters.add (nouveau RSSChannelHttpMessageConverter ()); } if (jackson2xmlpresent) {MessageConverters.add (new MappingJackson2xmlHttpMessageConverter ()); } else if (jaxb2present) {MessageConverters.add (new JaxB2RoOteElementHttpMessageConverter ()); } if (jackson2smilepresent) {MessageConverters.add (new MappingJackson2SmileHttpMessageConverter ()); } if (jackson2cborpresent) {MessageConverters.add (nouveau mappingjackson2cborHttpMessageConverter ()); } restTemplate.SetMessageConverters (MessageConverters); return restTemplate; }}Après avoir vu le code ci-dessus et comparé l'implémentation interne de RestTemplate, vous saurez que j'ai fait référence au code source de RestTemplate. Les gens obsédés par la propreté peuvent dire que ce morceau de code est un peu verbeux. Le groupe ci-dessus des variables finales statiques et des méthodes de données de remplissage de MessageConverters exposent la mise en œuvre de Rest -mplate. Si RestTemplate est modifiée, elle sera également modifiée ici, ce qui est très hostile et ne ressemblera pas du tout à OO.
Après analyse, RestTemplateBuilder.Build () construit l'objet RestTemplate. Modifiez simplement le MediaType pris en charge avec le MappingJackSon2HttpMessageConverter interne. Bien que le champ MessageConverters de RestTemplate soit final privé, nous pouvons toujours le modifier par la réflexion. Le code amélioré est le suivant:
Resttemplateconfig
package com.power.demo.restclient.config; import com.fasterxml.jackson.databind.objectmapper; import com.google.common.collect.lists; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.wotation.client.restterbleder; org.springframework.context.annotation.bean; import org.springframework.http.mediaType; import org.springframework.http.converter.httpmessageconverter; import org.springframework.http.converter.json.mappingjackson2httpmesgageconverter; org.springframework.sterreotype.component; import org.springframework.web.client.resttemplate; importer java.lang.reflect.field; import java.util.arrays; import java.util.list; import java.util.optional; import java.util.stream.Collectors; @componentpublic Class; RestTemplateConfig {// Remarque Lorsque nous commençons cela parce que nous injectons RestTemplate dans le service, nous devons instancier une instance de la classe lors du démarrage de Builder privé RestTemplateBuilder @Autowired; @Autowired Private ObjectMapper ObjectMapper; // Utilisez RestTemplateBuilder pour instancier l'objet RestTemplate. Spring a injecté l'instance RestTemplate par défaut @bean public restTemplate restTemplate () {restTEmplate restTemplate = builder.build (); List <httpMessageConverter <? >> MessageConverters = lists.newArrayList (); Mappingjackson2httpMessageConverter Converter = new MappingJackson2HttpMessageConverter (); converter.setObjectMapper (objectMapper); // Exception peut se produire si aucun ajout // ne peut extraire la réponse: Aucun HttpMessageConverter approprié trouvé pour le type de réponse [class] mediaType [] mediaType = new MediaType [] {mediatype.application_json, mediaType.Application_octet_Stream, mediaText_xml, Mediatype.Text_Thex Mediatype.application_stream_json, mediatype.application_atom_xml, mediatype.application_form_urlencoded, mediatype.application_json_utf8, mediatype.application_pdf,}; converter.SetSupportedMediaTypes (arrays.aslist (MediaTypes)); essayez {// définir MessageConverters via le champ de réflexion Field = restTemplate.getClass (). GetDeclaredField ("MessageConverters"); field.setAccessible (true); List <httpMessageConverter <? >> orgConverterList = (list <httpMessageConverter <? >>) field.get (restLEmplate); Facultatif <httpMessageConverter <? >> OpConverter = orgConverterList.Stream () .Filter (x -> x.getClass (). GetName (). EqualSignoreCase (MappingJackSon2HttpMessageConverter.Class .getName ())). if (opconverter.ispresent () == false) {return restTemplate; } MessageConverters.Add (Converter); // Ajouter MAPPINGJACKSON2HTTPMESSAGECONverter // Ajouter la liste d'origine HttpMessageConverter <httpMessageConverter <? >> LeftConverters = OrgConverterList.Stream () .filter (x -> LeftConverters = x.getClass (). getName (). equalSignoreCase (mappingjackson2httpMessageConverter.class .getName ()) == false) .collect (collectionners.tolist ()); MessageConverters.Addall (LeftConverters); System.out.println (String.Format ("【HttpMessageConverter】 Quantité d'origine:% S, quantité reconstruite:% s", orgConverterList.size (), MessageConverters.size ())); } catch (exception e) {e.printStackTrace (); } restTemplate.SetMessageConverters (MessageConverters); return restTemplate; }}Outre un champ MessageConverters, il semble que nous ne nous soucions plus des packages de dépendance externes et des processus de construction internes de RestTemplate. Comme prévu, il est très propre et concis et facile à entretenir.
3. Problème de code des ordures
C'est aussi une question très classique. La solution est très simple, trouvez httpmessageConverter et jetez un œil au charset pris en charge par défaut. AbstractJackson2HttpMessageConverter est une classe de base pour de nombreux HttpMessageConverter, et le codage par défaut est UTF-8:
AbstractJackson2httpMessageConverter
classe abstraite publique AbstractJackson2httpMessageConverter étend AbstractGenerIchTTPMessageConverter <Bobile> {public static final charset default_charset = standardCharsets.utf_8;}StringHttpMessageConverter est assez spécial. Certaines personnes ont signalé que le problème brouillé est causé par le codage ISO-8859-1 qu'il prend en charge par défaut:
StringHttpMessageConverter
/ ** * Implémentation de {@Link httpMessageConverter} qui peut lire et écrire des chaînes. * * <p> Par défaut, ce convertisseur prend en charge tous les types de supports ({@code}), * et écrit avec un {@code contenu-type} de {@code text / plain}. Cela peut être remplacé * en définissant la propriété {@link #setsupportedmediaTypes SupportEDEDIATYPES}. * * @Author Arjen Poutsma * @author Juergen Hoeller * @Since 3.0 * / Classe publique StringHttpMessageConverter étend AbstractThTTPMessageConverter <string> {public static final charset default_charset = standardCharsets.iso_8859_1; / ** * Un constructeur par défaut qui utilise {@code "ISO-8859-1"} comme Charset par défaut. * @see #StringHttpMessageConverter (charset) * / public StringHttpMessageConverter () {this (default_charset); }}Si le code brouillé se produit pendant l'utilisation, nous pouvons définir le codage pris en charge par HTTPMessageConverter via des méthodes, celles couramment utilisées incluent UTF-8, GBK, etc.
4. Exception de désérialisation
C'est un autre problème facile à rencontrer pendant le processus de développement. Parce qu'il y a tellement de frameworks et d'outils open source en Java et que la version change fréquemment, les pièges inattendus se produisent souvent.
Prenez le temps de Joda comme exemple. Joda Time est un cadre de temps et de date Java populaire, mais si votre interface expose le type de temps de Joda, tel que DateTime, alors l'appelant d'interface (systèmes isomorphes et hétérogènes) peut rencontrer des problèmes de sérialisation, et même lancer les exceptions suivantes directement pendant la désérialisation:
org.springframework.http.converter.httpMessageConversionException: Erreur de définition de type: [type simple, classe org.joda.time.chronologie]; L'exception imbriquée est com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Impossible de construire une instance de `org.joda.time.chronology` (aucun créateur, comme la construction par défaut, existe): les types abstraits doivent soit être mappés à des types concrets, avoir le désérialiseur personnalisé, ou contient des informations supplémentaires de type d'informations supplémentaires
à [Source: (PushBackInputStream);
J'ai rencontré cela dans l'usine précédente, et plus tard, pour la commodité de l'appel, je suis revenu au type de date qui exposait directement Java.
Bien sûr, il y a plus que cette solution. Vous pouvez utiliser Jackson pour soutenir la sérialisation et la désérialisation des classes personnalisées. Dans les systèmes avec des exigences de précision non très élevées, implémentez la sérialisation personnalisée simple DateTime:
DateTimeSerializer
package com.power.demo.util; import com.fasterxml.jackson.core.jsongenerator; import com.fasterxml.jackson.core.jsonprocessingException; import com.fasterxml.jackson.databind.jsesirializer; org.joda.time.datetime; import org.joda.time.format.datetimeFormat; import org.joda.time.format.datetimeFormatter; import java.io.ioexception; / ** * par défaut, Jackson sérialisera le temps joda dans une forme plus complexe, qui n'est pas conduisée à la lecture et a un objet Joda dans un objectif plus grand. * <p> * Lors de la sérialisation jodatime, DateTime peut être sérialisé en une chaîne, ce qui est plus facile à lire ** / classe publique DatetimeSerializer étend JSonSerializer <DateTime> {private static DateTimeFormatter DateFormatter = DateTimeFormat.Forpattern ("yyyyy-mm-dd hh: mm: ss"); @Override public void serialize (DateTime Value, JSongenerator JGen, SerializerProvider Provider) lève IOException, JSONProcessingException {jgen.WriteString (valeur.toString (dateFormatter)); }} Et désérialisation de DateTime:
Datetimedeserializer
package com.power.demo.util; import com.fasterxml.jackson.core.jsonparser; import com.fasterxml.jackson.core.jsonprocessingException; import com.fasterxml.jackson.databind.deserializationcontext; importation com.fasterxml.jackson.dateabind.jSondeRizer; com.fasterxml.jackson.databind.jsonNode; import org.joda.time.datetime; import org.joda.time.format.datetimeformat; import org.joda.time.format.datetimeformatter; DateTimeDeSerializer étend JSonSeSerializer <DateTime> {private static DateTimeFormatter DateFormatter = DateTimeFormat.forPattern ("Yyyy-mm-dd HH: mm: ss"); @Override public DateTime Desérialize (JSONParser JP, DeserializationContext Context) lève IoException, JSONProcessingException {JSONNode Node = jp.getCodec (). ReadTree (JP); String s = node.astext (); DateTime parse = datetime.parse (s, dateFormatter); Parse de retour; }}最后可以在RestTemplateConfig类中对常见调用问题进行汇总处理,可以参考如下:
RestTemplateConfig
package com.power.demo.restclient.config;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.module.SimpleModule;import com.google.common.collect.Lists;import com.power.demo.util.DateTimeSerializer;import com.power.demo.util.DatetimeDeserializer;import org.joda.time.DateTime;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.web.client.RestTemplateBuilder;import org.springframework.context.annotation.Bean;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;import org.springframework.stereotype.Component;import org.springframework.web.client.RestTemplate;import java.lang.reflect.Field;import java.util.Arrays;import java.util.List;import java.util.Optional;import java.util.stream.Collectors;@Componentpublic class RestTemplateConfig { // 启动的时候要注意,由于我们在服务中注入了RestTemplate,所以启动的时候需要实例化该类的一个实例@Autowired private RestTemplateBuilder builder; @Autowired private ObjectMapper objectMapper; // 使用RestTemplateBuilder来实例化RestTemplate对象,spring默认已经注入了RestTemplateBuilder实例@Bean public RestTemplate restTemplate() { RestTemplate restTemplate = builder.build(); //注册model,用于实现jackson joda time序列化和反序列化SimpleModule module = new SimpleModule(); module.addSerializer(DateTime.class, new DateTimeSerializer()); module.addDeserializer(DateTime.class, new DatetimeDeserializer()); objectMapper.registerModule(module); List<HttpMessageConverter<?>> messageConverters = Lists.newArrayList(); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setObjectMapper(objectMapper); //不加会出现异常//Could not extract response: no suitable HttpMessageConverter found for response type [class ] MediaType[] mediaTypes = new MediaType[]{ MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM, MediaType.TEXT_HTML, MediaType.TEXT_PLAIN, MediaType.TEXT_XML, MediaType.APPLICATION_STREAM_JSON, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON_UTF8, MediaType.APPLICATION_PDF, }; converter.setSupportedMediaTypes(Arrays.asList(mediaTypes)); try { //通过反射设置MessageConverters Field field = restTemplate.getClass().getDeclaredField("messageConverters"); field.setAccessible(true); List<HttpMessageConverter<?>> orgConverterList = (List<HttpMessageConverter<?>>) field.get(restTemplate); Optional<HttpMessageConverter<?>> opConverter = orgConverterList.stream() .filter(x -> x.getClass().getName().equalsIgnoreCase(MappingJackson2HttpMessageConverter.class .getName())) .findFirst(); if (opConverter.isPresent() == false) { return restTemplate; } messageConverters.add(converter);//添加MappingJackson2HttpMessageConverter //添加原有的剩余的HttpMessageConverter List<HttpMessageConverter<?>> leftConverters = orgConverterList.stream() .filter(x -> x.getClass().getName().equalsIgnoreCase(MappingJackson2HttpMessageConverter.class .getName()) == false) .collect(Collectors.toList()); messageConverters.addAll(leftConverters); System.out.println(String.format("【HttpMessageConverter】原有数量:%s,重新构造后数量:%s" , orgConverterList.size(), messageConverters.size())); } catch (Exception e) { e.printStackTrace(); } restTemplate.setMessageConverters(messageConverters); return restTemplate; }}目前良好地解决了RestTemplate常用调用问题,而且不需要你写RestTemplate帮助工具类了。
上面列举的这些常见问题,其实.NET下面也有,有兴趣大家可以搜索一下微软的HttpClient常见使用问题,用过的人都深有体会。更不用提RestSharp 这个开源类库,几年前用的过程中发现了非常多的Bug,到现在还有一个反序列化数组的问题困扰着我们,我只好自己造个简单轮子特殊处理,给我最深刻的经验就是,很多看上去简单的功能,真的碰到了依然会花掉不少的时间去排查和解决,甚至要翻看源码。所以,我们写代码要认识到,越是通用的工具,越需要考虑到特例,可能你需要花80%以上的精力去处理20%的特殊情况,这估计也是满足常见的二八定律吧。
参考:
https://stackoverflow.com/questions/21854369/no-suitable-httpmessageconverter-found-for-response-type
https://stackoverflow.com/questions/40726145/rest-templatecould-not-extract-response-no-suitable-httpmessageconverter-found
https://stackoverflow.com/questions/10579122/resttemplate-no-suitable-httpmessageconverter
http://forum.spring.io/forum/spring-projects/android/126794-no-suitable-httpmessageconverter-found
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持武林网。