Мы можем быстро разработать интерфейс REST до Spring Boot, и нам также может потребоваться вызвать внутренний и внешний интерфейс REST через Spring Boot, чтобы завершить бизнес -логику во время реализации интерфейса.
В Spring Boot существует два распространенных способа вызов API REST, которые используются для реализации сервисных вызовов через встроенный RestTemplate или разработку вашего собственного клиентского инструмента HTTP.
Resttemplate имеет очень мощные основные функции, но в некоторых специальных сценариях мы можем быть более привыкли к использованию наших собственных инкапсулированных классов инструментов, таких как загрузка файлов в распределенные файловые системы, обработка HTTPS -запросов с сертификатами и т. Д.
В этой статье используется Resttemplate в качестве примера для записи нескольких проблем и решений, найденных при использовании RestTemplate для вызова интерфейса.
1. Введение в Resttemplate
1. Что такое Resttemplate
HttpClient, который мы инкапсулируем, обычно имеют некоторый код шаблона, такой как установление соединения, строительство заголовка запроса и корпус запроса, а затем анализ информации о ответе на основе ответа и, наконец, закрытие соединения.
Resttemplate-это повторная инкапсуляция HttpClient в пружине, которая упрощает процесс инициирования HTTP-запросов и обработки ответов, с более высокими уровнями абстракции, снижением кода потребительского шаблона и созданием менее избыточного кода.
На самом деле, если вы думаете о многих классах XXXTEMPLATE в рамках Spring Boot, они также предоставляют различные методы шаблона, но уровень абстракции выше, а более подробная информация скрыта.
Кстати, Spring Cloud имеет декларативный Failign Service Call, который реализован на основе Netflix Feign, интегрирует Spring Cloud Libbon и Spring Cloud Hystrix и реализует метод определения декларативного клиента веб -службы.
По сути, Feign - это снова инкапсулировать его на основе Resttemplate, что помогает нам определить и реализовать определение зависимых интерфейсов обслуживания.
2. Общие методы для Resttemplate
Существует много методов запроса для общих услуг отдыха, таких как Get, Post, Pult, Delete, Head, Options и т. Д. Resttemplate реализует наиболее распространенный метод, а наиболее часто используемыми являются Get и Post. Вы можете обратиться к исходному коду при вызове API. Вот несколько определений метода (Get, post, delete):
методы
public <t> t getForObject (String URL, класс <T> responseType, Object ... urivariables) public <t> responseentity <t> GetForentity (String URL, класс <T> respenseType, объект ... urivariables) public <t> t postforobject (String url, @nullable объект запрос, класс <t> responsetype, объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект ... объект. Postforentity (String URL, @nullable объекта запроса объекта, класс <t> respenceType, объект ... urivariables) public void delete (string url, объект ... urivariables) public void delete (uri url)
В то же время вы должны обратить внимание на еще два «гибких» обмена методами и выполнять.
Обмен, выявленная Resttemplate, отличается от других интерфейсов:
(1) Разрешить абоненту указать метод HTTP -запроса (получить, отправить, удалить и т. Д.)
(2) Информация о теле и заголовках может быть добавлена в запрос, и его содержание описывается параметром 'httpentity <?> Запрос
(3) Обмен поддерживает «тип, содержащий параметры» (то есть общий класс) в качестве типа возврата, и эта функция описывается «параметризованным типированием <t> responseType».
RESTTEMPLATE ALL GET, POST и другие методы, а окончательный вызов метода выполнения - это метод выполнения. Внутренняя реализация чрезмерного метода состоит в том, чтобы преобразовать строковый URI в java.net.uri, а затем вызван метод Doexecute. Реализация метода DoExecute заключается в следующем:
DoExecute
/*** Выполните заданный метод на предоставленном URI. * <p> {@link clienthttprequest} обрабатывается с помощью {@link requestCallback}; * Ответ с {@link responseextractor}. * @param url the fully-expanded URL to connect to * @param method the HTTP method to execute (GET, POST, etc.) * @param requestCallback object that prepares the request (can be {@code null}) * @param responseExtractor object that extracts the return value from the response (can be {@code null}) * @return an arbitrary object, as returned by the {@link responseextractor} */ @nullable protectected <t> t doexecute (uri url, @nullable httpmethod метод, @nullable requestcallback requestcallback, @nullable responseextractor <t> responseextractor) thripes restClientExcept Assert.notnull (метод, «метод» не должен быть нулевым »); Clienthttpresponse response = null; try {clienthttprequest request = createrequest (url, method); if (requestCallback! = null) {requestCallback.dowitHrequest (request); } response = request.execute (); HandlerSponse (URL, метод, ответ); if (responseextractor! = null) {return responseextractor.extractdata (response); } else {return null; }} catch (ioException ex) {string resource = url.toString (); String Query = url.getRawQuery (); resource = (Query! = null? resource.substring (0, resource.indexof ('?')): resource); бросить новый ResourceCcessException ("ошибка ввода/вывода на" + method.name () + "запрос/" " + resource +"/":" + ex.getMessage (), ex); } наконец {if (response! = null) {response.close (); }}}Метод DoExecute инкапсулирует методы шаблона, такие как создание соединений, обработки запросов и ответов, закрытие соединений и т. Д.
Когда большинство людей видят это, они, вероятно, думают, что инкапсулирование RestClient просто так, верно?
3. Простой звонок
В качестве примера обратитесь к сообщению:
GoodsServiceClient
пакет com.power.demo.restclient; import com.power.demo.common.appconst; import com.power.demo.restclient.clientrequest.clientgetgoodsbygoodsidrequest; import com.power.demo.restclient.clientresponse.clientgegoudsbygoodsidresponse; org.springframework.beans.factory.annotation.autowired; import org.springframework.beans.factory.annotation.value; импорт org.springframework.stereotype.component; Import org.springframework.web.client.resttemplate GoodsServiceClient {// URL интерфейса, вызванный потребителем услуг, выглядит следующим образом: http: // localhost: 9090 @value ("$ {spring.power.serviceurl}") частная строка _serviceurl; @Autowired private resttemplate resttemplate; public ClientGetGoodsByGoodsIdresponse getGoodsbyGoodsid (ClientGetGoodsByGoodSidrequest) {String svcurl = getGoodssvcurl () + "/getInfobyid"; ClientGetGoodsByGoodsidResponse ответ = null; try {response = resttemplate.postforObject (svcurl, запрос, clientgetgoodsbygoodsidresponse.class); } catch (Exception e) {e.printstackTrace (); response = new ClientGetGoodsByGoodsidResponse (); response.setCode (appconst.fail); response.setMessage (e.toString ()); } return response; } частная строка 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/tober", _serviceurl); } else {url = string.format ("%s/api/v1/tober", _serviceurl); } return url; }}В демонстрации метод RESTTEMPLATE.POSTOROBJECT называется непосредственно, а объект десериализации преобразуется в эту внутреннюю инкапсуляцию RESTTEMPLATE.
2. Резюме проблем
1. Не найдено подходящего HttpmessageConverter для исключения типа запроса
Эта проблема обычно возникает, когда входящий объект передается в постпробекте для вызовов.
Проанализируйте исходный код Resttemplate. В методе DowithRequest класса httpentityRequestCallback, если MessageConverters (это поле будет продолжать упоминаться позже) не соответствует логике, которая прыгнула (то есть, не существует соответствующего HTTPMessageConverter), вышеупомянутое исключение добавляется: нет никакого соответствующего httpmessageconverter), вышеупомянутое исключение добавляется: нет никакого соответствующего httpmessageconverter), вышеупомянутое исключение: «Вышеупомянутое исключение».
Httpentityrequestcallback.dowithrequest
@Override @suppresswarnings ("unchecked") public void dowithrequest (clienthttprequest httprequest) бросает ioexception {super.dowithrequest (httprequest); Object RequestBody = this.requestentity.getBody (); if (requestbody == null) {httphaders httphaders = httprequest.getheaders (); Httpheaders requestheaders = this.requestentity.getheaders (); if (! requestHeaders.isempty ()) {for (map.Entry <string, list <string >> intry: requestHeaders.EntrySet ()) {httpheaders.put (entry.getKey (), new LinkedList <> (entry.getValue ())); }} if (httphaders.getContentLength () <0) {httpheaders.setContentLength (0l); }} else {class <?> requestBodyClass = requestBody.getClass (); Type requestType = (this.requestentity exance -of requestEntity? (((RequestEntity <?>) This.requestentity) .getType (): requestBodyClass); Httphaders httphaders = httprequest.getheaders (); Httpheaders requestheaders = this.requestentity.getheaders (); MediaType requestContentType = requestHeaders.getContentType (); Для (httpmessageConverter <?> MessageConverter: getMessageConverters ()) {if (экземпляр MessageConverter of generichtpmessageConverter) {generichttpmessageConverter <object> genericConverter = (GenerichttpmessageConverter <bheite>) MessageConverter; if (genericConverter.canWrite (requestBodyType, requestBodyClass, requestContentType)) {if (! requestHeaders.isempty ()) {for (map.Entry <String <String >> intry: requestHeaders.EntrySet ()) {httpHeaders.put (intrint.getkey (), new LinkedList <> (intrylie.get); }} if (logger.isdebugenabled ()) {if (requestContentType! = null) {logger.debug ("writise [" + requestbody + "] as /" + requestContentType + " /" "Использование [" + MessageConverter + "]");} els MessageConverter + "]"); : requestHeaders.EntrySet ()) {httpheaders.put (entry.getKey (), новый LinkedList <> (entry.getValue ()); [" + messageConverter +"] "); } else {logger.debug ("writing [" + requestbody + "]"); }} ((HttpmessageConverter <Object>) MessageConverter) .Write (requestBody, requestContentType, httprequest); возвращаться; }} String message = "Не удалось написать запрос: без подходящего httpmessageConverter, найденного для типа запроса [" + requestbodyclass.getName () + "]"; if (requestContentType! = null) {сообщение + = "и тип контента [" + requestContentType + "]"; } бросить новое RestClientException (сообщение); }} Самое простое решение - обернуть заголовок HTTP -запроса и сериализовать объект запроса в строку для передачи параметров. Спортивный пример кода заключается в следующем:
Postforobject
/ * * Post запрос Call * */ public Static String postforObject (RestTemplate RestTemplate, String URL, Object Params) {httpheaders headers = new httpheaders (); MediaType type = mediaType.parsemediatype ("Application/json; charset = utf-8"); Headers.SetContentType (тип); headers.add ("Accept", mediaType.application_json.tostring ()); String json = serializeutil.serialize (params); Httpentity <string> formentity = new httpentity <string> (json, заголовки); String result = restTemplate.postforObject (url, formentity, string.class); результат возврата; }Если мы также хотим напрямую вернуть объект, мы можем напрямую десериализировать возвращенную строку:
Postforobject
/ * * Post запрос вызов * */ public static <t> t postforobject (resttemplate resttemplate, string url, params объекта, класс <t> clazz) {t response = null; String rospstr = postforobject (resttemplate, url, params); response = serializeutil.deserialize (respstr, clazz); ответный ответ; }Среди них есть много инструментов для сериализации и десериализации, таких как Fastjson, Jackson и Gson.
2. Не найдено подходящего HttpmessageConverter для исключения типа ответа
Точно так же, как исключение происходит при начале запроса, также будут проблемы при обращении с ответом.
Кто -то задал тот же вопрос на Stackoverflow. Основная причина заключается в том, что конвертер сообщений HTTP HTTPMessageConverter не хватает типа MIME. То есть, когда HTTP передает результат вывода клиенту, клиент должен запустить соответствующее приложение для обработки выходного документа. Это может быть сделано с помощью нескольких типов MIME (многофункциональное протокол дополнения интернет -почты).
Для ответов на стороне сервера многие httpmessageConverter поддерживают различные типы носителей (MimeTypes) по умолчанию. Поддержка по умолчанию для StringhttpmessageConverter IS MediaType.text_plain, поддержка по умолчанию для SourcehttpmessageConverter IS MediaType.text_xml, поддержка по умолчанию FormTtpmessageConverter IS MediaType.application_form_urlencoded и MediaPe.multipart_form_data_data_data_data_data_data_data_data_data_data_data_data_data_data_data_datepa_form. В службе отдыха больше всего мы используем, это картирование Jackson2httpmessageConverter. Это относительно общий преобразователь (унаследованный от интерфейса GenerichttpmessageConverter). Согласно анализу, миметип, который он поддерживает по умолчанию, является mediaType.application_json:
MappingJackson2httpmessageConverter
/*** Построить новый {@link MappingJackson2httpmessageConverter} с помощью пользовательского {@link objectmapper}. * Вы можете использовать {@link jackson2objectmapperbuilder}, чтобы легко его построить. *@see jackson2objectmapperbuilder#json () */ public mappingJackson2httpmessageConverter (ObjectMapper ObjectMapper) {super (ObjectMapper, mediaType.application_json, new MediaType ("Application", "+json")); }Тем не менее, ответ по умолчанию некоторых прикладных интерфейсов MimeType не является приложением/JSON. Например, если мы вызовым интерфейс прогноза внешней погоды, если мы используем конфигурацию по умолчанию RestTemplate, нельзя напрямую возвращать струнный ответ:
String url = "http://wthrcdn.etouch.cn/weather_mini?city=shanghay"; string result = resttemplate.getforobject (url, string.class); clientWeatherResultvo vo = serializeutil.deserialize (результат, ClientWeatherResultvo.class);
Однако, если мы хотим прямо вернуть объект объекта напрямую:
String url = "http://wthrcdn.etouch.cn/weather_mini?city=shanghay"; clientweatherresultvo weatherresultvo = resttemplate.getforobject (url, clientWeatherResultvo.class);
Затем сообщите о исключении напрямую:
Не удалось извлечь ответ: нет подходящего httpmessageconverter для типа ответа [класс]
и тип контента [приложение/октябрь]]
Многие люди столкнулись с этой проблемой. Большинство из них смущены, когда впервые сталкиваются с этим. Многие интерфейсы возвращаются в формате JSON, XML или простого текста. Что такое приложение/октет?
Проверьте исходный код Resttemplate и следите за ним все время, вы обнаружите, что метод ExtractData класса HttpmessageConverterExtractor имеет логику отклика и десериализации. Если это не успешное, информация об исключении брошенной, как и выше:
Httpmessageconverterextractor.extractdata
@Override @suppresswarnings ({"unchecked", "rawtypes", "resource"}) public t extractdata (clienthttpresponse response) throws ioexception {messagebodyclienthttpresponsewrapper responsewrapper = new MessageBodyCtttpResponseWrapper (response); if (! responsewrapper.hasmessagebody () || responsewrapper.hasemptymessagebody ()) {return null; } MediaType contentType = getContentType (responseWrapper); Попробуйте {for (httpmessageConverter <?> MessageConverter: this.messageConverters) {if (экземпляр MessageConverter of generichtpmessageConverter) {genionichttpmessageConverter <?> GenericMessAgeConverter = (generichtpmessageConverter <?> MSSSAGECECECECECECECECECECECECECECECECECECECECECECONVERTERTERTERTERTERTERTERTERTERTERTERSE) if (genericMessageConverter.canRead(this.responseType, null, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Reading [" + this.responseType + "] as /"" + contentType + "/" using [" + messageConverter + "]"); } return (t) genericMessageConverter.read (this.ResponseType, null, responsewrapper); }} if (this.ResponseClass! = null) {if (messageConverter.canread (this.ResponseClass, contentType)) {if (logger.isdebugenabled ()) {logger.debug ("choude [" + this.responseclass.getname () + "] as /" " + contenttype +" /" /" /" /" Занят. + "]"); } return (t) messageConverter.read ((class) this.Responseclass, responsewrapper); }}}} catch (ioexception | httpmessagenotreadableexception ex) {throw new RestClientException («Ошибка при извлечении ответа для типа [" + this.responseType + "] и типа контента [" + contentType + "]", ex); } бросить новое RestClientException («Не удалось извлечь ответ: без подходящего httpmessageConverter« + »для типа ответа [" + this.ResponseType + "] и типа контента [" + contentType + "]"); } Пример кода решения на Stackoverflow является приемлемым, но он не является точным. Обычные миметипы должны быть добавлены. Я опубликую код, я думаю, правильный:
RESTTEMPLATECONFIG
Пакет com.power.demo.restclient.config; import com.fasterxml.jackson.databind.objectmapper; import com.google.common.collect.lists; импорт org.springframework.beans.factory.nanotation.autowired; импорт org.spramework.boot.webient.resttat org.springframework.context.annotation.bean; import org.springframework.http.mediatype; import org.springframework.http.converter.*; import org.springframework.http.converter.cbor.mappingjackshon2cborhtpmessage.http.converter.cbor. org.springframework.http.converter.feed.atomfeedhttpmessageconverter; import org.springframework.http.converter.feed.rsschannelhttpmessageconverter; importmessecmessecmessecmessecmessecmessecmessecmessecmessecmessecmessecmessecmessecmess org.springframework.http.converter.json.jsonbhttpmessageConverter; import org.springframework.http.converter.json.mappingjackson2httpmessageConverter; import org.springframework.http.converter.smile.mappingjackson2smilehttpmessageConverter; import org.springframework.http.converter.support.allencompassingformhtpmessageConverter; import org.springframework.http.converter.xml.jaxb2rootelementhttpmessageconverter; import org.springframework.http.converter.xml.mappingJackson2xmlhtpmessageConverter; import org.springframework.http.converter.xml.sourcehttpmessageConverter; import org.springframework.stepyotyle.component; импорт org.springframework.util.classutils; импорт org.spramework.web.lient.restteplate; java.util.list; @componentpublic class resttemplateconfig {private static final boolean romepresent = classutils.ispresent ("com.rometools.rome.feed.wirefeed", resttemplate .class.getClassloader ()); Private Static Final Boolean jaxb2present = classutils.ispresent ("javax.xml.bind.binder", resttemplate.class.getClassloader ()); Частный статический финальный логический Boolean jackson2present = classutils.ispresent ("com.fasterxml.jackson.databind.objectmapper", resttemplate.class.getclassloader ()) && classutils.ispresent ("com.fasterxml.jackson.core.jsongenerator", restteplate. Частный статический финал Boolean jackson2xmlpresent = classutils.ispresent ("com.fasterxml.jackson.dataformat.xml.xmlmapper", resttemplate.class.getClassloader ()); Частный статический финальный логический Boolean jackson2smilepresent = classutils.ispresent ("com.fasterxml.jackson.dataformat.smile.smilefactory", resttemplate.class.getClassloader ()); Частный статический финал Boolean jackson2cborpresent = classutils.ispresent ("com.fasterxml.jackson.dataformat.cbor.cborfactory", resttemplate.class.getClassloader ()); Частный статический финал Boolean gsonpresent = classutils.ispresent ("com.google.gson.gson", resttemplate.class.getClassloader ()); Частный статический финал Boolean jsonbpresent = classutils.ispresent ("javax.json.bind.jsonb", resttemplate.class.getClassloader ()); // Обратите внимание, что при запуске этого, поскольку мы внедряем восстановление в службу, нам нужно создать экземпляр этого класса при запуске частного строителя Resttemplatebuilder; @Autowired Private ObjectMapper ObjectMapper; // Используйте ResttemplateBuilder для создания объекта RestTemplate. Весна внесла экземпляр ResttemplateBuilder по умолчанию @bean public resttemplate resttemplate () {resttemplate resttemplate = builder.build (); Список <httpmessageConverter <? >> messageConverters = lists.newarraylist (); MappingJackson2httpmessageConverter Converter = новый картирование Jackson2httpmessageConverter (); Converter.setObjectMapper (ObjectMapper); // Исключение появится без добавления // не может извлечь ответ: без подходящего httpmessageConverter для типа ответа [Class] MediaType [] mediaTypes = new MediaType [] {mediaType.application_json, mediaType.Application_octet_stream, mediaType.application_json_utf8, mediaTypepe.texteml_stream, mediaType.application_json_utf8, mediaTypepe.TextMl_SteTeM_STEM. MediaType.text_plain, mediaType.text_xml, mediaType.application_stream_json, mediaType.application_atom_xml, mediatype.application_form_urlencoded, mediatype.application_pdf,}; Converter.setSupportedMediatypes (Arrays.aslist (MediaTypes)); //messageconverters.add(converter); if (jackson2present) {messageconverters.add (конвертер); } else if (gsonpresent) {messageconverters.add (new gsonhttpmessageconverter ()); } else if (jsonbpresent) {messageconverters.add (new jsonbhttpmessageconverter ()); } messageConverters.Add (new FormhttpmessageConverter ()); MessageConverters.Add (new StringhttpmessageConverter ()); MessageConverters.Add (new StringhttpmessageConverter ()); MessageConverters.Add (новый ресурсхттпмессагейвертер (false)); MessageConverters.Add (New SourceHttpmessageConverter ()); MessageConverters.Add (новый AllencOmpassingFormhttpmessageConverter ()); if (romepresent) {messageconverters.add (new atomfeedhttpmessageconverter ()); MessageConverters.Add (новый rsschannelhttpmessageConverter ()); } if (jackson2xmlpresent) {messageconverters.add (new Mapting Jackson2xmlhttpmessageConverter ()); } else if (jaxb2present) {messageconverters.add (new jaxb2rootelementhttpmessageconverter ()); } if (jackson2smilepresent) {messageconverters.add (новый картирование Jackson2smilehttpmessageConverter ()); } if (jackson2cborpresent) {messageconverters.add (new MaptingJackson2cborhttpmessageConverter ()); } resttemplate.setMessageConverters (MessageConverters); вернуть RestTemplate; }}Просмотрев приведенный выше код и сравнивая внутреннюю реализацию Resttemplate, вы узнаете, что я сослался на исходный код Resttemplate. Люди, которые одержимы чистотой, могут сказать, что этот фрагмент кода немного условно. Вышеуказанная группа статических конечных переменных и MessageConverters заполняет методы данных, выявляют реализацию RestTemplate. Если Resttemplate будет изменен, он также будет изменен здесь, что очень недружелюбно и вообще не выглядит.
После анализа ResttemplateBuilder.build () строит объект RestTemplate. Просто измените поддерживаемый медиатип с помощью внутреннего картирования Jackson2httpmessageConverter. Хотя поле MessageConverters of Resttemplate является частным финалом, мы все равно можем изменить его посредством размышлений. Улучшенный код заключается в следующем:
RESTTEMPLATECONFIG
Пакет com.power.demo.restclient.config; import com.fasterxml.jackson.databind.objectmapper; import com.google.common.collect.lists; импорт org.springframework.beans.factory.nanotation.autowired; импорт org.spramework.boot.webient.resttat org.springframework.context.annotation.bean; import org.springframework.http.mediatype; import org.springframework.http.converter.httpmessageConverter; import.springframework.http.converter.json.maspyping.mappercembersecmessagmessagecmessecmessagecmessAgseCmessAgseC. org.springframework.stereotype.component; импорт org.springframework.web.client.resttemplate; import java.lang.reflect.field; импорт java.util.arrays; import java.util.list; импорт java.util.optional; import java.tream.collectors; RESTTTEMPLATECONFIG {// Примечание При запуске этого. Поскольку мы вводим RestTemplate в сервис, нам нужно создать экземпляр класса при запуске @Autowired Private RestTemplateBuilder Builder; @Autowired Private ObjectMapper ObjectMapper; // Используйте ResttemplateBuilder для создания объекта RestTemplate. Весна вводила экземпляр Resttemplate по умолчанию @bean public resttemplate resttemplate () {resttemplate resttemplate = builder.build (); Список <httpmessageConverter <? >> messageConverters = lists.newarraylist (); MappingJackson2httpmessageConverter Converter = новый картирование Jackson2httpmessageConverter (); Converter.setObjectMapper (ObjectMapper); // может возникнуть исключение, если нет добавления // не может извлечь ответ: без подходящего httpmessageConverter MediaType.application_stream_json, mediaType.application_atom_xml, mediaType.application_form_urlencoded, mediaType.application_json_utf8, mediaType.application_pdf,}; Converter.setSupportedMediatypes (Arrays.aslist (MediaTypes)); try {// установить MessageConverters через поле отражения поля = resttemplate.getClass (). getDeclaredfield ("MessageConverters"); Field.SetAccessible (true); Список <httpmessageConverter <? >> orgConverterList = (list <httpmessageConverter <? >>) Field.get (resttemplate); Необязательно <httpmessageconverter <? >> opconverter = orgconverterlist.stream () .filter (x -> x.getClass (). GetName (). if (opconverter.ispresent () == false) {return resttemplate; } messageConverters.Add (конвертер); // Добавить mappingJackson2httpmessageConverter // Добавить исходный оставшийся список httpmessageConverter <httpmessageConverter <? >> LeatsConverters = orgconverterlist.stream () .filter (x ->> x.getClass (). getName (). EqualSignoreCase (mappingJackson2httpmessageConverter.class .getName ()) == false) .collect (collectors.tolist ()); MessageConverters.addall (левые конвертерс); System.out.println (string.format ("【httpmessageConverter】 Оригинальная величина: %s, реконструированная величина: %s", orgconverterlist.size (), messageConverters.size ())); } catch (Exception e) {e.printstackTrace (); } resttemplate.setMessageConverters (MessageConverters); вернуть RestTemplate; }}Помимо поля MessageConverters, кажется, что мы больше не заботимся о пакетах внешних зависимостей и внутренних строительных процессах RestTemplate. Как и ожидалось, это очень чисто и кратко и легко поддерживать.
3. Проблема с кодом мусора
Это также очень классический вопрос. Решение очень простое, найдите httpmessageconverter и посмотрите на поддерживаемый по умолчанию charset. AbstractJackson2httpmessageConverter-это базовый класс для многих httpmessageconverter, а кодирование по умолчанию-UTF-8:
AbstractJackson2httpmessageConverter
Общественный абстрактный класс AbstractJackson2httpmessageConverter Extrable AbstractgenerichtpmessageConverter <object> {public static final charset default_charset = standardcharsets.utf_8;}StringhttpmessageConverter очень особенный. Некоторые люди сообщают, что искаженная проблема вызвана кодированием ISO-8859-1, которую она поддерживает по умолчанию:
StringhttpmessageConverter
/*** Реализация {@link httpmessageconverter}, которая может читать и записывать строки. * * <p> По умолчанию этот преобразователь поддерживает все типы носителей ({@code}), * и записывает с {@code content-type} {@code text/plain}. Это может быть переопределено * путем установки свойства {@link #setsupportedMediatypes поддерживает mediaTypes}. * * @author arjen poutsma * @author juergen hoeller * @since 3.0 */public class stringhttpmessageconverter extracthttpmessageconverter <string> {public static final charset default_charset = standardcharsets.iso_8859_1; /*** Конструктор по умолчанию, который использует {@code "iso-8859-1"} в качестве charset по умолчанию. * @see #StringHttpMessageConverter (charset) */ public stringhttpmessageConverter () {this (default_charset); }}Если во время использования возникает искаженная код, мы можем установить кодирование, поддерживаемое HTTPMessageConverter с помощью методов, обычно используемые включают UTF-8, GBK и т. Д.
4. исключение пустыней
Это еще одна проблема, с которой легко столкнуться в процессе разработки. Поскольку в Java так много фреймворков и инструментов с открытым исходным кодом, и версия часто меняется, часто возникают неожиданные ловушки.
Возьмите Joda Time в качестве примера. Joda Time - это популярная структура времени и даты Java, но если ваш интерфейс раскрывает тип времени Joda, такой как DateTime, то абонент интерфейса (изоморфные и гетерогенные системы) может столкнуться с проблемами сериализации и даже бросить следующие исключения непосредственно во время пустерилизации::
org.springframework.http.converter.httpmessageconversionexception: Ошибка определения типа: [простой тип, класс org.joda.time.chronology]; Вложенное исключением является com.fasterxml.jackson.databind.exc.invaliddefinitionException: не может построить экземпляр «org.joda.time.chronology` (нет создателей, например, по умолчанию, существует, есть дополнительная информация):
в [Источник: (PropbackInputStream);
Я столкнулся с этим на предыдущей фабрике, а затем, для удобства вызова, я перешел на тип даты, который непосредственно обнажил Java.
Конечно, есть больше, чем это решение. Вы можете использовать Джексона для поддержки сериализации и десериализации пользовательских классов. В системах с не очень высокими требованиями точности реализовать простой пользовательскую сериализацию DateTime:
DateTimeSerializer
пакет com.power.demo.util; import com.fasterxml.jackson.core.jsongenerator; import com.fasterxml.jackson.core.jsonprocessingexception; импорт com.fasterxml.jackson.databind.jsonserializ org.joda.time.datetime; import org.joda.time.format.datetimeformat; импорт org.joda.time.format.datetimeformatter; импорт java.io.ioexception;/*** По умолчанию Джексон будет сериализовать время Joda в более сложную форму, которая не способствует чтению и имеет более крупный объект. * <p>* При сериализации Jodatime, DateTime может быть сериализован в строку, которую легче читать **/открытый класс DateTimeSerializer Extends jsonserializer <DateTime> {Private Static DateTimeFormatter DateFormatter = DateTimeFormat.Forpattern ("yyyy-MM-DD HH: MM: ss"); @Override public void serialize (DateTime Value, JSongenerator JGen, SerializerProvider) Throws IOException, jsonProcessingException {jgen.Writestring (value.toString (dateFormatter)); }} И DateTime Deserialization:
Datetimedeserializer
пакет com.power.demo.util; import com.fasterxml.jackson.core.jsonparser; import com.fasterxml.jackson.core.jsonprocessingexception; import com.fasterxml.jackson.databind.deserializationContext; importM.FasterXml.jackson.databind.jsonserization context; imporm.fasterxml.jackson.databind.jsonserization; com.fasterxml.jackson.databind.jsonnode; import org.joda.time.datetime; import org.joda.time.format.datetimeformat; импорт org.joda.time.format.datetimeformatter; импорт java.io.ioexcept DateTimEdeserializer Extends JSondErializer <TATETIME> {Private Static DateTimeFormatter DateFormatter = DateTimeFormat.Forpattern ("yyyy-mm-dd hh: mm: ss"); @Override public DateTime deserialize (JSonParser JP, DeserializationContext Context) Throws IOException, jsonProcessingException {jsonNode node = jp.getCodec (). ReadTree (jp); String s = node.astext (); Datetime parse = datetime.parse (s, dateformatter); вернуть Parse; }}最后可以在RestTemplateConfig类中对常见调用问题进行汇总处理,可以参考如下:
RestTemplateConfig
package com.power.demo.restclient.config;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.module.SimpleModule;import com.google.common.collect.Lists;import com.power.demo.util.DateTimeSerializer;import com.power.demo.util.DatetimeDeserializer;import org.joda.time.DateTime;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.web.client.RestTemplateBuilder;import org.springframework.context.annotation.Bean;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;import org.springframework.stereotype.Component;import org.springframework.web.client.RestTemplate;import java.lang.reflect.Field;import java.util.Arrays;import java.util.List;import java.util.Optional;import java.util.stream.Collectors;@Componentpublic class RestTemplateConfig { // 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%的特殊情况,这估计也是满足常见的二八定律吧。
参考:
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
Выше всего содержание этой статьи. Я надеюсь, что это будет полезно для каждого обучения, и я надеюсь, что все будут поддерживать Wulin.com больше.