Podemos desarrollar rápidamente la interfaz REST a través del arranque de Spring, y también es posible que necesitemos llamar a la interfaz de descanso interna y externa a través del arranque de Spring para completar la lógica comercial durante la implementación de la interfaz.
En Spring Boot, hay dos formas comunes de llamar a las API REST, que se utilizan para implementar llamadas de servicio a través de RestTemplate incorporado o desarrollar su propia herramienta HTTP Cliente.
RestTemplate tiene funciones básicas muy potentes, pero en algunos escenarios especiales, podemos estar más acostumbrados a usar nuestras propias clases de herramientas encapsuladas, como cargar archivos a sistemas de archivos distribuidos, procesar solicitudes HTTPS con certificados, etc.
Este artículo utiliza RestTemplate como ejemplo para registrar varios problemas y soluciones encontradas durante el uso de RestTemplate para llamar a la interfaz.
1. Introducción a RestTemplate
1. ¿Qué es RestTemplate?
El httpclient que nos encapsulamos generalmente tiene algún código de plantilla, como establecer una conexión, construir un encabezado de solicitud y un cuerpo de solicitud, luego analizar la información de respuesta basada en la respuesta y finalmente cerrar la conexión.
RestTemplate es una reencapsulación de HTTPClient en Spring, que simplifica el proceso de iniciar solicitudes HTTP y respuestas de procesamiento, con niveles de abstracción más altos, reduciendo el código de plantilla del consumidor y hacer un código menos redundante.
En realidad, si piensa en muchas clases de XXXTemplate bajo el arranque de Spring, también proporcionan varios métodos de plantilla, pero el nivel de abstracción es más alto y se ocultan más detalles.
Por cierto, Spring Cloud tiene una llamada de servicio de servicio declarativo Feign, que se implementa en base a Netflix Feign, integra Spring Cloud Ribbon y Spring Cloud Hystrix, e implementa un método de definición del cliente de servicio web declarativo.
En esencia, Feign es encapsularlo nuevamente sobre la base de RestTemplate, lo que nos ayuda a definir e implementar la definición de interfaces de servicio dependientes.
2. Métodos comunes para la respuesta de reposo
Existen muchos métodos de solicitud para servicios de descanso comunes, como Get, Post, Put, Eliminar, la cabeza, las opciones, etc. RestTemplate implementa el método más común, y los más utilizados son Get and Post. Puede consultar el código fuente cuando llame a la API. Aquí hay algunas definiciones de métodos (obtener, publicar, eliminar):
métodos
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 (url de cadena, @nullable solicitud de objeto, class <T> ResponseTetype, objeto ... Urivaria) public <T> String url, @nullable Solicitud de objeto, class <T> ResponseTetype, objeto ... Urivaria) public <T> String URL, @nullable Object Solicitud URL, @Nullable Object Solicitud, clase <T> ResponseType, objeto ... urivariables) public void Eliminar (URL de cadena, objeto ... urivariables) public void Eliminar (uri url)
Al mismo tiempo, debe prestar atención a dos métodos más "flexibles" intercambiar y ejecutar.
El intercambio expuesto por RestTemplate es diferente de otras interfaces:
(1) Permita que la persona que llama especifique el método de solicitud HTTP (obtener, publicar, eliminar, etc.)
(2) La información de cuerpo y encabezado se puede agregar a la solicitud, y su contenido se describe con el parámetro 'httpentity <?> Solicitity'
(3) Exchange admite 'Tipo que contiene parámetros' (es decir, clase genérica) como tipo de retorno, y esta característica se describe mediante 'ParameterizedTyPereference <T> ResponseType'.
RestTemplate All Get, Post y otros métodos, y la llamada final del método de ejecución es el método Ejecutar. La implementación interna del método Excute es convertir el URI de formato de cadena en java.net.uri, y luego se llama al método doexecute. La implementación del método DoExecute es la siguiente:
doexecute
/*** Ejecute el método dado en el URI proporcionado. * <p> El {@link clientHttprequest} se procesa utilizando {@link requestCallback}; * La respuesta con el {@link ResponseExtractor}. * @param URL La URL totalmente expandida para conectarse al método * @param el método http para ejecutar (get, post, etc.) * @param requestCallback Object que prepara la solicitud (puede ser {@code null}) * @param Respuesta de respuesta al objeto que extrae el valor de retorno de la respuesta (puede ser {@code null}) * @return un objeto arbitrarrario, como se retira por el valor de retorno (@link (puede ser {@code}) * @return un objeto arbitrarrario, como se retira por el valor de retorno (puede ser {puede ser {@code}) * @return un objeto arbitrarrario, como se retira. ResponseExtractor} */ @Nullable Protected <T> t doExecute (uri url, @nullable httpmethod método, @nullable requestCallback requestCallback, @nullable ResponseExtractor <T> ResponseeStractor) lanza RESTCLIENTEXCECTIONS {ASSERT.NOTNULL (URL, "URL" no debe ser null "); Afirmar.notnull (método, "'método' no debe ser nulo"); ClientHttPResponse Response = NULL; Pruebe {ClientHttpRequest Soly = CreateRequest (url, método); if (requestCallback! = null) {requestCallback.dowithRequest (solicitud); } respuesta = request.exeCute (); HandlerEponse (URL, método, respuesta); if (ResponseExtractor! = NULL) {return responsextractor.extractData (respuesta); } else {return null; }} Catch (ioException ex) {String Resource = url.ToString (); String Query = url.getrawQuery (); Resource = (Query! = Null? Resource.substring (0, Resource.IndexOf ('?')): Resource); Agregue nuevo ResourceAccessException ("Error de E/S en" + Method.Name () + "Solicitud para/" " + Resource +"/":" + Ex.getMessage (), ex); } finalmente {if (respuesta! = null) {respuesta.close (); }}}El método DoExecute encapsula métodos de plantilla, como crear conexiones, solicitudes de procesamiento y respuestas, conexiones de cierre, etc.
Cuando la mayoría de las personas ven esto, probablemente piensen que encapsular un descanso es así, ¿verdad?
3. Llamada simple
Tome una llamada de publicación como ejemplo:
BetsserviceClient
paquete com.power.demo.restclient; import com.power.demo.common.appconst; import com.power.demo.restclient.clientRequest.clientgetgoodsbygoodsidrequest; import com.power.demo.restclient.clientResponse.clientgetgodsbysidRespesse; import org.springframework.beans.factory.annotation.aUtowired; import org.springframework.beans.factory.annotation.value; import org.springframework.stereotype.component; importe org.springFrame.web.client.resttatemplate;/***Product Interface CLANT GoodsServiceClient {// La URL de la interfaz llamada por el consumidor del servicio es el siguiente: http: // localhost: 9090 @Value ("$ {spring.power.serviceUrl}") cadena privada _serviceUrl; @AUtoWired private RestTemplate RestTTemplate; Public ClientgetGoodsbyGoodSidResponse GetGoodsbygoodsid (ClientgetGoodSbyGoodSidRequest Solicitud) {String svCurl = getGoodsSvCurl () + "/getInfobyid"; ClientgetGoodsbyGoodSidResponse Response = null; Pruebe {Respuesta = RestTemplate.PostforObject (svcurl, request, clientgetgoodsbygoodsidResponse.class); } catch (Exception e) {E.PrintStackTrace (); respuesta = new ClientgetGoodSbyGoodSidResponse (); Response.setCode (appconst.fail); respuesta.setMessage (e.ToString ()); } Respuesta de retorno; } cadena privada 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/bienes", _serviceUrl); } else {url = string.format ("%s/api/v1/bienes", _serviceUrl); } URL de retorno; }}En la demostración, el método RestTemplate.postforObject se llama directamente, y la entidad de deserialización se convierte en esta encapsulación interna RESTTemplate.
2. Resumen de problemas
1. No se encuentra HTTPMessageConverter adecuado para la excepción de tipo de solicitud
Este problema generalmente ocurre cuando un objeto entrante se pasa en PostforObject para llamadas.
Analice el código fuente RestTemplate. En el método DowithRequest de la clase HttpentityRequestCallback, si los MessageConverters (este campo continuará mencionándose más adelante) no cumple con la lógica que las devoluciones saltaron (es decir, no hay httpMessageConverter coincidente), se inicia la excepción anterior:
HttpentityRequestCallback.dowithrequest
@Override @SupessWarnings ("no controlado") public void dowithRequest (ClientHttpRequest httprequest) lanza IoException {super.dowithrequest (httprequest); Objeto requestbody = this.requestentity.getBody (); if (requestBody == null) {httpheaders httpheaders = httprequest.getheaders (); Httpheaders requestheaders = this.requestentity.getheaders (); if (! requestheaders.isempty ()) {for (map.entry <string, list <string>> Entry: requestheaders.Entryset ()) {httpheaders.put (entry.getKey (), new LinkedList <> (Entry.getValue ())); }} if (httpheaders.getContentLength () <0) {httpheaders.setContentLength (0l); }} else {class <?> requestBodyClass = requestBody.getClass (); Escriba requestBodyType = (this.RequestEntity Instance de requestentity? (((Requestity <?>) This.RequestEntity) .gettype (): requestBodyClass); Httpheaders httpheaders = httprequest.getheaders (); Httpheaders requestheaders = this.requestentity.getheaders (); Mediatype requestContentType = requestheaders.getContentType (); para (httpMessageConverter <?> MessageConverter: getMessageConverters ()) {if (MessageConverter instancia de generichttpMessageConverter) {generichttpMessageConverter <pectus> genericConverter = (generictTpMessageConverter <Sang>) MessageConverter; if (genicConverter.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 (requitsContentType! = null) {logger.debug ("writing [" + requestbody + "] as /" + requitsContentType + " /" usando [" + MessageConverter +"] ");} else {logger.debug (" Writing [" +" + "" + " MessageConverter + "]"); : requesters.entryset ()) {httpheaders.put (entry.getKey (), new LinkedList <> (Entry.getValue ()); usando [" + MessageConverter +"] "); } else {logger.debug ("escribir [" + requestbody + "]"); }} ((HttpMessageConverter <S Object>) MessageConverter) .Write (requestbody, requestContentType, httprequest); devolver; }} String Message = "No se pudo escribir solicitud: no se encuentra httpmessageConverter adecuado para el tipo de solicitud [" + requestBodyClass.getName () + "]"; if (requestContentType! = null) {mensaje + = "y tipo de contenido [" + requestContentType + "]"; } tirar nueva RestClientException (mensaje); }} La solución más fácil es envolver el encabezado de solicitud HTTP y serializar el objeto de solicitud en una cadena para pasar los parámetros. El código de muestra de referencia es el siguiente:
postforobject
/ * * Post Solicitud de solicitud * */ public static String postforObject (RestTemplate RestTemplate, String URL, Object Params) {httpheaders encabezados = new httpheaders (); Mediatype type = Mediatype.Parsemediatype ("Aplicación/JSON; Charset = UTF-8"); Headers.setContentType (tipo); Headers.Add ("Aceptar", Mediatype.application_json.ToString ()); Cadena json = SerializeUtil.Serialize (params); HttPentity <String> FORMENTITY = new Httpentity <String> (JSON, encabezados); String result = RestTemplate.PostforObject (url, formentity, string.class); resultado de retorno; }Si también queremos devolver directamente el objeto, podemos deserializar directamente la cadena devuelta:
postforobject
/ * * Post Request Call * */ public static <t> t postforObject (RestTemplate RestTemplate, String Url, Object Params, Class <T> CLAZZ) {t respuesta = null; Cadena respstr = postforObject (RestTemplate, URL, Params); respuesta = SerializeUtil.deserialize (respstr, clazz); Respuesta de retorno; }Entre ellos, hay muchas herramientas para la serialización y la deserialización, como Fastjson, Jackson y Gson.
2. No se encuentra HTTPMessageConverter adecuado para la excepción de tipo de respuesta
Al igual que se produce una excepción al iniciar una solicitud, también habrá problemas al manejar la respuesta.
Alguien hizo la misma pregunta en Stackoverflow. La causa raíz es que el convertidor de mensajes HTTP httpMessageConverter carece de tipo mimo. Es decir, cuando HTTP transmite el resultado de salida al cliente, el cliente debe iniciar la aplicación apropiada para procesar el documento de salida. Esto se puede hacer a través de múltiples tipos de MIME (protocolo de aumento de correo de Internet multifuncional).
Para las respuestas del lado del servidor, muchos httpmessageConverter admiten diferentes tipos de medios (mimetypes) de forma predeterminada. El soporte predeterminado para StringHTTPMessageConverter es Mediatype.text_lain, el soporte predeterminado para SourceHttpMessageConverter es Mediatype.text_xml, el soporte predeterminado para FormHTTPMessageConverter es Mediatype.applation_Form_urlencoded y Mediatype.multIPart_Data. En el servicio REST, lo máximo que usamos es Mappingjackson2httpMessageConverter. , este es un convertidor relativamente general (heredado de la interfaz GenericttpMessageConver). Según el análisis, el MIMETYPE que admite por defecto es Mediatype.Application_JSON:
Mappingjackson2httpmessageConverter
/*** Construya un nuevo {@link MappingJackson2httpMessageConverter} con un personalizado {@link ObjectMapper}. * Puede usar {@link Jackson2ObjectMapperBuilder} para construirlo fácilmente. *@see jackson2ObjectMapperBuilder#json () */ public Mappingjackson2httpMessageConverter (ObjectMapper ObjectMapper) {super (ObjectMapper, Mediatype.Application_JSON, New Mediatype ("Aplicación", " *+Json")); }Sin embargo, la respuesta predeterminada de algunas interfaces de aplicación mimetype no es la aplicación/JSON. Por ejemplo, si llamamos a una interfaz de pronóstico meteorológico externo, si usamos la configuración predeterminada de RestTemplate, no es un problema devolver directamente una respuesta de cadena:
String url = "http://wthrcdn.etouch.cn/weather_mini?city=shanghai";string result = resttEmplate.getForObject (url, string.class); clientweatherResultvo vo = serializeutil.deserialize (resultado, clientwreatresultvo.class);
Sin embargo, si queremos devolver un objeto de entidad directamente:
String url = "http://wthrcdn.etouch.cn/weather_mini?city=shanghai" ;clientweatherResultvo weatherResultvo = resttemplate.getforObject (url, clientweatherResultvo.class);
Luego informe una excepción directamente:
No se pudo extraer respuesta: no se encuentra httpmessageConverter adecuado para el tipo de respuesta [clase]
y tipo de contenido [aplicación/octet-stream]
Muchas personas se han encontrado con este problema. La mayoría de ellos están confundidos cuando lo encuentran por primera vez. Muchas interfaces se devuelven en formato de texto JSON, XML o simple. ¿Qué es la aplicación/flujo de octeto?
Verifique el código fuente RestTemplate y realice un seguimiento de él hasta el final, encontrará que el método ExtractData de la clase httpMessageConterExtractor tiene una respuesta de análisis y lógica de deserialización. Si no tiene éxito, la información de excepción lanzada es la misma que la anterior:
HttpmessageConverterExtractor.extractData
@Override @SupessWarnings ({"sin verificar", "RawTypes", "recursos"}) public t ExtractData (ClientHttPResponse Respuesta) lanza IOException {MessageBodyClientHttTTPonseWrappers ResponsewRapper = New MessageBodyClientHtTTTTTRAPPER (respuesta); if (! ResponseWrapper.HasMessageBody () || Responsewrapper.hasemptyMessageBody ()) {return null; } Mediatype contentType = getContentType (ResponseWrapper); Pruebe {for (httpMessageConverter <?> MessageConverter: this.MessageConverters) {if (MessageConverter Instance of GenerichttpMessageConverter) {generichttpMessageConverter <?> genericMessageConverter = (generictttpMessageNverter <?>) if (genicMessageConverter.canread (this.responseSetype, null, contentType)) {if (logger.isdeBugeNabled ()) {logger.deBug ("Reading [" + this.ResponseType + "] AS /" + ContentType + " /" Usando [" + Messageconverter +"] "); } return (t) genicMessageConverter.read (this.responseSetype, null, ResponseWrapper); }} if (this.ResponseClass! = Null) {if (MessageConverter.canRead (this.ResponseClass, contentype)) {if (logger.isDebugenabled ()) {logger.debug ("Reading [" + this.responseClass.getName () + "] as /" " + ContentTypeTypeType +" /" /" "Usar +" "" MessageConverter + "]"); } return (t) MessageConverter.read ((clase) this.ResponseClass, ResponseWrapper); }}}} Catch (ioException | httpMessageNoTreadableException ex) {Throw New RestClientException ("Error al extraer la respuesta para el tipo [" + this.ResponseType + "] y el tipo de contenido [" + ContentType + "]", ex); } Lanzar nueva RestClientException ("no pudo extraer respuesta: no se encontró httpmessageConverter adecuado" + "para el tipo de respuesta [" + this.responseType + "] y el tipo de contenido [" + contentType + "]"); } El código de muestra de la solución en StackOverflow es aceptable, pero no es preciso. Se deben agregar mimetipos comunes. Publicaré el código que creo que es correcto:
ResttemplateConfig
paquete 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.weboT org. org.springframework.http.converter.feed.atomfeedhttpmessageConverter; import org.springframework.http.converter.feed.rsschannelhttpMessageConverter; import og.springframework.http.converter.json.gegsonhtters; org.springframework.http.converter.json.jsonbhttpmessageConverter; import org.springframework.http.converter.json.mappingjackson2httpmessageConver; import org. org.springframework.http.converter.xml.jaxb2rooTelementHttpMessageConverter; import og.springframework.http.converter.xml.mappingjackson2xmlhttpmessageConverter; import org.springframework.http.converter.xml.sourcehttpmessageConverter; import org.springframework.stereotype.component; import og.springframework.util.classutils; importar org.springframework.web.client.resttemplate; import javeS. java.util.list; @ComponentPublic Class ResttEmplateConfig {private static final boolean romePresent = classUtils.espresent ("com.rometools.rome.feed.wirefeed", restsplate .class.getClassLoader ()); Private static final boolean jaxb2Present = classUtils.esPresent ("javax.xml.bind.binder", resttemplate.class.getclassloader ()); Private static final boolean jackson2Present = classUtils.esPresent ("com.fasterxml.jackson.databind.objectMapper", resttemplate.class.getclassloader ()) && classutils.ispresent ("com.fasterxml.jackson.core.jsongenerator", rettttate.class.classemtM (); Private Static Final Boolean Jackson2xmlPresent = classUtils.esPresent ("com.fasterxml.jackson.dataformat.xml.xmlmapper", Resttemplate.class.getClassLoader ()); Private Static final boolean jackson2smilepresent = classUtils.espresent ("com.fasterxml.jackson.dataformat.smile.smilefactory", resttemplate.class.getclassloader ()); Private Static final boolean jackson2cborpresent = classUtils.espresent ("com.fasterxml.jackson.dataformat.cbor.cborfactory", resttemplate.class.getclassloader ()); Private static final boolean gsonPresent = classUtils.espersent ("com.google.gson.gson", resttEmplate.class.getClassLoader ()); Private Static final boolean jsonbPresent = classUtils.espresent ("javax.json.bind.jsonb", resttemplate.class.getclassloader ()); // Tenga en cuenta que al comenzar eso, ya que inyectamos RestTemplate en el servicio, necesitamos instanciar una instancia de esta clase al comenzar @AUTOWIREDIRDIRed RestTemplateBuilder Builder; @AUtowired private ObjectMapper ObjectMapper; // Use RestTemplateBuilder para instanciar el objeto RestTemplate. Spring ha inyectado la instancia de RestTemplateBuilder de forma predeterminada @Bean RESTTEmplate RestTeMplate () {RestTemplate RestTemplate = Builder.Build (); List <httpmessageConverter <? >> MessageConverters = lists.newArrayList (); MAPPINGJACKSON2HTTPMESSAGECONVERTER Converter = new MappingJackson2httpMessageConverter (); convertidor.setObjectMapper (ObjectMapper); // La excepción aparecerá sin agregar // no pudo extraer respuesta: no se encuentra httpmessageConverter adecuado para el tipo de respuesta [class] mediatype [] mediAtypes = new Mediatype [] {Mediatype.application_json, mediatype.application_octet_stream, mediAtype.application_json_utf8, mediatype.text_hextml, mediAtype.textype.textype. Mediatype.text_xml, mediatype.application_stream_json, mediatype.application_atom_xml, mediatype.application_form_urlencoded, mediatype.application_pdf,}; convertidor.setsupportedmediatypes (matrizs.aslist (mediatypes)); //messageConverters.add(converter); if (Jackson2Present) {MessageConverters.add (convertidor); } else if (gsonPresent) {MessageConverters.Add (new GsonhttpMessageConverter ()); } else if (jsonBPresent) {MessageConverters.add (new JsonBhttpMessageConverter ()); } MessageConverters.Add (nuevo formhttpMessageConverter ()); MessageConverters.Add (new StringHttpMessageConverter ()); MessageConverters.Add (new StringHttpMessageConverter ()); MessageConverters.Add (new ResourcehttpMessageConverter (falso)); MessageConverters.add (New SourceHttpMessageConverter ()); MessageConverters.Add (nuevo AllEcmompassingFormhttpMessageConverter ()); if (romePresent) {MessageConverters.add (nuevo AtomFeedHttpMessageConverter ()); MessageConverters.Add (nuevo RSSChannelhttpMessageConverter ()); } if (jackson2xmlPresent) {MessageConverters.add (nuevo MappingJackson2xmlhttpMessageConverter ()); } else if (jaxb2Present) {MessageConverters.Add (new Jaxb2ROOTELEMENTHTTPMessageConverter ()); } if (jackson2smilepresent) {MessageConverters.add (nuevo MappingJackson2SmileHttpMessageConverter ()); } if (Jackson2CborPresent) {MessageConverters.Add (nuevo MappingJackson2CborhttpMessageConverter ()); } Resttemplate.SetMessAgeconverters (MessageConverters); return RestTemplate; }}Después de ver el código anterior y comparar la implementación interna de RestTemplate, sabrá que he referido al código fuente de RestTemplate. Las personas obsesionadas con la limpieza pueden decir que este código es un poco detallado. El grupo anterior de variables finales estáticas y los métodos de datos de MessageConverters exponen la implementación de RESTTemplate. Si se modifica RestTemplate, también se modificará aquí, que es muy hostil y no se ve en absoluto.
Después del análisis, RestTemplateBuilder.Build () construye el objeto RestTemplate. Simplemente modifique el mediatipo compatible con el mappingjackson interno2httpmessageConverter. Aunque el campo MessageConverters of RestTemplate es privado final, aún podemos modificarlo a través de la reflexión. El código mejorado es el siguiente:
ResttemplateConfig
paquete 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.weboT org. org. ResttEmplateConfig {// Tenga en cuenta que al comenzar eso porque inyectamos RestTemplate en el servicio, necesitamos instanciar una instancia de la clase al comenzar @AUTOWIREDIRDIRed RestTemplateBuilder Builder; @AUtowired private ObjectMapper ObjectMapper; // Use RestTemplateBuilder para instanciar el objeto RestTemplate. Spring ha inyectado la instancia de RestTemplate por defecto @Bean public List <httpmessageConverter <? >> MessageConverters = lists.newArrayList (); MAPPINGJACKSON2HTTPMESSAGECONVERTER Converter = new MappingJackson2httpMessageConverter (); convertidor.setObjectMapper (ObjectMapper); //Exception may occur if no addition//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,}; convertidor.setsupportedmediatypes (matrizs.aslist (mediatypes)); Pruebe {// Establezca MessageConverters a través del campo Reflection Field = RestTemplate.getClass (). GetDeclaredField ("MessageConverters"); campo.setAccessible (verdadero); List <httpMessageConverter <? >> orgConverterList = (list <httpMessageConverter <? >>) field.get (RestTemplate); Opcional <httpMessageConverter <? >> opConverter = orgConverterList.Stream () .Filter (x -> x.getClass (). GetName (). EqualSignReCase (mappingJackson2httpMessageConverter.class .getName ())) .FinDfirst (); if (opConverter.espersent () == false) {return resttEmplate; } MessageConverters.Add (convertidor); // Agregar MappingJackson2httpMessageConverter // Agregue la lista original de httpmessageConverter restante <httpmessageConverter <?>> LeftConverters = orgConverterlist.stream (). x.getClass (). getName (). EqualSignorEcase (MappingJackson2httpMessageConverter.Class .getName ()) == False) .Collect (coleccionista.tolist ()); MessageConverters.addall (LeftConverters); System.out.println (String.Format ("【httpMessageConverter】 Cantidad original: %s, cantidad reconstruida: %s", orgConverterList.size (), MessageConverters.size ())); } catch (Exception e) {E.PrintStackTrace (); } Resttemplate.SetMessAgeconverters (MessageConverters); return RestTemplate; }}Además de un campo MessageConverters, parece que ya no nos importa los paquetes de dependencia externos y los procesos de construcción internos de RestTemplate. Como se esperaba, es mucho limpio y conciso y fácil de mantener.
3. Problema del código de basura
Esta es también una pregunta muy clásica. La solución es muy simple, encuentre httpmessageConverter y eche un vistazo al charset compatible predeterminado. AbstractJackson2httpMessageConverter es una clase base para muchos httpmessageConverter, y la codificación predeterminada es UTF-8:
AbstractJackson2httpMessageConverter
public abstract class abstractJackson2httpMessageConverter extiende abstractGenerichttpMessageConverter <ject> {public static final Charset default_charset = StandardCharsets.utf_8;}StringHttpMessageConverter es bastante especial. Algunas personas han informado que el problema confuso es causado por la codificación ISO-8859-1 que admite por defecto:
StringhttpmessageConverter
/*** Implementación de {@link httpmessageConverter} que puede leer y escribir cadenas. * * <p> Por defecto, este convertidor admite todos los tipos de medios ({@code}), * y escribe con un {@code content-type} de {@code text/sencillo}. Esto se puede anular * estableciendo la propiedad {@link #setsupportedMediatypes SupportedMediatyPes}. * * @author Arjen Poutsma * @author Juergen Hoeller * @since 3.0 */public class StringHttpMessageConverter extiende AbstracthttpMessageConverter <tring> {public static final Charset default_charset = StandardCharSets.iso_8859_1; /*** Un constructor predeterminado que usa {@code "ISO-8859-1"} como el charset predeterminado. * @see #StringHttpMessageConverter (charset) */ public stringHttpMessageConverter () {this (default_charset); }}Si se produce un código confuso durante el uso, podemos establecer la codificación admitida por httpmessageConverter a través de métodos, los que se usan comúnmente incluyen UTF-8, GBK, etc.
4. Excepción de deserialización
Este es otro problema que es fácil de encontrar durante el proceso de desarrollo. Debido a que hay tantos marcos y herramientas de código abierto en Java y la versión cambia con frecuencia, a menudo ocurren dificultades inesperadas.
Tómese el tiempo de Joda como ejemplo. El tiempo de Joda es un popular marco de tiempo y fecha de Java, pero si su interfaz expone el tipo de tiempo de Joda, como la hora de fecha, entonces la persona que llama (sistemas isomórficos y heterogéneos) puede encontrar problemas de serialización e incluso lanzar las siguientes excepciones directamente durante la deserialización:
org.springframework.http.converter.httpmessageConversionException: Tipo de definición Error: [Tipo simple, clase org.joda.time.cronology]; La excepción anidada es com.fasterxml.jackson.databind.exc.invaliddefinitionException: No se puede construir una instancia de `org.joda.time.cronology` (sin creadores, como la construcción predeterminada, existir): los tipos abstractos deben asignarse a tipos concretos, tener un deserializador personalizado o contener información de tipo adicional
en [Fuente: (PushbackInputStream);
Encontré esto en la fábrica anterior, y más tarde, para la conveniencia de llamar, volví al tipo de fecha que expuso directamente a Java.
Por supuesto, hay más que esta solución. Puede usar Jackson para apoyar la serialización y la deserialización de las clases personalizadas. En sistemas con requisitos de precisión no muy altos, implementen una serialización personalizada simple de fecha y hora:
DataTimesSerializer
paquete com.power.demo.util; import com.fasterxml.jackson.core.jsongenerator; import com.fasterxml.jackson.core.jsonprocessingexception; import com.fasterxml.jackson.databind.jsonserializer; import com.fasterxml.jackson.databind.serialiator; importialer; org.joda.time.datetime; import org.joda.time.format.dateTimeFormat; import org.joda.time.format.datetimeFormatter; import java.io.ioException;/*** Por defecto, Jackson serializará el tiempo de Joda en una forma más compleja, que no es llevada a cabo para leer y tiene un objeto más grande. * <p>* Al serializar Jodatime, DateTime se puede serializarse en una cadena, que es más fácil de leer **/public class DatTimeSerializer extiende JSonserializer <Stetime> {private staticeMeFormatter dateFormatter = datTimeFormat.forPattern ("yyyyyy-mm-dd hh: mm: ss");; @Override public void serialize (valor de fecha y hora, jsongenerator JGen, proveedor de serializador) lanza IOException, JSONProcessingException {JGen.WriteString (value.ToString (dateFormatter)); }} Y deserialización de fecha y hora:
Datetimedeserializer
paquete com.power.demo.util; import com.fasterxml.jackson.core.jsonparser; import com.fasterxml.jackson.core.jsonprocessingexception; import com.fasterxml.jackson.dataBind.deserializationContex com.fasterxml.jackson.databind.jsonnode; import org.joda.time.datetime; import org.joda.time.format.dateTimeFormat; import org.joda.time.format.datetimeFormatter; import java.io.ioexception;/***jodatime seglings strings en strings en strings en strings en strings en strings en el strings de strings de strings de strings de strings de strings de strings de strings de strings de strings. DateTimedeserializer extiende JSondeserializer <Stetime> {private static dateTimeFormatter dateFormatter = DateTimeFormat.ForPattern ("yyyy-mm-dd hh: mm: ss"); @Override public DateTime Deserialize (JSONParser JP, DeserializationContext Context) lanza IOException, JSONProcessingException {JSONNode Node = jp.getCodeC (). Readtree (JP); Cadena s = node.astext (); DateTime parse = DateTime.Parse (s, dateFormatter); Volver a Parse; }}Finalmente, puede resumir los problemas de llamadas comunes en la clase RESTTempLateConFig, y puede consultarlo de la siguiente manera:
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 { // 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 (Exception e) {E.PrintStackTrace (); } restTemplate.setMessageConverters(messageConverters); return restTemplate; }}目前良好地解决了RestTemplate常用调用问题,而且不需要你写RestTemplate帮助工具类了。
上面列举的这些常见问题,其实.NET下面也有,有兴趣大家可以搜索一下微软的HttpClient常见使用问题,用过的人都深有体会。更不用提RestSharp 这个开源类库,几年前用的过程中发现了非常多的Bug,到现在还有一个反序列化数组的问题困扰着我们,我只好自己造个简单轮子特殊处理,给我最深刻的经验就是,很多看上去简单的功能,真的碰到了依然会花掉不少的时间去排查和解决,甚至要翻看源码。所以,我们写代码要认识到,越是通用的工具,越需要考虑到特例,可能你需要花80%以上的精力去处理20%的特殊情况,这估计也是满足常见的二八定律吧。
referirse a:
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. Espero que sea útil para el aprendizaje de todos y espero que todos apoyen más a Wulin.com.