Wir können die REST -Schnittstelle schnell über Spring Boot entwickeln und müssen möglicherweise auch die interne und externe REST -Schnittstelle über Spring Boot aufrufen, um die Geschäftslogik während der Implementierung der Schnittstelle zu vervollständigen.
Im Spring Boot gibt es zwei gemeinsame Möglichkeiten, REST-APIs aufzurufen, mit denen Serviceanrufe über das integrierte RastTemplate implementiert oder Ihr eigenes HTTP-Client-Tool entwickelt werden.
RestTemplate hat sehr leistungsstarke grundlegende Funktionen, aber in einigen besonderen Szenarien sind wir möglicherweise eher daran gewöhnt, unsere eigenen eingekapselten Toolklassen zu verwenden, z.
In diesem Artikel wird RastTemplate als Beispiel verwendet, um mehrere Probleme und Lösungen aufzuzeichnen, die während der Verwendung von RestTemplate zum Aufrufen der Schnittstelle gefunden wurden.
1. Einführung in RestTemplate
1. Was ist RestTemplate
Der httpclient, den wir selbst zusammenfassen, verfügt normalerweise über einen Vorlagencode, z. B. die Erstellung einer Verbindung, das Erstellen eines Anforderungsheaders und eine Anforderungsbehörde, dann die Antwortinformationen anhand der Antwort analysieren und schließlich die Verbindung schließen.
RESTTEMPLATE ist eine Neukapselung von HTTPClient in Spring, die den Prozess der Initiierung von HTTP-Anforderungen und -verarbeitungsantworten mit höheren Abstraktionsniveaus, Reduzierung der Verbrauchervorlagencode und der Erstellung eines weniger redundanten Codes vereinfacht.
Wenn Sie über viele XXXTemplate -Klassen unter Spring Boot nachdenken, bieten sie auch verschiedene Vorlagenmethoden an, die Abstraktionsebene ist jedoch höher und weitere Details sind verborgen.
Übrigens verfügt Spring Cloud über einen deklarativen Service -Call -Aufruf vor, der basierend auf Netflix Feign implementiert wird, Spring Cloud Ribbon und Spring Cloud Hystrix integriert und eine deklarative Webdienst -Client -Definitionsmethode implementiert.
Im Wesentlichen soll vorgegeben, es auf der Grundlage von RestTemplate erneut zu verkapulieren, was uns hilft, die Definition abhängiger Service -Schnittstellen zu definieren und zu implementieren.
2. Häufige Methoden für RestTemplate
Es gibt viele Anforderungsmethoden für gemeinsame REST -Dienste, z. B. Get, Post, Put, Löschen, Kopf, Optionen usw. Implementiert die häufigste Methode, und die am häufigsten verwendeten sind Get and Post. Sie können sich beim Aufrufen der API auf den Quellcode beziehen. Hier sind einige Methodendefinitionen (get, post, löschen):
Methoden
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType,Object... uriVariables)public <T> ResponseEntity<T> postForEntity(String URL, @Nullable -Objektanforderung, Klasse <T> Antworttyp, Objekt ... Urivariablen) öffentliche Leere löschen (String -URL, Objekt ... Urivariablen) Öffentliche Leere löschen (URI -URL)
Gleichzeitig sollten Sie zwei weitere "flexible" Methoden austauschen und ausführen.
Der von RestTemplate ausgesetztes Austausch unterscheidet sich von anderen Schnittstellen:
(1) Erlauben Sie dem Anrufer, die Methode der HTTP -Anforderung anzugeben (Get, Post, Löschen usw.)
(2) Körper- und Headerinformationen können zur Anforderung hinzugefügt werden, und sein Inhalt wird durch den Parameter 'Httpentity <?> RequireEntity' beschrieben
(3) Exchange unterstützt 'Typ mit Parametern "(d. H. Generische Klasse) als Rückkehrtyp, und diese Funktion wird durch' parameterizedtypeerference <T> reagestyps 'beschrieben.
RestTemplate Alle Get-, Post- und anderen Methoden, und der letzte Anruf der Ausführungsmethode ist die Ausführungsmethode. Die interne Implementierung der Excut-Methode besteht darin, die String-Format-URI in java.net.uri zu konvertieren, und dann wird die Doexecute-Methode aufgerufen. Die Implementierung der Doexecute -Methode lautet wie folgt:
doexecute
/*** Führen Sie die angegebene Methode auf dem bereitgestellten URI aus. * <p> Die {@link clienthttpRequest} wird mit dem {@link requestCallback} verarbeitet; * Die Antwort mit der {@link responsextractor}. * @Param URL Die voll ausgestattete URL, um eine Verbindung zu * @param-Methode herzustellen, die HTTP-Methode zum Ausführen (GET, Post usw.) * @param RequestCallback-Objekt, das die Anforderung vorbereitet (kann {@Code null}) * @param responsextractor-Objekt extrahiert, das den Rückgaberwert extrahiert. {@link responsextractor} */ @nulleable protected <t> t doExecute (URI -URL, @Nulleable httpMethod -Methode, @Nullable RequestCallback RequestCallback, @Nullable responsextractor <T> responsextractor) veröffentlichen restclientException {assert.notnOll (Url, "url '. Assert.notnull (Methode, "Methode" darf nicht null sein "); ClienthttPesponse response = null; try {clienthttpRequest request = createRequest (URL, Methode); if (RequestCallback! } response = request.execute (); Handlesponse (URL, Methode, Antwort); if (responsextractor! } else {return null; }} catch (ioException ex) {String ressource = url.toString (); String query = url.getRawQuery (); Resource = (Abfrage! Neue ResourceAccessException werfen ("E/A -Fehler auf" + method.name () + "Anforderung für/" " + Ressource +"/":" + ex.getMessage (), ex); } endlich {if (response! = null) {response.close (); }}}Die Doexecute -Methode umfasst Template -Methoden wie das Erstellen von Verbindungen, Verarbeitungsanforderungen und Antworten, Schließen von Verbindungen usw.
Wenn die meisten Leute das sehen, denken sie wahrscheinlich, dass die Einkapselung eines Restclient genau so ist, oder?
3. Einfacher Anruf
Nehmen Sie einen Postanruf als Beispiel:
WarenserviceClient
paket com.power.demo.restclient; import com.power.demo.common.appconst; import com.power org.springframework.beans.factory.annotation.autowired; import org.springframework.bean.factory.Annotation **/ @componentPublic Class WarenserviceClient {// Die vom Service Consumer genannte Schnittstellen -URL lautet wie folgt: http: // localhost: 9090 @Value ("$ {Spring.Power.Serviceurl}") private String _Serviceurl; @Autowired Private rastTemplate rastTemplate; public ClientGetGoodsByGoodSIDResponse getGoodsByGoodsid (ClientgetGoodsByGoodSIdRequest -Anfrage) {String svcurl = getGoodSVCurl () + "/getInfobyid"; ClientgetGoodsByGoodSidResponse -Antwort = null; try {response = rastTemplate.postforObject (SVCURL, Anfrage, ClientgetGoodsByGoodSIdResponse.class); } catch (Ausnahme e) {e.printstacktrace (); response = new ClientgetGoodsByGoodSidResponse (); response.setCode (AppConst.fail); Antwort.SetMessage (e.toString ()); } Rückgabeantwort; } private String 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/Waren", _ServiceUrl); } else {url = string.format ("%s/api/v1/good", _serviceUrl); } return URL; }}In der Demo wird die Methode rastTemplate.postforObject direkt aufgerufen und die Deserialisierungsentität wird in diese interne Einkapselung von RestTemplate konvertiert.
2. Zusammenfassung der Probleme
1. Kein geeignetes httpMessageConverter für die Anforderungstyp -Ausnahme gefunden
Dieses Problem tritt normalerweise auf, wenn ein eingehendes Objekt für Anrufe in postForObject übergeben wird.
Analysieren Sie den Quellcode von RestTemplate. In der Dowithrequest -Methode der httpentityRequestCallback -Klasse erfüllt die MessageConverters (dieses Feld wird weiterhin später erwähnt) nicht der Logik, die einsprungen (dh es gibt keine übereinstimmende httpMessageConverter), die obige Ausnahme: Die obige Ausnahme wird ausgelöst:
HttpentityRequestCallback.DowitHrequest
@Override @SuppressWarnings ("Deaktiviert") public void dowithrequest (clientHttpRequest httprequest) löst ioException {super.dowitHequest (httprequest) aus; Object RequestBody = this.requestentity.getBody (); if (requestBody == null) {httpheaders httpheaders = httprequest.getheaders (); Httpheaders requestHeaders = this.requestentity.Getheaders (); if (! requestheaders.isempty ()) {for (map.entry <String, Liste <String >> Eintrag: RequestheaNers.EntrySet ()) {httpheaders.put (Eintrag.GeteKey (), New LinkedList <> (Eintrag.getValue ()); }} if (httpheaders.getContentLength () <0) {httpheaders.setContentLength (0L); }} else {class <?> RequestBodyClass = RequestBody.getClass (); Type RequestBodyType = (this.requestentity InstanceOf Anreize? ((RequestEntity <?>) This.requestentity) .getType (): RequestBodyClass); Httpheaders httpheaders = httprequest.Getheaders (); Httpheaders requestHeaders = this.requestentity.Getheaders (); MediateType requestContentType = requestheaders.getContentType (); für (httpMessageConverter <?> MessageConverter: getMessageConverters ()) {if (MessageConverter -Instanz von generighttpMessageConverter) {genannt if (genericConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) { if (!requestHeaders.isEmpty()) { for (Map.Entry<String, List<String>> entry : requestHeaders.entrySet()) { httpHeaders.put(entry.getKey(), new LinkedList<>(entry.getValue())); }} if (logger.isdebugenabled ()) {if (requestContentType! MessageConverter + "]"); : Requestheaders.EntrySet ()) {httpheaders.put (Entry.Getkey (), New LinkedList <> (Eintrag.getValue ()); [" + messageConverter +"] "); } else {logger.debug ("schreiben [" + requestBody + "]"); }}. zurückkehren; }} String message = "konnte keine Anforderung schreiben: Kein geeignetes httpMessageConverter für Anforderungstyp [" + RequestBodyClass.getName () + "]"; if (requestContentType! = null) {message + = "und content type [" + requestContentType + "]"; } neue rastClientException (Nachricht) werfen; }} Die einfachste Lösung besteht darin, den HTTP -Anforderungsheader zu wickeln und das Anforderungsobjekt in eine Zeichenfolge zu serialisieren, um die Parameter zu übergeben. Der Referenz -Beispielcode lautet wie folgt:
postForObject
/ * * Postanforderung Anruf * * */ public static String postForObject (restTemplate restTemplate, String -URL, Objektparams) {httpheaders headers = new httpheaders (); MediateType type = minyType.ParsemediType ("application/json; charset = utf-8"); Header.SetContentType (Typ); Headers.Add ("Accept", Mediitype.Application_json.toString ()); String JSON = SerializeUtil.Serialize (Params); Httpentity <string> formentity = new httpentity <string> (JSON, Header); String result = rastTemplate.postforObject (URL, Förderität, String.Class); Rückgabeergebnis; }Wenn wir das Objekt auch direkt zurückgeben möchten, können wir die zurückgegebene Zeichenfolge direkt deserialisieren:
postForObject
/ * * Postanforderung Anruf * * */ public static <T> t postForObject (restTemplate rastTemplate, String -URL, Objektparams, Klasse <T> clazz) {t response = null; String repstr = postforObject (restTemplate, url, params); response = serializeUtil.deserialize (repstr, clazz); Rücksendemantwort; }Unter ihnen gibt es viele Werkzeuge für Serialisierung und Deserialisierung, wie Fastjson, Jackson und GSON.
2. Kein geeignetes httpMessageConverter für die Ausnahme des Antworttyps gefunden
Genau wie eine Ausnahme bei der Einleitung einer Anfrage auftritt, wird es auch beim Umgang mit der Antwort Probleme geben.
Jemand stellte die gleiche Frage zum Stackoverflow. Die Grundursache ist, dass der HTTP -Nachrichtenkonverter httpMessageConverter fehlt den MIME -Typ. Das heißt, wenn HTTP das Ausgabeergebnis an den Client überträgt, muss der Client die entsprechende Anwendung starten, um das Ausgabedokument zu verarbeiten. Dies kann über mehrere MIME -Typen (Multifunktionaler Internet -Mail -Augmentation -Protokoll) erfolgen.
Für serverseitige Antworten unterstützt viele httpMessageConverter standardmäßig verschiedene Medientypen (Mimetypen). Die Standardunterstützung für StringHttpMessageConverter lautet in minytype.text_plain, die Standardunterstützung für SourceHttpMessArverter ist in mydeType.text_xml, die Standardunterstützung für formHttpMessageConverter is mediType.aplication_form_form_form_urlencoded und mediattype.multipart_data. Im REST -Service, das wir am meisten verwenden, ist Mappingjackson2HttpMessageConverter. Dies ist ein relativ allgemeiner Konverter (von der GeneralttpMessageConverter -Schnittstelle geerbt). Laut Analyse ist der Mimetyp, den es standardmäßig unterstützt, in minyType.application_json:
Mapingjackson2httpMessageConverter
/*** Konstruieren Sie ein neues {@link mapingjackson2HttpMessageConverter} mit einem benutzerdefinierten {@link ObjectMapper}. * Sie können {@link jackson2ObjectMapperBuilder} verwenden, um es leicht zu erstellen. *@see Jackson2ObjectMapperBuilder#json () */ public mapingjackson2HttpMessageConverter (ObjectMapper ObjectMapper) {Super (ObjectMapper, mediatype.application_json, New MediaType ("Anwendung", *+Json "); }Die Standardantwort einiger Anwendungsschnittstellen ist jedoch keine Anwendung/JSON. Wenn wir beispielsweise eine externe Wettervorhersage -Schnittstelle für die Wettervorhersage aufrufen, ist es kein Problem, eine String -Antwort direkt zurückzugeben:
String url = "http://wthrcdn.etouch.cn/weather_Mini?city=shanghai";string result = rastTemplate.getforobject (url, String.class); ClientWeatherResultvo vo = serializeUtil.deserialize (Ergebnis, ClientweatherResultvo.classe);
Wenn wir jedoch ein Entitätsobjekt direkt zurückgeben möchten:
String url = "http://wthrcdn.etouch.cn/weather_Mini?city=shanghai";clientWeatherResultvo weatherresultvo = rastTemplate.getforObject (URL, ClientWeatResultvo.class);
Melden Sie dann eine Ausnahme direkt:
Es konnte keine Antwort extrahieren: Kein geeignetes httpMessageConverter für den Antworttyp [Klasse]
und Inhaltstyp [Anwendung/Oktettstrom]
Viele Menschen sind auf dieses Problem gestoßen. Die meisten von ihnen sind verwirrt, wenn sie es zum ersten Mal begegnen. Viele Schnittstellen werden in JSON, XML oder einfachem Textformat zurückgegeben. Was ist Anwendung/Oktettstrom?
Überprüfen Sie den Quellcode von RestTemplate und behalten Sie den Überblick, dass die ExtractData -Methode der httpMessageConverterExtractor -Klasse eine Parsing -Antwort und Deserialisierungslogik hat. Wenn es nicht erfolgreich ist, sind die geworfenen Ausnahmeinformationen übereinstimmend wie oben:
HttpMessageConverterExtractor.extractData
@Override @SuppressWarnings ({"Unbekämpfte", "rawtttpes", "ressource"}) public t extractData (ClientHttPesponse -Antwort) löst IOException {messageBodyClientHtttttttttPesponse -ResponseWrapper = new MessageBodyClientHttPesponseWressWrapper (Antwort) aus; if (! responseWrapper.hasmessageBody () || responseWrapper.hasemptymessageBody ()) {return null; } MinyType contentType = getContentType (responseWrapper); Versuchen Sie {für (httpMessageConverter <?> MessageConverter: this.messageConverters) {if (MessageConverter -Instanz von generighttpMessArverter) {generichttpMessageConverter <> genericMessageConverter = (GENDECHTTPMESSAGECONVERTERTE; if (genericMessageConverter.canread (this.responsetype, null, contentType) {if (logger.isdebugenabled ()) {logger.debug ("Reading [" + this.ResponSetype + "] als /" + contentType + " /" verwendet [" + messageConverter +". } return (t) genericMessageConverter.read (this.responsetype, null, responseWrapper); }} if (this.responseclass! MessageConverter + "]"); } return (t) messageConverter.read ((Klasse) this.responseclass, responseWrapper); }}}} catch (ioException | httpMessagenotreadableException ex) {werfen neu restClientException ("Fehler beim Extrahieren der Antwort für type [" + this.responSetype + "] und Inhaltsart [" + contentType + "]", Ex); } neue rastClientException werfen ("konnte keine Antwort extrahieren: Kein geeignetes httpMessageConverter gefunden" + "für den Antworttyp [" + this.responSetype + "] und Inhaltstyp [" + contentType + "]"); } Der Beispielcode der Lösung auf Stackoverflow ist akzeptabel, aber nicht genau. Häufige Mimetypen sollten hinzugefügt werden. Ich werde den Code veröffentlichen, den ich für richtig halte:
RastTemplateConfig
paket com.power.demo.restclient.config; import com.fasterxml.jackson.databind.objectmapper; import com.google.common.collect.lists; org.springframework.boot.web.client.resttemplateBuilder; import org.springframework.context.annotation.bean; import org.springframework.http.mediatype; org.springframework.http.converter.cbor.mappingjackson2cborhttpMessageConverter; Import org.springframework.http.converter.feed.atomfeedhtpMessageConverter; Import org.springframework.http.converter.feed.rSschannelhttpMessageConverter; import org.springframework.http.converter.json.gsonHttpMessageConverter; org.springFramework.http.converter.json.mappingjackson2HttpMessageConverter; import org.springframework.http.converter.smile.mappingjackson2smileHttpMessageConverter; Import org.springframework.http.converter.support.AllencompassingFormhttpMessageConverter; Import org.springframework.http.converter.xml.jaxb2rootelementHttpMessageConverter; org.springframework.http.converter.xml.mappingjackson2xmlhttpMessageConverter; Import org.springframework.http.converter.xml.sourcehttpMessageConverter; org.springframework Clasutils.isspresent ("com.rometools.rome.feed.wirfeed", rastTemplate .class.getClassloader ()); private statische endgültige boolean jaxb2present = classutils.isspresent ("javax.xml.bind.binder", restTemplate.class.getClassloader ()); Private Static Final Boolean Jackson2Present = classutils.isspresent ("com.fasterxml.jackson.databind.objectMapper", rastTemplate.class.getClassloader ()) && classutils.isspresent ("com.fasterxml.jackson.core.core.jsonder.jsonger (; Private statische endgültige Boolean Jackson2xMlPresent = clasutils.isspresent ("com.fasterxml.jackson.dataformat.xml.xmlmapper", rastTemplate.class.getClassloader ()); Private statische endgültige Boolean Jackson2SmilePresent = clasutils.isspresent ("com.fasterxml.jackson.dataFormat.smile.smilefactory", rastTemplate.class.getClassloader ()); private statische endgültige Boolean Jackson2cborpresent = clasutils.isspresent ("com.fasterxml.jackson.dataformat.cbor.cborfactory", rastTemplate.class.getClassloader ()); private statische endgültige boolean gsonpresent = clasutils.isspresent ("com.google.gson.gson", rastTemplate.class.getClassloader ()); private statische endgültige boolean jsonbPresent = clasutils.ispresent ("javax.json.bind.jsonb", restTemplate.class.getClassloader ()); // Beachten Sie, dass wir bei Beginn des Starts, da wir RastTemplate in den Dienst injizieren, eine Instanz dieser Klasse instanziieren müssen, wenn wir @Autowired private rastTemplateBuilder -Builder starten. @Autowired Private ObjectMapper ObjectMapper; // RastTemplateBuilder verwenden, um das RastTemplate -Objekt zu instanziieren. Spring hat die restTemplateBuilder -Instanz standardmäßig @Bean public rastTemplate rastTemplate () {restTemplate rastTemplate = builder.build () injiziert; Liste <httpMessageConverter <? >> MessageConverters = lists.NewarrayList (); Mapingjackson2httpMessageConverter Converter = new mapPingJackson2HttpMessageConverter (); Converter.SetObjectMapper (ObjectMapper); // Ausnahme wird ohne Hinzufügen // keine Antwort extrahieren: Kein geeignetes httpMessageConverter für den Antworttyp [Klasse] in mediType [] mediatypes = new Mediitype [] {midentype.application_json, mydytype.application_tet_stream, mediatype.application_json_utf8, mediattet_stream, application_json_utf8, mediattet_httm. In mentype.text_plain, in mediateType.text_xml, mediatype.application_stream_json, mediatype.application_atom_xml, mediatype.application_form_urlencoded, mediatype.application_pdf,}; Converter.SetsupportedMediTypes (Arrays.aslist (mediadypes)); //messageConverters.add(Converter); if (Jackson2Present) {MessageConverters.Add (Konverter); } else if (gsonpresent) {MessageConverters.Add (neue gsonHttpMessageConverter ()); } else if (jsonbPresent) {messageConverters.add (new JsonBHttpMessageConverter ()); } messageConverters.add (neue FormhttpMessageConverter ()); MessageConverters.add (neue StringHttpMessageConverter ()); MessageConverters.add (neue StringHttpMessageConverter ()); messageConverters.add (neue ResourcehtpMessageConverter (False)); MessageConverters.Add (neue SourceHttpMessageConverter ()); messageConverters.add (neuer AllencompassingFormhttpMessageConverter ()); if (romePresent) {MessageConverters.Add (neue AtomfeedhttpMessageConverter ()); MessageConverters.Add (neue rSchannelhttpMessageConverter ()); } if (jackson2xmlPresent) {MessageConverters.add (new mapingjackson2xmlhttpMessageConverter ()); } else if (jaxb2Present) {MessageConverters.Add (New Jaxb2RootElementHttpMessageConverter ()); } if (Jackson2SmilePresent) {MessageConverters.add (New mapingjackson2smilehttpMessageConverter ()); } if (jackson2cborpresent) {MessageConverters.add (New mapingjackson2cborhttpMessageConverter ()); } rastTemplate.setMessageConverters (MessageConverters); restTemplate zurückgeben; }}Nachdem Sie den obigen Code gesehen und die interne Implementierung von RestTemplate verglichen haben, wissen Sie, dass ich auf den Quellcode von RestTemplate verwiesen habe. Menschen, die von Sauberkeit besessen sind, können sagen, dass dieses Stück Code ein bisschen ausführlich ist. Die oben genannte Haufen statischer endgültiger Variablen und MessageConverters füllen Datenmethoden aus, die die Implementierung von RestTemplate enthüllen. Wenn RestTemplate geändert wird, wird sie auch hier geändert, was sehr unfreundlich ist und überhaupt nicht aussieht.
Nach der Analyse konstruiert rastTemplateBuilder.build () das RestTemplate -Objekt. Ändern Sie einfach den unterstützten MediaPe mit dem internen mapingjackson2HttpMessageConverter. Obwohl das Feld MessageConverters von RestTemplate privates Finale ist, können wir es trotzdem durch Reflexion ändern. Der verbesserte Code lautet wie folgt:
RastTemplateConfig
paket com.power.demo.restclient.config; import com.fasterxml.jackson.databind.objectmapper; import com.google.common.collect.lists; org.springframework.boot.web.client.resttemplateBuilder; import org.springframework.context.annotation.bean; org.springframework.http.converter.json.mappingjackson2httpMessageConverter; Import org.springframework.stereotype.comPonent; java.util.list; import Java.util.optional; import Java.util.stream.collectors; @ComponentPublic Class rastTemplateConfig {// Notiz Wenn Sie mit dem beginnen, weil wir restTemplate in den Service einteilen, müssen wir einen Instanziieren der Klasseninstitution in die Klasseninstitution einleiten, wenn private rasttemplate -bullopere bullungs starten; @Autowired Private ObjectMapper ObjectMapper; // RastTemplateBuilder verwenden, um das RastTemplate -Objekt zu instanziieren. Spring hat die restTemplate -Instanz standardmäßig injiziert @Bean public rastTemplate rastTemplate () {restTemplate rastTemplate = builder.build (); Liste <httpMessageConverter <? >> MessageConverters = lists.NewarrayList (); Mapingjackson2httpMessageConverter Converter = new mapPingJackson2HttpMessageConverter (); Converter.SetObjectMapper (ObjectMapper); // Ausnahme kann auftreten, wenn keine Zugabe nicht extrahieren konnte: Kein geeignetes httpMessageConverter für den Antworttyp [Klasse] mediatype [] mediateTypes = new mediatype [] {mentype. In mentype.text_xml, mediatype.application_stream_json, mediatype.application_atom_xml, mediateType.application_form_urlencoded, mentype.application_json_utf8, midentype.application_pdf,}; Converter.SetsupportedMediTypes (Arrays.aslist (mediadypes)); Versuchen Sie {// MessageConverters durch Reflection Field Field = restTemplate.getClass (). getDeclaredfield ("MessageConverters"); field.setAccessible (true); Liste <httpMessageConverter <? >> orgConverterList = (Liste <httpMessageConverter <? >>) field.get (rastTemplate); Optional <httpMessageConverter <? >> OpConverter = orgConverterList.stream () .Filter (x -> x.getClass (). GetName (). EqualSignoreCase (mapingjackson2httpMessageConverter.class .getname ()) .Findfirst (); if (opConverter.ispresent () == false) {return rastTemplate; } messageConverters.add(converter);//Add MappingJackson2HttpMessageConverter //Add the original remaining HttpMessageConverter List<HttpMessageConverter<?>> leftConverters = orgConverterList.stream() .filter(x -> X.getClass (). getName (). EqualSignoreCase (mapingjackson2HttpMessageConverter.Class .getName ()) == Falsch) .Collect (collectors.tolist ()); MessageConverters.addall (LeftConverters); System.out.println (String.Format ("【httpMessageConverter】 Originalmenge: %s, rekonstruierte Menge: %s", orgConverterList.size (), MessageConverters.size ()); } catch (Ausnahme e) {e.printstacktrace (); } rastTemplate.setMessageConverters (MessageConverters); restTemplate zurückgeben; }}Abgesehen von einem Feld MessageConverters scheint es, dass wir uns nicht mehr um die externen Abhängigkeitspakete und internen Konstruktionsprozesse von RestTemplate kümmern. Wie erwartet ist es viel sauber und prägnant und leicht zu warten.
3. Problem des Müllcodes
Dies ist auch eine sehr klassische Frage. Die Lösung ist sehr einfach, finden Sie httpMessageConverter und schauen Sie sich das Standard -unterstütztes Charset an. AbstractJackson2HttpMessageConverter ist eine Basisklasse für viele httpMessageConverter, und die Standardcodierung ist UTF-8:
AbstractJackson2HttpMessageConverter
public Abstract Class AbstractJackson2HttpMessageConverter erweitert AbstractGenerichttpMessageConverter <Object> {public static Final charset default_charset = StandardCharets.utf_8;}StringHttpMessageConverter ist etwas ganz Besonderes. Einige Leute haben berichtet, dass das verstümmelte Problem durch das codierende ISO-8859-1 verursacht wird, dass es standardmäßig unterstützt:
StringHttpMessageConverter
/*** Implementierung von {@link httpMessageConverter}, das Zeichenfolgen lesen und schreiben kann. * * <p> standardmäßig unterstützt dieser Konverter alle Medientypen ({@code}), * und schreibt mit einem {@Code-Inhaltstyp} von {@code text/plain}. Dies kann überschrieben werden *, indem die Eigenschaft {@link #setsupportedMediTypes unterstützt wird. * * @Author Arjen poutsma * @Author Juergen Hoeller * @Since 3.0 */public class StringHttpMessageConverter erweitert AbstracthttpMessArverter <string> {public static Final charset default_charset = Standardcharets.iso_8859_1; /*** Ein Standardkonstruktor, der {@code "ISO-8859-1"} als Standard-Zeichensatz verwendet. * @see #stringhttpMessageConverter (charSet) */ public stringHttpMessageConverter () {this (default_charset); }}Wenn während der Verwendung verstümmelter Code auftritt, können wir die von httpMessageConverter unterstützte Codierung durch Methoden festlegen. Zu den häufig verwendeten gehören UTF-8, GBK usw.
4. Ausnahme der Deserialisierung
Dies ist ein weiteres Problem, das während des Entwicklungsprozesses leicht zu begegnen ist. Da es in Java so viele Open -Source -Frameworks und Tools gibt und die Version häufig ändert, treten häufig unerwartete Fallstricke auf.
Nehmen Sie Joda Zeit als Beispiel. Joda Time ist ein beliebtes Java -Zeit- und -datum -Framework. Wenn Ihre Schnittstelle jedoch die Art der Joda -Zeit aufdeckt, wie z. B. DateTime, kann der Schnittstellenrufer (isomorphe und heterogene Systeme) auf Serialisierungsproblemen stoßen und sogar während der Deserialisierung die folgenden Aussagen machen: sogar die folgenden Aussagen.
org.springframework.http.converter.httpMessageConversionException: Typ Definition Fehler: [Einfacher Typ, Klasse org.joda.time.chronology]; Die verschachtelte Ausnahme ist com.fasterxml.jackson.databind.exc.invaliddefinitionexception: kann die Instanz von `org.joda.time.chronology nicht konstruieren
bei [Quelle: (RushbackInputStream);
Ich habe dies in der vorherigen Fabrik begegnet, und später, um die Anforderung zu bequem zu machen, wechselte ich auf den Datumstyp zurück, der Java direkt enthüllte.
Natürlich gibt es mehr als diese Lösung. Sie können Jackson verwenden, um die Serialisierung und Deserialisierung von benutzerdefinierten Klassen zu unterstützen. Implementieren Sie in Systemen mit nicht sehr hohen Genauigkeitsanforderungen die einfache datensätze benutzerdefinierte Serialisierung:
DateTimeSerializer
paket com.power.demo.util; import com.fasterxml.jackson.core.jsongenerator; import com.fasterxml.jackson.core.jsonprocessingace; org.joda.time.datetime; import org.joda.time.format.datetimeFormat; import org.joda.time * <p>* Wenn das Jodatime serialisiert wird, kann datetime in eine Zeichenfolge serialisiert werden, die einfacher zu lesen ist @Override public void serialize (DateTime Value, Jsongenerator JGen, SerializerProvider -Anbieter) löst IOException, JsonprocessingException {jgen.writestring (value.toString (DateFormatter)) aus; }} Und DateTime -Deserialisierung:
DateTimedeSerializer
paket com.power.demo.util; import com.fasterxml.jackson.core.jsonparser; import com.fasterxml.jackson.core.jsonprocessingace; com.fasterxml.jackson.databind.jsonnode; import org.joda.time.datetime; import org.joda.time DateTimedeSerializer erweitert JSondesserializer <Datenetime> {private statische dateTimeFormatter DateFormatter = DateTimeFormat.forpattern ("yyyy-mm-dd hh: mm: ss"); @Override public datetime Deserialize (JsonParser JP, DeserializationContext -Kontext) löst IOException, JsonprocessingException {jsonnode node = jp.getCodec (). ReadTree (jp) aus; String s = node.astext (); DateTime Parse = DateTime.Parse (s, DateFormatter); analysieren; }}Schließlich können Sie allgemeine Anrufprobleme in der RESTTEMPLATECONFIG -Klasse zusammenfassen und wie folgt darauf verweisen:
RastTemplateConfig
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 { // Be careful when starting that because we inject RestTemplate into the service, we need to instantiate an instance of this class when starting @Autowired private RestTemplateBuilder builder; @Autowired Private ObjectMapper ObjectMapper; // Use RestTemplateBuilder to instantiate the RestTemplate object. Spring has injected the RestTemplateBuilder instance by default @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = builder.build(); //Register model, used to implement jackson joda time serialization and deserialization 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); //Exception will appear without adding//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 { //Set 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);//Add MappingJackson2HttpMessageConverter //Add the original remaining 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】Original quantity: %s, reconstructed quantity: %s" , orgConverterList.size(), messageConverters.size())); } catch (Ausnahme 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
The above is all the content of this article. Ich hoffe, es wird für das Lernen aller hilfreich sein und ich hoffe, jeder wird Wulin.com mehr unterstützen.