Podemos desenvolver rapidamente a interface restante através da Spring Boot, e também podemos precisar chamar a interface de repouso interna e externa através da Spring Boot para concluir a lógica de negócios durante a implementação da interface.
Na Spring Boot, existem duas maneiras comuns de chamar APIs REST, que são usados para implementar chamadas de serviço através do RestTemplate Integral ou desenvolver sua própria ferramenta de cliente HTTP.
O Resttemplate possui funções básicas muito poderosas, mas em alguns cenários especiais, podemos estar mais acostumados a usar nossas próprias classes de ferramentas encapsuladas, como fazer upload de arquivos para sistemas de arquivos distribuídos, processando solicitações HTTPS com certificados etc.
Este artigo usa RestTemplate como exemplo para registrar vários problemas e soluções encontradas durante o uso do Resttemplate para chamar a interface.
1. Introdução ao Resttemplate
1. O que é Resttemplate
O httpclient que nos encapsulamos geralmente possui algum código de modelo, como estabelecer uma conexão, construir um cabeçalho de solicitação e um órgão de solicitação e, em seguida, analisar as informações de resposta com base na resposta e finalmente fechar a conexão.
O Resttemplate é uma re-encapsulamento do HTTPClient na primavera, que simplifica o processo de iniciar solicitações HTTP e de processamento, com níveis mais altos de abstração, reduzindo o código do modelo do consumidor e fazendo um código menos redundante.
Na verdade, se você pensa em muitas classes xxxxtemplate no Spring Boot, elas também fornecem vários métodos de modelo, mas o nível de abstração é mais alto e mais detalhes estão ocultos.
A propósito, a Spring Cloud possui um serviço de chamada de serviço declarativo, que é implementado com base no Netflix Feign, integra a fita de nuvem da primavera e a hystrix da nuvem da primavera e implementa um método de definição de cliente de serviço da web declarativo.
Em essência, Feign é encapsulá -lo novamente com base no RestTemplate, o que nos ajuda a definir e implementar a definição de interfaces de serviço dependentes.
2. Métodos comuns para Resttemplate
Existem muitos métodos de solicitação para serviços de repouso comuns, como get, post, put, exclusão, cabeça, opções, etc. Resttemplate implementa o método mais comum e os mais usados são Get and Post. Você pode consultar o código -fonte ao chamar a API. Aqui estão algumas definições de método (GET, POST, DELETE):
Métodos
public <T> t GetForObject (URL da string, classe <T> ResponseType, Object ... urivariable) public <T> Responsabilidade <T> GetForentity (String URL, classe <T> ResponseType, Object ... Urivariables) <t> t PostForObject (Url Url, @nullable Object Soldem, Class> Responsivy, objeto <T> T. URL, @Nullable Object Solicy, Class <T> ResponseType, Object ... urivariable) Public void Delete (URL da String, objeto ... Uivariable) Public Void Delete (URI URL)
Ao mesmo tempo, você deve prestar atenção a mais dois métodos "flexíveis" troca e executar.
A troca exposta pelo Resttemplate é diferente de outras interfaces:
(1) Permitir que o chamador especifique o método de solicitação http (obtenha, postar, excluir etc.)
(2) As informações do corpo e do cabeçalho podem ser adicionadas à solicitação, e seu conteúdo é descrito pelo parâmetro 'httpentity <?> Requestentity'
(3) O Exchange suporta 'Tipo contendo parâmetros' (ou seja, classe genérica) como tipo de retorno, e esse recurso é descrito por 'ParametizedTypereference <T> Responsetype'.
Resttemplate todos os métodos Get, Post e outros métodos, e a chamada final do método Execute é o método Execute. A implementação interna do método de escutas é converter o URI formato de string em java.net.uri e, em seguida, o método doexecute é chamado. A implementação do método doexecute é o seguinte:
Doexecute
/*** Execute o método fornecido no URI fornecido. * <p> O {@link clientHttPrequest} é processado usando o {@link requestCallback}; * A resposta com o {@link Responsextractor}. * @param url O URL totalmente extraído para se conectar ao * @param Método O método HTTP a ser executado (obtenha, postar, etc.) * @param request objeto que prepara a solicitação (pode ser {@code null}) * @param ResponderExtract objeto que extrai o valor de retorno da resposta (pode ser {{ @ @ {@link Responsextractor} */ @nullable protegido <t> t Doexecute (Uri URL, @Nullable HttpMethod Método, @Nullable RequestCallback requestCallback, @NullLable Responstractor <T> Responstroutor) lança RestcientException {ASSERT.NotNull (Url); Assert.NotNull (Método, "'Método' não deve ser nulo"); CLIENTHTTPPRESPOnsion Response = null; tente {clienthttprequest request = createrequest (url, método); if (requestCallback! = null) {requestCallback.dowithRequest (request); } resposta = request.execute (); manutenção de manutenção (URL, método, resposta); if (Responsextractor! = null) {retorna respostaextractor.extractData (resposta); } else {return null; }} catch (ioexception ex) {string Resource = url.toString (); String consulta = url.getrawQuery (); Resource = (consulta! = NULL? Resource.SubString (0, Resource.Indexof ('?')): Recurso); lançar novo ResourceAcceSception ("Erro de E/S em" + method.name () + "request for/" " + Resource +"/":" + ex.getMessage (), ex); } finalmente {if (resposta! = null) {Response.close (); }}}O método doexecute encapsula métodos de modelo, como criar conexões, processamento de solicitações e respostas, conexões de fechamento, etc.
Quando a maioria das pessoas vê isso, provavelmente pensa que encapsular umclient é assim, certo?
3. CHAMADA PELASA
Consulte uma chamada postagem como exemplo:
GoodServiceClient
pacote com.power.demo.restclient; importar com.power.demo.common.appconst; importar com.power.demo.restclient.clientRequest.clientGeTGOODSbygoodsidRequest; importgOwer.demo.restclient.portResponsonse.clientGoodsgOodSgOodSgODSGODSGODSGODSGODSGODSGOME org.springframework.beans.factory.annotation.autowired; importar org.springframework.beans.factory.annotation.value; import org.springframework.strestype.component; BOTSSERVICECLIENT {// O URL da interface chamado pelo Consumidor do Serviço é o seguinte: http: // localhost: 9090 @value ("$ {spring.power.power.serviceurl}") private string _serviceurl; @Autowired Private Resttemplate Resttemplate; public clientGetGoodsbygoodsidResponse getgoodsbygoodsid (clientgetgoodsbygoodsidrequest request) {string svcurl = getgoodssvcurl () + "/getinfobyid"; ClientGetGoodsBygoodsidResponse resposta = null; tente {Response = RestTemplate.PostForObject (svcurl, request, clientgetgoodsbygoodsidResponse.class); } catch (Exceção e) {e.printStackTrace (); resposta = new clientGetGoodsBygoodsidResponse (); Response.SetCode (AppConst.Fail); Response.setMessage (e.toString ()); } resposta de retorno; } String 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/bens", _serviceurl); } else {url = string.format ("%s/api/v1/bens", _serviceurl); } retornar URL; }}Na demonstração, o método RestTemplate.PostForObject é chamado diretamente e a entidade de deserialização é convertida nesses encapsulamentos internos Resttemplate.
2. Resumo dos problemas
1. Nenhum httpmessageConverter adequado encontrado para a exceção do tipo de solicitação
Esse problema geralmente ocorre quando um objeto de entrada é aprovado no PostForObject para chamadas.
Analise o código -fonte RestTemplate. No método DowithRequest da classe HttpentityRequestCallback, se os Messageconverters (esse campo continuará sendo mencionado mais adiante) não atender à lógica que retorna (isto é, não há htttpmessageconverter correspondente), a exceção acima é lançada:
HttpentityRequestCallback.dowithRequest
@Override @SUPletSwarnings ("desmarcado") public void DowithRequest (clientHttPrequest httPrequest) lança IoException {super.dowithRequest (httPrequest); Objeto solicitaBody = this.requestentity.getBody (); if (requestbody == null) {httpheaders httpheaders = httprequest.getheaders (); Httpheaders requestaders = this.requestentity.getheaders (); if (! requestHeaders.isEmpty ()) {for (map.entry <string, list <string>> Entrada: requestHeaders.entrySet ()) {httpheaders.put (entradas.getKey (), new LinkedList <> (entradas.getValue ()); }} if (httpheaders.getContentLength () <0) {httpheaders.setContentLength (0L); }} else {classe <?> requestbodyclass = requestbody.getclass (); TIPO requestBodyType = (this.Requestentity Instância do solicitação de solicitação? ((Requestentity <?>) This.requestentity) .gettype (): requestbodyclass); Httpheaders httpheaders = httprequest.getheaders (); Httpheaders requestaders = this.requestentity.getheaders (); MediaTyPE requestContentType = requestHeaders.getContentType (); para (httpmessageConverter <?> messageconverter: getMessageConverters ()) {if (Instância de Messageconverter de GenerichttpMessageConverter) {generichttpMessageconverter <ject) genérico (generichTtTttTPMEssagOnverter <ject) MessCoverter = (GenerichTTTTTTTTTPMAGeconverter <ject) MessCoverter = (GenerichTTTTTTTTTTTP; if (genericConverter.Canwrite (requestbodyType, requestbodyclass, requestContentType)) {if (! requestaders.isEmpty ()) {for (map.entry <string, list <string>> entrada: requestHeaders.entrySet ()) {httpheader.put.get () }} if (logger.isdebugenabled ()) {if (requestContentType! = null) {Logger.debug ("Writing [" + requestBody + "] como /" + requestContentType + " /" usando [" + messageconverter" "]; }} GenericConverter.write (requestbody, requestbodyPe, requestContentType, httprequest); requestaders.entrySet ()) {httpheaders.put (entrada.getKey (), new LinkedList <> (entrada.getValue ())); [" + messageconverter +"] "); } else {Logger.debug ("Writing [" + requestbody + "]"); }} ((HttpmessageConverter <ject>) messageconverter) .write (requestbody, requestContentType, httprequest); retornar; }} String message = "Não foi possível escrever solicitação: nenhum httpmessageConverter adequado encontrado para o tipo de solicitação [" + requestbodyclass.getName () + "]"; if (requestContentType! = null) {message + = "e conteúdo tipo [" + requestContentType + "]"; } lança nova RestClientException (mensagem); }} A solução mais fácil é envolver o cabeçalho da solicitação HTTP e serializar o objeto de solicitação em uma string para passar os parâmetros. O código de amostra de referência é o seguinte:
PostForObject
/ * * Chamada de solicitação de postagem * */ public static string postForObject (RestTemplate Resttemplate, String URL, Objeto Params) {httpheaders headers = new httpheaders (); MediaType Type = MediaType.ParseMEdItype ("Application/JSON; Charset = UTF-8"); headers.setContentType (tipo); headers.add ("Acept", mediatype.application_json.toString ()); String json = serializeutil.Serialize (params); Httpentity <string> formentity = new httpentity <string> (json, cabeçalhos); String result = RestTemplate.PostForObject (URL, Formação, String.class); resultado de retorno; }Se também queremos retornar diretamente o objeto, podemos desservar diretamente a string retornada:
PostForObject
/ * * CHAMADA DE POST SOLD SCOLD * */ public static <T> t PostForObject (Resttemplate Resttemplate, String URL, Params do objeto, classe <t> clazz) {t resposta = null; String respstr = PostForObject (RestTemplate, URL, Params); resposta = serializeutil.deserialize (respstr, clazz); resposta de retorno; }Entre eles, existem muitas ferramentas para serialização e desserialização, como Fastjson, Jackson e GSON.
2. Nenhum httpmessageConverter adequado encontrado para a exceção do tipo de resposta
Assim como ocorre uma exceção ao iniciar uma solicitação, também haverá problemas ao lidar com a resposta.
Alguém fez a mesma pergunta no Stackoverflow. A causa raiz é que o conversor de mensagem HTTP httpmessageConverter não possui tipo MIME. Ou seja, quando o HTTP transmite o resultado da saída para o cliente, o cliente deve iniciar o aplicativo apropriado para processar o documento de saída. Isso pode ser feito por meio de vários tipos MIME (Protocolo multifuncional de aumento da Internet Mail).
Para respostas do lado do servidor, muitos httpmessageConverter suportam diferentes tipos de mídia (mimetipos) por padrão. O suporte padrão para stringhttpmessageConverter é mediatype.text_plain, o suporte padrão para fontehttpmessageConverter é mediatype.text_xml, o suporte padrão para o formhttpmessageconverter mediaType.Apglication_FurLencoded e MediaTyPeRter IStyType.Apglication_UrtLencoded e MediaTyPeter.Apglication_urLencoded. No serviço restante, o máximo que usamos é o MappingJackson2httpmessageConverter. , este é um conversor relativamente geral (herdado da interface generichttpmessageConverter). De acordo com a análise, o mimetype que ele suporta por padrão é mediatype.application_json:
MappingJackson2httpMessageConverter
/*** Construa um novo {@link mappingjackson2httpmessageConverter} com um {@link objectMapper personalizado}. * Você pode usar {@link Jackson2ObjectMapperBuilder} para construí -lo facilmente. *@See Jackson2ObjectMapperBuilder#JSON () */ public Mappingjackson2httpMessageConVerter (ObjectMapper ObjectMapper) {super (objectmapper, mediatype.application_json, new mediatype ("aplicativo", " *+json")); }No entanto, a resposta padrão de algumas interfaces de aplicativo Mimetype não é aplicativo/JSON. Por exemplo, se chamarmos uma interface de previsão meteorológica externa, se usarmos a configuração padrão do RestTemplate, não é problema retornar diretamente uma resposta de string:
String url = "http://wthrcdn.etouch.cn/weather_mini?city=shanghai"; String Result = Resttemplate.getForObject (url, string.class); clientweatherResultvo vo = serializeutil.DeSerialize (Result, Result, ClientReAtherResultVo.cl);
No entanto, se queremos devolver um objeto de entidade diretamente:
String url = "http://wthrcdn.etouch.cn/weather_mini?city=shanghai";clientweatherResultvo weatherresultvo = Resttemplate.getForObject (URL, clientweatherResultvo.class);
Em seguida, relate uma exceção diretamente:
Não foi possível extrair resposta: nenhum httpmessageConverter adequado encontrado para o tipo de resposta [classe]
e tipo de conteúdo [aplicativo/stream de octeto]
Muitas pessoas encontraram esse problema. A maioria deles está confusa quando o encontra. Muitas interfaces são retornadas no formato JSON, XML ou de texto sem formatação. O que é aplicativo/stream de octeto?
Verifique o código -fonte RestTemplate e acompanhe -o por todo o caminho, você descobrirá que o método extractdata da classe httpmessageConverterextractor possui uma lógica de resposta e desserimização. Se não for bem -sucedido, as informações de exceção lançadas são as mesmas que acima:
HttpmessageConverterextractor.extractData
@Override @SUPletSwarnings ({"não contratado", "RawTypes", "Resource"}) public t ExtractData (resposta clientHtpResponse) lança ioexception {messageBodyClientHttProSponseWrapper Responsewrapwper = new MessageBodyClientHttProSponseper (resposta); if (! ResponseWrapper.HasMessageBody () || ResponseWrapper.HasEmemSessageBody ()) {return null; } Mediatype contentType = getContentType (ResponseWrapper); tente {for (httpmessageConverter <?> messageconverter: this.messageConverters) {if (Instância do MessageconVerter de GenerichttpMessageConverter) {generichttpMessMESSAGeconverter <? if (genericmessageConverter.CanRead (this.ResponseType, null, contentType)) {if (logger.isdebugenabled ()) {Logger.debug ("Reading [" + this.ResponseType + "] como /" " + contentType +" /"usando [" + messagec. } return (t) genericmessageConverter.read (this.ResponseType, null, Responswrapper); }} if (this.Responseclass! = NULL) {if (Messageconverter.CanRead (this.Responseclass, contentType)) {if (Logger.isdebugenabled ()) {Logger.debug ("Reading /" this.RessonSeclass.getName () + " + messageconverter + "]"); } return (t) messageconverter.read ((classe) this.Responseclass, ResponsWrapper); }}}} catch (ioexception | httpMessageNotReadableException Ex) {throw new RestClientException ("Erro ao extrair a resposta do tipo [" + this.ResponseType + "] e Tipo de conteúdo [" + contentType + "], Ex); } lança a nova RestClientException ("Não foi possível extrair a resposta: nenhum httpmessageConverter adequado encontrado" + "para o tipo de resposta [" + this.ResponseType + "] e Tipo de conteúdo [" + contentType + "]"); } O código de amostra da solução no StackOverflow é aceitável, mas não é preciso. Mimetipos comuns devem ser adicionados. Vou postar o código que acho correto:
RestTemplateConfig
pacote com.power.demo.restclient.config; importar com.fasterxml.jackson.databind.objectmapper; importar com.google.common.collect.lists; importar.springframework.bean.factory.annotation.autowired; org.springframework.context.annotation.bean; importar org.springframework.http.mediatype; importar org.springframework.http.converter.*; importação org.springframework.http.converter.cborter; org.springframework.http.converter.feed.atomfeedhttpmessageConverter; importar org.springframework.http.converter.famespring.htptpmessageconter; importação.springFrOnTw.Httpttpmessageconter; importJ.SpringFronT.Htttp.CertP.CernAnTernag.Htp.CronstPernag.Merson org.springframework.http.converter.json.jsonbhttpmessageConverter; importar org.springframework.http.converter.json.mappingjackson2httpmessageConverter; importar org.springframework.http.converter.smile.mappingjackson2smilehttpmessageConverter; importar org.springframework.http.converter.support.allencompassingFormHttpMessageConverter; import org.springframework.http.converter.xml.jaxb2rootelementhttpmessageConverter; importar org.springframework.http.converter.xml.mappjackson2xmlHttpmessageConverter; importação org.springframework.http.converter.xml.SourceHttpMessageConverter;import org.springframework.stereotype.Component;import org.springframework.util.ClassUtils;import org.springframework.web.client.RestTemplate;import java.util.Arrays;import Java.util.list; @ComponentPublic Classe RestTemplatEConfig {private estático final booleano romePrente = classutils.ispresent ("com.rometools.rome.feed.wirefeed", Resttemplate .class.getClassloader ()); Private estático final boolean Jaxb2Present = classutils.ispresent ("javax.xml.bind.binder", RestTemplate.class.getclassloader ()); private static final boolean jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", RestTemplate.class.getClassLoader()) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", RestTemplate.class.getClassLoader()); Private estático final boolean Jackson2xmlPresent = classutils.ispresent ("com.fasterxml.jackson.dataformat.xml.xmlmapper", RestTemplate.class.getclassloader ()); private estático final boolean jackson2smilepresent = classutils.ispresent ("com.fasterxml.jackson.dataformat.smile.smileFactory", RestTemplate.class.getclassloader ()); Private estático final boolean Jackson2cborpresent = classutils.ispresent ("com.fasterxml.jackson.dataformat.cbor.cborFactory", Resttemplate.class.getclassloader ()); private estático final boolean gsonpresess = classutils.ispresent ("com.google.gson.gson", Resttemplate.class.getclassloader ()); Private estático final boolean jsonbpresent = classutils.ispresent ("javax.json.bind.jsonb", resttemplate.class.getclassloader ()); // Observe ao iniciar que, como injetamos Resttemplate no serviço, precisamos instanciar uma instância dessa classe ao iniciar o @Autowired Private ResttemplateBuilder Builder; @Autowired Private ObjectMapper ObjectMapper; // Use RestTemplateBuilder para instanciar o objeto RestTemplate. A primavera injetou a instância RestTemplateBuilder por padrão @Bean public RestTemplate RestTemplate () {RestTemplate RestTemplate = Builder.build (); List <httpmessageConverter <? >> messageconverters = lists.newArrayList (); MAPPINGJACKSON2HTTPMESSAGECONVERTER CONVERTER = NOVO MAPPINGINGJON2HTTPMESSAGECONVERTER (); converter.setObjectMapper (objectMapper); // Exceção aparecerá sem adicionar // não foi possível extrair resposta: Nenhuma httpmessageConverter MediaType.text_plain, mediatype.text_xml, mediatype.application_stream_json, mediatype.application_atom_xml, mediatype.application_form_urlencoded, mediatype.application_pdf,}; Converter.SetSupportedMediTypes (Arrays.asList (MediaTypes)); //messageConverters.add(converter); if (Jackson2Present) {Messageconverters.add (conversor); } else if (gsonpresent) {messageconverters.add (novo gsonHttpMessageConverter ()); } else if (jsonbpresent) {messageconverters.add (novo jsonbhttpMessageConverter ()); } messageconverters.add (new FormHttpMessageConverter ()); messageconverters.add (novo stringhttpMessageConverter ()); messageconverters.add (novo stringhttpMessageConverter ()); messageconverters.add (novo ResourceHttpMessageConverter (false)); messageconverters.add (new SourcehttpMessageConverter ()); messageconverters.add (new allencompassingFormHttpMessageConverter ()); if (RomePresent) {Messageconverters.add (new AtomfeedHttpMessageConverter ()); messageconverters.add (novo rsschanLHttpMessageConverter ()); } if (Jackson2xmlPresent) {Messageconverters.add (novo Mappingjackson2xmlHttpMessageConverter ()); } else if (JAXB2PREST) {Messageconverters.add (new JaxB2RooTELEMEMENTHTTPMESSAGECONVERTER ()); } if (Jackson2SmilePresent) {Messageconverters.add (new Mappingjackson2SMILEHTTPMESSAGECONVERTER ()); } if (Jackson2cborpresent) {Messageconverters.add (novo Mappingjackson2cborhttpMessageConverter ()); } RestTemplate.SetMessageConverters (Messageconverters); Retornar Resttemplate; }}Depois de ver o código acima e comparar a implementação interna do Resttemplate, você saberá que me referi ao código -fonte do Resttemplate. Pessoas obcecadas com limpeza podem dizer que este pedaço de código é um pouco detalhado. O grupo acima de variáveis finais estáticas e messageconverters preenchem métodos de dados expõem a implementação do Resttemplate. Se o RestTemplate for modificado, ele também será modificado aqui, o que é muito hostil e não parecerá oO.
Após a análise, RestTemplateBuilder.build () constrói o objeto RestTemplate. Basta modificar o MediaType suportado com o interno Mappingjackson2httpmessageConverter. Embora o campo Messageconverters do Resttemplate seja privado, ainda podemos modificá -lo através da reflexão. O código aprimorado é o seguinte:
RestTemplateConfig
pacote com.power.demo.restclient.config; importar com.fasterxml.jackson.databind.objectmapper; importar com.google.common.collect.lists; importar.springframework.bean.factory.annotation.autowired; org.springframework.context.annotation.bean; importar org.springframework.http.mediatype; importar org.springframework.http.converter.httpmessageConverter; import.springframework.http.converter.jsonverter; import.springframework.http.converter.jonverter; org.springframework.stereotype.component; importar org.springframework.web.client.resttemplate; importar java.lang.reflect.field; importar java.util.arrays; imporrays; RestTemplateConfig {// Observe ao iniciá -lo como injetamos Resttemplate no Serviço, precisamos instanciar uma instância da classe ao iniciar o @AUTOWIRED PRIVADO RESTTEMPLATEBUILLER Builder; @Autowired Private ObjectMapper ObjectMapper; // Use RestTemplateBuilder para instanciar o objeto RestTemplate. A primavera injetou a instância do restante por padrão @Bean public Resttemplate RestTemplate () {RestTemplate RestTemplate = Builder.build (); List <httpmessageConverter <? >> messageconverters = lists.newArrayList (); MAPPINGJACKSON2HTTPMESSAGECONVERTER CONVERTER = NOVO MAPPINGINGJON2HTTPMESSAGECONVERTER (); converter.setObjectMapper (objectMapper); // Exceção pode ocorrer se nenhuma adição // não puder extrair resposta: Nenhuma httpmessageConverter MediaType.Text_XML, MediaType.Application_Stream_Json, MediaType.Application_atom_XML, MediaType.Application_Form_urlencoded, MediaType.Application_Json_Utf8, MediaType.Application_PDF,}; Converter.SetSupportedMediTypes (Arrays.asList (MediaTypes)); tente {// defina o messageconverter através do campo de reflexão = Resttemplate.getClass (). getDecaredfield ("messageconverters"); field.setAccessible (true); List <httpmessageConverter <? >> orgConverterList = (list <httpMessageConverter <? >>) field.get (Resttemplate); Opcional <httpmessageConverter <? >> opconverter = orgConverterList.stream () .Filter (x -> x.getclass (). GetName (). Equalsignorecase (mapingjackson2httpmessageconverter.class .getName (). if (opconverter.ispresent () == false) {return Resttemplate; } Messageconverters.add (conversor); // Adicione o MappingJackson2httpMessageConverter // Adicione a lista httpmessageConverter restante original <httpmessageConverter <? >> lesão de esquerda = orgConverterlist.stream (). x.getClass (). getName (). Equalsignorecase (Mappingjackson2httpMessageConverter.class .getName ()) == false) .Collect (collectors.tolist ()); messageconverters.addall (esquerda); System.out.println (string.format ("【httpmessageConverter】 Quantidade original: %s, quantidade reconstruída: %s", orgConverterList.size (), messageconverters.size ()); } catch (Exceção e) {e.printStackTrace (); } RestTemplate.SetMessageConverters (Messageconverters); Retornar Resttemplate; }}Além de um campo MessageConverters, parece que não nos importamos mais com os pacotes de dependência externa e os processos de construção internos do Resttemplate. Como esperado, é muito limpo, conciso e fácil de manter.
3. Problema do código de lixo
Esta também é uma pergunta muito clássica. A solução é muito simples, encontre httpmessageConverter e dê uma olhada no charset suportado padrão. AbstractJackson2httpmessageConverter é uma classe base para muitos httpmessageConverter, e a codificação padrão é UTF-8:
AbstractJackson2httpMessageConverter
Classe abstrata public abstratojackson2httpmessageConverter estende abstrategenerichttpmessageConverter <ject> {public static final charset default_charset = padrãocharsets.utf_8;}StringhttpMessageConverter é bastante especial. Algumas pessoas relataram que o problema ilegal é causado pela codificação ISO-8859-1 que ele suporta por padrão:
StringhttpMessageConverter
/*** Implementação de {@link httpmessageConverter} que pode ler e escrever strings. * * <p> Por padrão, este conversor suporta todos os tipos de mídia ({@code}), * e grava com um {@code content-type} de {@code text/planing}. Isso pode ser substituído * definindo a propriedade {@link #SetSupportedMediTypes SupportedMediTypes}. * * @Author Arjen Poutsma * @Author Juergen Hoeller * @Since 3.0 */classe pública stringhttpmessageConverter estende abstracthttpmessageConverter <string> {public static final charset default_charsetSetSets.ISO_885_1; /*** Um construtor padrão que usa {@code "ISO-8859-1"} como o charset padrão. * @see #stringhttpmessageConverter (charset) */ public stringhttpMessageConverter () {this (default_charset); }}Se ocorrer um código distorcido durante o uso, podemos definir a codificação suportada pelo httpmessageConverter por meio de métodos, os comumente usados incluem UTF-8, GBK, etc.
4. Exceção de deserialização
Esse é outro problema que é fácil de encontrar durante o processo de desenvolvimento. Como existem muitas estruturas e ferramentas de código aberto em Java e a versão muda com frequência, geralmente ocorrem armadilhas inesperadas.
Reserve o tempo de Joda como exemplo. O Joda Time é uma estrutura popular de tempo e data de Java, mas se a sua interface expõe o tipo de tempo Joda, como o DateTime, então o chamador da interface (sistemas isomorficos e heterogêneos) pode encontrar problemas de serialização e até lançar as seguintes exceções diretamente durante a deseralização:
org.springframework.http.converter.httpmessageConversionException: Tipo de definição Erro: [Tipo simples, classe org.joda.time.chronology]; A exceção aninhada é com.fasterxml.jackson.databind.exc.InValidDefinitionException: Não é possível construir a instância de `org.joda.time.chronology` (sem criadores, como construção padrão, existir): tipos abstratos precisam ser mapeados para tipos de concreto, têm um imediante personalizado, ou contém informações adicionais
na [fonte: (pushbackInputStream);
Encontrei isso na fábrica anterior e, posteriormente, para a conveniência da chamada, mudei de volta para o tipo de data que expostos diretamente em Java.
Obviamente, existem mais do que esta solução. Você pode usar Jackson para apoiar a serialização e a deserialização das classes personalizadas. Em sistemas com requisitos de precisão não muito alta, implemente a serialização personalizada simples da DateTime:
DATETIMERIALIZER
pacote com.power.demo.util; importar com.fasterxml.jackson.core.jsongnerator; importar com.fasterxml.jackson.core.jsonprocessingException; importp.fasterxml.jackson.databind.jsonserializer; importância org.joda.time.dateTime; importar org.joda.time.format.dateTimeFormat; importar org.joda.time.format.datetimeformatter; importar java.io.ioexception;/***, por padrão, Jackson serializará o tempo de joda mais complexo. * <p>* Ao serializar o jodatime, o tempo de dados pode ser serializado em uma string, que é mais fácil de ler **/classe pública DatetimeSerializer estende o jsonserializer <TateTime> {private static datetimeformatternatternatter = DateTimeFormat.forpn ("yyyyy-mm-dd hh: mm: mm: ss: ss") ("yyyyyy-mm-dd hh: mm: mm: ss" ss ") (" yyyyyy-mm-dd hh: mm: mm: ss "ss") ("yyyyyy-mm-dd hh: mm: mm: ss: ss") ("yyyyyy-mm-dd hh: mm: mm: ss" ss ") (" yyyyyy-mm-dd hh: mm: mm: ss "ss"). @Override public void Serialize (DateTime Value, Jsongenerator JGen, SaterializerProvider provedor) lança IoException, JSONProcessingException {jgen.WritEstring (value.tostring (dateFormatter)); }} E Deserialização do DateTime:
DATETIMEDESERIDIZER
pacote com.power.demo.util; importar com.fasterxml.jackson.core.jsonparser; importar com.fasterxml.jackson.core.jsonprocessingException; import com.fasterxml.jackson.databind.deserializationContext; import complyml.jackson.datind.deserializationContext; importS com complicml.jackson.databind.deserializationContext; importS com Comb.terxml.jackson.datind.deserializationcontext; 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 Deserialization converts strings into datetime **/public class DATETIMEDESERializer estende o JSONDeSerializer <TateTime> {private static datetimeformatter dateformatter = datetimeformat.forpattern ("yyyy-mm-dd hh: mm: ss"); @Override Public DateTime Deserialize (JSONPARSER JP, DeserializationContext Context) lança IoException, JSONProcessingException {JsonNode Node = JP.getCodec (). Readtree (JP); String s = node.astext (); DateTime parse = dateTime.parse (s, dateformatter); Retornar análise; }}Por fim, você pode resumir problemas de chamada comuns na classe RestTemplateConfig, e pode se referir a seguinte:
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 (Exceção 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
O exposto acima é todo o conteúdo deste artigo. Espero que seja útil para o aprendizado de todos e espero que todos apoiem mais o wulin.com.