Kami dapat dengan cepat mengembangkan antarmuka REST melalui Spring Boot, dan kami mungkin juga perlu memanggil antarmuka REST internal dan eksternal melalui Spring Boot untuk menyelesaikan logika bisnis selama implementasi antarmuka.
Di Spring Boot, ada dua cara umum untuk memanggil API REST, yang digunakan untuk mengimplementasikan panggilan layanan melalui resttemplate bawaan atau mengembangkan alat klien HTTP Anda sendiri.
RestTemplate memiliki fungsi dasar yang sangat kuat, tetapi dalam beberapa skenario khusus, kami mungkin lebih terbiasa menggunakan kelas alat yang dienkapsulasi kami sendiri, seperti mengunggah file ke sistem file terdistribusi, memproses permintaan HTTPS dengan sertifikat, dll.
Artikel ini menggunakan RestTemplate sebagai contoh untuk merekam beberapa masalah dan solusi yang ditemukan selama penggunaan RestTemplate untuk memanggil antarmuka.
1. Pengantar Resttemplate
1. Apa itu RestTemplate
HTTPClient kami merangkum diri kami biasanya memiliki beberapa kode template, seperti membuat koneksi, membangun header permintaan dan badan permintaan, kemudian menguraikan informasi respons berdasarkan respons, dan akhirnya menutup koneksi.
RESTTemplate adalah konteksulasi ulang httpClient di musim semi, yang menyederhanakan proses memulai permintaan HTTP dan respons pemrosesan, dengan tingkat abstraksi yang lebih tinggi, mengurangi kode templat konsumen, dan membuat kode yang lebih kecil.
Sebenarnya, jika Anda berpikir tentang banyak kelas XXXTEMPLATE di bawah Spring Boot, mereka juga menyediakan berbagai metode templat, tetapi tingkat abstraksi lebih tinggi dan lebih banyak detail disembunyikan.
Ngomong -ngomong, Spring Cloud memiliki Petchign Call Layanan Deklaratif, yang diimplementasikan berdasarkan Netflix Petchign, mengintegrasikan Spring Cloud Ribbon dan Spring Cloud Hystrix, dan mengimplementasikan metode definisi klien layanan web deklaratif.
Pada dasarnya, peti mula adalah merangkumnya lagi berdasarkan restemplate, yang membantu kita mendefinisikan dan mengimplementasikan definisi antarmuka layanan dependen.
2. Metode Umum untuk Resttemplate
Ada banyak metode permintaan untuk layanan istirahat umum, seperti GET, POST, HAPUS, DELETE, HEAD, OPTION, dll. RESTTemplate mengimplementasikan metode yang paling umum, dan yang paling umum digunakan adalah GET dan POST. Anda dapat merujuk ke kode sumber saat memanggil API. Berikut adalah beberapa definisi metode (dapatkan, posting, hapus):
metode
PUBLIK <T> t GetForObject (string url, class <T> ResponseType, Object ... Urivariable) Publik <T> Responentity <T> GetForEntity (string url, class <t> ResponseType, Object ... Urivariables) <t> PostForObject (String URL, @Blablable Object Request Objek, Class <t> THEPRESSETIPE, OBPACETICE, (TERPESPILES PUBLY) PUBLIK PUBLY (TERPADAPS (TERPADAP PUBLIK, Kelas, Kelas, Kelas, Kelas, Kelas, Class, Class <t> URL, @Nullable Object Request, Class <T> ResponseType, Object ... Urivariable) Public Void Delete (String URL, Object ... Urivariable) Public Void Delete (URI URL)
Pada saat yang sama, Anda harus memperhatikan dua metode "fleksibel" yang lebih "fleksibel".
Pertukaran yang diekspos oleh RestTemplate berbeda dari antarmuka lain:
(1) Izinkan penelepon untuk menentukan metode permintaan HTTP (dapatkan, posting, hapus, dll.)
(2) Informasi tubuh dan header dapat ditambahkan ke permintaan, dan isinya dijelaskan oleh parameter 'httpentity <?> Permintaan'
(3) Exchange mendukung 'tipe yang mengandung parameter' (mis. Kelas generik) sebagai tipe pengembalian, dan fitur ini dijelaskan oleh 'parameterisasiTypereference <T> responseType'.
RestTemplate semua dapatkan, posting dan metode lain, dan panggilan terakhir dari metode eksekusi adalah metode eksekusi. Implementasi internal dari metode ekstute adalah untuk mengubah URI format string menjadi java.net.uri, dan kemudian metode doexecute dipanggil. Implementasi metode doexecute adalah sebagai berikut:
doexecute
/*** Jalankan metode yang diberikan pada URI yang disediakan. * <p> {@link clientHttpRequest} diproses menggunakan {@link requestCallback}; * Respons dengan {@link responseExtractor}. * @param URL URL yang sepenuhnya diperluas untuk terhubung ke * @param Metode Metode HTTP untuk dieksekusi (Get, Post, dll.) * @param requestCallback objek yang menyiapkan permintaan (bisa {@code null}) * @param responsextract objek * ResponseExtractor} */ @nullable dilindungi <T> t doExecute (URI url, @nullable httpmethod metode, @nullable requestCallback requestCallback, @nullable responseExtractor <T> ResponseExtractor) melempar restclientException {assert.notnull (urlull (Urlull ("" "no no no no nozl ({Urll," " Assert.notnull (metode, "'metode' tidak boleh nol"); ClientHttpresponse response = null; coba {clientHttpRequest request = createRequest (url, metode); if (requestCallback! = null) {requestCallback.dowithRequest (request); } response = request.execute (); handleresponse (url, metode, respons); if (responseExtractor! = null) {return responseExtractor.extractData (respons); } else {return null; }} catch (ioException ex) {String Resource = url.toString (); String kueri = url.getRawQuery (); resource = (kueri! = null? resource.substring (0, resource.indexof ('?')): sumber daya); Lempar New ResourceAccessException ("I/O Error pada" + Method.name () + "Permintaan untuk/" " + Sumber Daya +"/":" + Ex.getMessage (), ex); } akhirnya {if (response! = null) {response.close (); }}}Metode DoExecute merangkum metode template, seperti membuat koneksi, pemrosesan permintaan dan jawaban, menutup koneksi, dll.
Ketika kebanyakan orang melihat ini, mereka mungkin berpikir bahwa merangkum restclient sama seperti ini, bukan?
3. Panggilan Sederhana
Ambil panggilan posting sebagai contoh:
GoodsserviceClient
package com.power.demo.restclient;import com.power.demo.common.AppConst;import com.power.demo.restclient.clientrequest.ClientGetGoodsByGoodsIdRequest;import com.power.demo.restclient.clientresponse.ClientGetGoodsByGoodsIdResponse;import org.springframework.beans.factory.annotation.Autowired; Impor org.springframework.beans.factory.annotation.value; produce org.springframework.stereotype.sponent; Impor org.springframework.web.client.RestemPlate; **/ @ComponentPublic Class GoodsServiceClient {// Url antarmuka yang dipanggil oleh konsumen layanan adalah sebagai berikut: http: // localhost: 9090 @value ("$ {spring.power.serviceurl}") string pribadi _serviceurl; @Autowired Private RestTemplate RestTemplate; Public ClientGetGoodSByGoodSidResponse GetGoodsByGoodSid (klienGetGoodSbyGoodSidRequest Request) {String svCurl = getGoodSSVCURL () + "/getInfobyid"; ClientgetgoodsbygoodsidResponse response = null; coba {response = restTemplate.postforObject (svcurl, request, clientgetgoodsbygoodsidresponse.class); } catch (Exception e) {E.PrintStackTrace (); respons = clientgetgoodsbygoodsidResponse () baru; response.setCode (appconst.fail); response.setMessage (e.tostring ()); } return response; } Private String getGoodSSVCURL () {string url = ""; if (_serviceUrl == null) {_serviceUrl = ""; } if (_serviceurl.length () == 0) {return url; } if (_serviceurl.substring (_serviceurl.length () - 1, _serviceurl.length ()) == "/") {url = string.format ("%sapi/v1/barang", _serviceurl); } else {url = string.format ("%s/api/v1/barang", _serviceUrl); } return url; }}Dalam demo, metode resttemplate.postforObject disebut secara langsung, dan entitas deserialisasi dikonversi menjadi enkapsulasi internal resttemplate ini.
2. Ringkasan Masalah
1. Tidak ada httpmessageConverter yang cocok untuk pengecualian tipe permintaan
Masalah ini biasanya terjadi ketika objek yang masuk dilewatkan di postforObject untuk panggilan.
Menganalisis kode sumber RestTemplate. Dalam metode DowithRequest dari kelas httpentityRequestCallback, jika MessageConverters (bidang ini akan terus disebutkan nanti) tidak memenuhi logika yang kembali melompat (yaitu, tidak ada HTTPMessageConverter yang cocok), pengecualian di atas dilemparkan:
HttpentityRequestCallback.dowithRequest
@Override @suppresswarnings ("Uncecked") public void dowithRequest (clientHttpRequest httpRequest) melempar ioException {super.dowithRequest (httpRequest); Objek 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 (), LinkedList baru <> (entry.getValue ())); }} if (httpheaders.getContentLength () <0) {httpheaders.setContentLength (0l); }} else {class <?> requestBodyClass = requestBody.getClass (); Type requestBodyType = (this.RequestEntity instance dari requestEntity? ((Permintaan <?>) This.RequestEntity) .getType (): RequestBodyClass); Httpheaders httpheaders = httpRequest.getheaders (); Httpheaders requestHeaders = this.RequestEntity.getHeaders (); Mediatype requestContentType = requestHeaders.getContentType (); untuk (httpmessageConverter <?> MessageConverter: getMessageConVerters ()) {if (MessageConverter dari GeneriChttpMessageConverter) {GeneriChTPMessAgeConverter <BOMPERT> GENICCONVERTER)) if (genericConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) { if (!requestHeaders.isEmpty()) { for (Map.Entry<String, List<String>> entry : requestHeaders.entrySet()) { httpHeaders.put(entry.getKey(), new LinkedList<>(entry.getValue())); } } if (logger.isDebugEnabled()) { if (requestContentType != null) { logger.debug("Writing [" + requestBody + "] as /" + requestContentType + "/" using [" + messageConverter + "]"); } else { logger.debug("Writing [" + requestBody + "] using [" + messageConverter + "]"); {httpheaders.put (entry.getKey (), new LinkedList <> (entry.getValue ())); "]"); } else {logger.debug ("menulis [" + requestbody + "]"); }} ((HttpmessageConverter <Papeat>) MessageConverter) .write (requestbody, requestContentType, httpRequest); kembali; }} String message = "tidak dapat menulis permintaan: tidak ada httpmessageConverter yang sesuai ditemukan untuk jenis permintaan [" + requestBodyClass.getName () + "]"; if (requestContentType! = null) {pesan + = "dan tipe konten [" + requestContentType + "]"; } lempar RestClientException baru (pesan); }} Solusi termudah adalah membungkus header permintaan HTTP dan membuat serial objek permintaan ke dalam string untuk meneruskan parameter. Kode sampel referensi adalah sebagai berikut:
PostforObject
/ * * POS POIN PERMINTAAN POST * */ PUBLIK PUBLIK POSTFOROBJECT (RESTTEMPLATE RESTTEMPLATE, STRING URL, PARAM OBYEK) {header httpheaders = httpheaders baru (); Mediatype type = mediatype.parsemediatype ("Application/JSON; charset = UTF-8"); header.setContentType (type); headers.add ("terima", mediatype.application_json.tostring ()); String json = serializeutil.serialize (params); Httpentity <string> formentity = httpentity baru <string> (json, header); Hasil string = restTemplate.postforObject (url, formentity, string.class); hasil pengembalian; }Jika kita juga ingin mengembalikan objek secara langsung, kita dapat secara langsung deserialize string yang dikembalikan:
PostforObject
/ * * POIN PERMINTAAN POST * */ PUBLIK PUBLIK <T> T POSTFOROBJECT (RESTTEMPLATE RESTTEMPLATE, URL String, Param Objek, Kelas <T> CLAZZ) {T Response = null; String respstr = postforObject (resttemplate, url, params); respons = serializeutil.deserialize (respstr, clazz); respons pengembalian; }Di antara mereka, ada banyak alat untuk serialisasi dan deserialisasi, seperti Fastjson, Jackson dan Gson.
2. Tidak ada httpmessageConverter yang cocok ditemukan untuk pengecualian jenis respons
Sama seperti pengecualian yang terjadi saat memulai permintaan, juga akan ada masalah saat menangani respons.
Seseorang mengajukan pertanyaan yang sama tentang Stackoverflow. Akar penyebabnya adalah bahwa konverter pesan http httpmessageConverter tidak memiliki jenis mime. Artinya, ketika HTTP mengirimkan hasil output ke klien, klien harus memulai aplikasi yang sesuai untuk memproses dokumen output. Ini dapat dilakukan melalui beberapa jenis MIME (multifungsi Internet Mail Protocol).
Untuk respons sisi server, banyak httpmessageConverter mendukung berbagai jenis media (mimetypes) secara default. Dukungan default untuk StringHttpMessageConverter adalah mediatype.text_plain, dukungan default untuk SourceHTTPMessageConverter adalah mediatype.text_xml, dukungan default untuk formhttpmessageConverter. Dalam layanan istirahat, yang paling kami gunakan adalah Mappingjackson2httpMessageConverter. , ini adalah konverter yang relatif umum (diwarisi dari antarmuka generichttpmessageConverter). Menurut analisis, mimetype yang didukung secara default adalah mediatype.application_json:
Mappingjackson2httpmessageConverter
/*** Bangun {@link MappingJackson2HttPMessageConverter {@link baru dengan {@link objectMapper} khusus. * Anda dapat menggunakan {@link Jackson2ObjectMapperBuilder} untuk membangunnya dengan mudah. *@see Jackson2ObjectMapperBuilder#json () */ Public Mappingjackson2HttpMessageConverter (ObjectMapper ObjectMapper) {Super (ObjectMapper, Mediatype.Application_json, MediaType baru ("Aplikasi", " *+JSON")); }Namun, respons default dari beberapa antarmuka aplikasi mimetype bukan aplikasi/json. Misalnya, jika kita memanggil antarmuka ramalan cuaca eksternal, jika kita menggunakan konfigurasi default restemplate, tidak masalah untuk secara langsung mengembalikan respons string:
String url = "http://wthrcdn.etouch.cn/weather_mini?city=shanghai";string hasil = resttemplate.getForObject (url, string.class); clientweatherResultVo vo = serializeutil.deserialize (hasil, klienweatherrerRvo vo = serializeutil.deserialize (hasil, clientweatreRerRVO vo = Serializeutil.deserialize (hasil, clientweatreRerRVO vo = Serializeutil.deserialize (hasil, clientweatreRerRVO vo = Serializeutil.deserialize (hasil, clientweatreRerresullVo = Serializeutil.deserialize (hasil, clientweatreRerRVO.
Namun, jika kita ingin mengembalikan objek entitas secara langsung:
String url = "http://wthrcdn.etouch.cn/weather_mini?city=shanghai";clientweatherresultVo WeatherResultVo = resttemplate.getForObject (url, clientweatherresultvo.class);
Kemudian laporkan pengecualian secara langsung:
Tidak dapat mengekstrak respons: Tidak ada httpmessageConverter yang cocok ditemukan untuk jenis respons [kelas]
dan tipe konten [aplikasi/oktet-stream]
Banyak orang mengalami masalah ini. Sebagian besar dari mereka bingung ketika mereka pertama kali menemukannya. Banyak antarmuka dikembalikan dalam format JSON, XML atau teks biasa. Apa itu Aplikasi/Octet-Stream?
Periksa kode sumber RestTemplate dan lacak sepanjang jalan, Anda akan menemukan bahwa metode ExtractData dari kelas HTTPMessageConVerTerExtractor memiliki respons parsing dan logika deserialisasi. Jika tidak berhasil, informasi pengecualian yang dilemparkan sama seperti di atas:
HttpmessageConVerTerExtractor.extractdata
@Override @suppressWarnings ({"Uncecked", "RawTypes", "Resource"}) public t ExtractData (ClientHttPresponse Response) Lempar IOException {MessageBodyCresWrapper (Response); Response; ResponsePonsewrapper; if (! ResponseWrapper.hasmessageBody () || responseWrapper.hasemptymessageBody ()) {return null; } Mediatype contentType = getContentType (responseWrapper); Coba {untuk (httpmessageConverter <?> MessageConverter: this.messageConVerters) {if (MessageConverter dari GeneriChttpMessageConverter) {generichtpMessageConverter <?> GenericMessageConverter = (generichtpMessageConverter <? if (genericMessageConverter.canRead (this.responsetype, null, contentType)) {if (logger.isdebugeNabled ()) {logger.debug ("bacaan [" + this.responsetype + "] sebagai /" " + ContentType +" /"menggunakan [" + Messagecon + "] sebagai /" " + ContentType +" /"Menggunakan [" + Messagecon + "] sebagai /" " +" + ContentType + " /" Menggunakan [" + MESSAGECEC +"] " +" + " +" /"Menggunakan [" + Messagecon "] sebagai /" " +" /"Menggunakan [" + Messagecon ") sebagai /" ") } return (t) genericMessageConverter.read (this.responsetype, null, responseewrapper); }} if (this.Responseclass! = null) {if (messageConverter.canRead (this.responseclass, contentType)) {if (logger.isdebugeNabled () {logger.debug ("baca [" + this.responseclass.getName () ["" "" "" "" "" + " +" "" MessageConverter + "]"); } return (t) messageConverter.read ((kelas) this.responseclass, responseWrapper); }}}} catch (ioException | httpmessagenotreadableException ex) {lempar baru restclientException ("Kesalahan sambil mengekstraksi respons untuk tipe [" + this.responsetype + "] dan tipe konten [" + contentType + "]", ex); } lempar RestClientException baru ("Tidak dapat mengekstrak respons: tidak ada httpmessageConverter yang cocok ditemukan" + "untuk jenis respons [" + this.responsetype + "] dan tipe konten [" + contentType + "]"); } Kode sampel solusi pada stackoverflow dapat diterima, tetapi tidak akurat. Mimetipe umum harus ditambahkan. Saya akan memposting kode yang menurut saya benar:
ResttemplateConfig
package com.power.demo.restclient.config;import com.fasterxml.jackson.databind.ObjectMapper;import com.google.common.collect.Lists;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.web.client.RestTemplateBuilder;import org.springframework.context.annotation.bean; impor org.springframework.http.mediatype; impor org.springframework.http.converter.*; import org.springframework.http.converter.cbor.mappappyckson2cborborhewework org.springframework.http.converter.feed.atomfeedHttpMessageConverter; impor org.springframework.http.converter.feed.rsschannelhtpmessageconverter; impor org org.springframework.http.converter.json.jsonbhttpmessageConverter; impor org.springframework.http.converter.json.mappingjackson2httpmessageconverter; impor org.springframework.http.converter.smile.mappingjackson2smilehttpMessageConverter; impor org.springframework.http.converter.support.allencingFormHttpMessAgeConverter; Impor org.springframework.http.converter.xml.jaxb2rootelementHttpMessageConverter; impor org.springframework.http.converter.xml.mappingjackson2xmlHtpmessageConVerter; impor org.springframework.http.converter.xml.sourcehttpMessageConverter; impor org.springframework.stereotype.sponent; impor org.springframework.util.classutils; impor org.springframework.weB.weRate.restle.ccliate. java.util.list; @ComponentPublic kelas 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 ()); private static final boolean jackson2present = classutils.ispresent ("com.fasterxml.jackson.databind.objectmapper", resttemplase.class.getClassLoader ()) && classutils.ispresent ("com.fasterxml.jackson.core.jsongenerenory", "com.fasterxml.jackson.core.jsongenerory", "com.fasterxml.jackson.core.jsongenerory", "com.fasterxml.jackson.core.jsongenerory" private static final boolean jackson2xmlpresent = classutils.ispresent ("com.fasterxml.jackson.dataformat.xml.xmlmapper", resttemplate.class.getClassLoader ()); private static final boolean jackson2smilepresent = classutils.ispresent ("com.fasterxml.jackson.dataformat.smile.smileFactory", resttemplate.class.getClassLoader ()); private static final boolean jackson2cborpresent = classutils.ispresent ("com.fasterxml.jackson.dataformat.cbor.cborfactory", resttemplate.class.getClassloader ()); private static final boolean gsonpresent = classutils.ispresent ("com.google.gson.gson", resttemplate.class.getClassLoader ()); private static final boolean jsonbpresent = classutils.ispresent ("javax.json.bind.jsonb", resttemplate.class.getClassLoader ()); // Perhatikan saat memulainya karena kami menyuntikkan restemplate ke dalam layanan, kita perlu instantiate instance dari kelas ini saat memulai @Autowired private restTemplateBuilder Builder; @Autowired Private ObjectMapper ObjectMapper; // Gunakan RestTemplateBuilder untuk membuat instantiate objek RestTemplate. Spring telah menyuntikkan instance RestTemplateBuilder secara default @Bean Public RestTemplate restTemplate () {resttemplate restTemplate = builder.build (); Daftar <httpmessageConverter <? >> MessageConVerters = lists.newarraylist (); MappingJackson2httpMessageConverter converter = MappingJackson2httpMessageConverter () baru; 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.APPLICATION_JSON_UTF8, MediaType.TEXT_HTML, MediaType.TEXT_PLAIN, Mediatype.text_xml, mediatype.application_stream_json, mediatype.application_atom_xml, mediatype.application_form_urlencoded, mediatype.application_pdf,}; converter.setsupportedmediatypes (arrays.aslist (mediatypes)); //messageConverters.add(converter); if (jackson2present) {messageConVertters.add (converter); } else if (gsonpresent) {messageConVertters.add (GSONHTTPMessageConverter ()) baru; } else if (jsonbpresent) {messageConVertters.add (baru jsonbhttpmessageConverter ()); } messageConVertters.add (formhttpmessageConverter baru ()); MessageConVerters.Add (StringHttPMessageConverter () baru ()); MessageConVerters.Add (StringHttPMessageConverter () baru ()); MessageConverters.Add (sumber daya baru HTTPMessageConverter (false)); MessageConverters.Add (SourceHTTPMessageConverter baru ()); MessageConverters.Add (allencompassingFormHttpMessageConverter ()) baru; if (romePresent) {messageConVertters.add (atomfeedHttpMessageConverter ()) baru; MessageConverters.Add (RSSChannelHttpMessageConverter () baru ()); } if (jackson2xmlpresent) {messageConVerters.add (mappingJackson2xmlhttpMessageConverter ()) baru; } else if (jaxb2present) {messageConVertters.add (baru jaxb2rooteLementHttpMessageConverter ()); } if (jackson2smilepresent) {messageConVertters.add (mappingJackson2smilehttpMessageConverter ()) baru; } if (jackson2cborpresent) {messageConVertters.add (mappingjackson2cborhttpmessageConverter ()) baru; } restTemplate.setMessageConverters (MessageConverters); return resttemplate; }}Setelah melihat kode di atas dan membandingkan implementasi internal RESTTemplate, Anda akan tahu bahwa saya telah merujuk pada kode sumber RESTTemplate. Orang -orang yang terobsesi dengan kebersihan dapat mengatakan bahwa sepotong kode ini agak bertele -tele. Kelompok variabel akhir statis dan MessageConverters yang di atas mengisi metode data mengekspos implementasi RestTemplate. Jika resttemplate dimodifikasi, itu juga akan dimodifikasi di sini, yang sangat tidak ramah dan tidak terlihat sama sekali.
Setelah analisis, resttemplateBuilder.build () membangun objek resttemplate. Cukup modifikasi mediatype yang didukung dengan pemetaan internal jackson2httpmessageConverter. Meskipun bidang MessageConverters dari RestTemplate adalah final pribadi, kami masih dapat memodifikasinya melalui refleksi. Kode yang ditingkatkan adalah sebagai berikut:
ResttemplateConfig
package com.power.demo.restclient.config;import com.fasterxml.jackson.databind.ObjectMapper;import com.google.common.collect.Lists;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.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; impor org.springframework.web.client.resttemplate; impor java.lang.reflect.field; impor java.util.arrays; impor java.util.list; impor java.util.optional; Imporpl clutivent;@OvaMorvor; RESTTempLateConfig {// Catatan Saat memulainya karena kami menyuntikkan restemplate ke dalam layanan, kami perlu membuat instance dari kelas saat memulai @Autowired private restTemplateBuilder builder; @Autowired Private ObjectMapper ObjectMapper; // Gunakan RestTemplateBuilder untuk membuat instantiate objek RestTemplate. Spring telah menyuntikkan instance RestTemplate secara default @Bean Public RestTemplate restTemplate () {resttemplate restTemplate = builder.build (); Daftar <httpmessageConverter <? >> MessageConVerters = lists.newarraylist (); MappingJackson2httpMessageConverter converter = MappingJackson2httpMessageConverter () baru; converter.setObjectMapper (ObjectMapper); // Pengecualian dapat terjadi jika tidak ada penambahan // tidak dapat mengekstraksi respons: tidak ada httpmessageConverter yang cocok untuk jenis respons [kelas] mediatype [] mediatypes = mediatype baru [] {mediatype.application_json, mediatype.application_octet_stream, mediatype.application_html, mediatype. Mediatype.application_stream_json, mediatype.application_atom_xml, mediatype.application_form_urlencoded, mediatype.application_json_utf8, mediatype.application_pdf,}; converter.setsupportedmediatypes (arrays.aslist (mediatypes)); Coba {// atur MessageConverters melalui bidang refleksi bidang = restTemplate.getClass (). GetDeclaredfield ("MessageConVertters"); field.setAccessible (true); Daftar <httpmessageConverter <? >> orgConverterlist = (daftar <httpmessageConverter <? >>) field.get (restTemplate); Opsional <httpmessageConverter <? >> opconverter = orgConverterlist.stream () .filter (x -> x.getClass (). GetName (). EqualSignorecase (mappingjackson2httpmessageconter.class .getName ()). if (opconverter.ispresent () == false) {return restemplate; } MessageConVertters.Add (converter); // Tambahkan MappingJackson2HttpMessageConverter // Tambahkan daftar httpmessageConverter yang tersisa <httpmessageConverter <? >> LeftConverters = orgConverterlist.stream () .filter <? x.getClass (). getName (). EqualSignorecase (MappingJackson2httpMessageConverter.class .getName ()) == false) .collect (collectors.tolist ()); MessageConverters.addall (LeftConverters); System.out.println (string.format ("【httpmessageConverter】 Jumlah asli: %s, kuantitas yang direkonstruksi: %s", orgconverterlist.size (), messageConverters.size ())); } catch (Exception e) {E.PrintStackTrace (); } restTemplate.setMessageConverters (MessageConverters); return resttemplate; }}Terlepas dari bidang MessageConverters, tampaknya kita tidak lagi peduli dengan paket ketergantungan eksternal dan proses konstruksi internal RestTemplate. Seperti yang diharapkan, ini jauh bersih dan ringkas dan mudah dipelihara.
3. Masalah kode sampah
Ini juga pertanyaan yang sangat klasik. Solusinya sangat sederhana, temukan httpmessageConverter dan lihat charset yang didukung default. Abstractjackson2httpmessageConverter adalah kelas dasar bagi banyak httpmessageConverter, dan pengkodean default adalah UTF-8:
Abstractjackson2httpmessageConverter
Publik Abstrak Kelas Abstrak Jackson2httpMessageConverter memperluas AbstractGeneriChTTPMessageConverter <BOJPAT> {public static final charset default_charset = standardcharsets.utf_8;}StringHttpMessageConverter cukup istimewa. Beberapa orang telah melaporkan bahwa masalah kacau disebabkan oleh pengkodean ISO-8859-1 yang didukung secara default:
StringHttpMessageConverter
/*** Implementasi {@link httpmessageConverter} yang dapat membaca dan menulis string. * * <p> Secara default, konverter ini mendukung semua tipe media ({@code}), * dan menulis dengan {@code konten-tipe} dari {@code Text/Plain}. Ini dapat diganti * dengan mengatur properti {@link #setsupportedMediatypes}. * * @author arjen poutsma * @author juergen hoeller * @since 3.0 */kelas publik stringHttpMessageConverter memperluas abstracthttpmessageConverter <string> {public static charset default_charset = standardcharsets.iso_8859_1; /*** Konstruktor default yang menggunakan {@code "ISO-8859-1"} sebagai charset default. * @see #stringHttpMessageConverter (charset) */ public stringHttpMessageConverter () {this (default_charset); }}Jika kode kacau terjadi selama penggunaan, kami dapat mengatur pengkodean yang didukung oleh httpmessageConverter melalui metode, yang umum digunakan termasuk UTF-8, GBK, dll.
4. Pengecualian deserialisasi
Ini adalah masalah lain yang mudah ditemui selama proses pengembangan. Karena ada begitu banyak kerangka kerja dan alat open source di Java dan versi sering berubah, jebakan yang tidak terduga sering terjadi.
Luangkan waktu Joda sebagai contoh. Joda Time adalah kerangka kerja java waktu dan tanggal yang populer, tetapi jika antarmuka Anda memperlihatkan jenis waktu Joda, seperti datetime, maka penelepon antarmuka (sistem isomorfik dan heterogen) dapat menghadapi masalah serialisasi, dan bahkan melemparkan pengecualian berikut langsung selama deserialisasi:
org.springframework.http.converter.httpMessageConVersionException: Type Definition Error: [Tipe Sederhana, kelas org.joda.time.chronology]; Pengecualian bersarang adalah com.fasterxml.jackson.databind.exc.invaliddefinitionException: tidak dapat membuat contoh dari `org.joda.time.chronology` (tidak ada pencipta, seperti konstruk default, atau ada tipe tambahan yang perlu dipetakan untuk tipe -tipe beton, memiliki deserizise kustom, atau bersaing dengan informasi tambahan khusus, memiliki informasi tambahan khusus, memiliki informasi tambahan khusus, memiliki informasi tambahan khusus, memiliki informasi tambahan khusus, memiliki informasi tambahan kustom, atau bersaing dengan tipe deserizer kustom, atau contains.
di [Sumber: (PushbackInputStream);
Saya menemukan ini di pabrik sebelumnya, dan kemudian, untuk kenyamanan menelepon, saya berubah kembali ke jenis tanggal yang secara langsung mengekspos Java.
Tentu saja, ada lebih dari solusi ini. Anda dapat menggunakan Jackson untuk mendukung serialisasi dan deserialisasi kelas khusus. Dalam sistem dengan persyaratan akurasi yang tidak terlalu tinggi, terapkan Serialisasi Kustom Datetime sederhana:
Datetimeserializer
package 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.SerializerProvider;import org.joda.time.DateTime;import org.joda.time.format.DateTimeFormat;import org.joda.time.format.DateTimeFormatter;import java.io.IOException;/** * By default, jackson will serialize joda time into a more complex form, which is not conducive to reading and has a larger object. * <p>* Saat membuat serialisasi jodatime, datetime dapat diserialisasi menjadi string, yang lebih mudah dibaca **/kelas publik datetimeserializer memperluas jsonserializer <dateTime> {private static DateTimeformatter DateFormatter = dateTimeMat.forpattern ("yyyyy-mm: mm: mm: mm: sp: sse"); @Override public void serialize (nilai datetime, jsongenerator jgen, penyedia serializerProvider) melempar ioException, jsonprocessingException {jGen.writeString (value.toString (DateFormatter)); }} Dan deserialisasi datetime:
DateTimedeserializer
Paket com.power.demo.util; import com.fasterxml.jackson.core.jsonparser; import com.fasterxml.jackson.core.jsonprocessingException; import com.fasterxml.jackson.databind.deserializationContext; impor com.fasterxml.jackson.databind.deserializationcext; impor com.fasterxmlercaster com.fasterxml.jackson.databind.jsonnode; impor org.joda.time.datetime; impor org.joda.time.format.datetimeformat; impor org.joda.time.format.datetimeFormatter; impor java.io.ioException;/** formating; Datetimedeserializer memperluas jsondeserializer <datetime> {private static DateTimEformatter DateFormatter = datetimeformat.forpattern ("yyyy-mm-dd hh: mm: ss"); @Override Public DateTime Deserialize (JSONParser JP, DeserializationContext Context) melempar IOException, JsonProcessingException {JSonnode node = jp.getCodec (). Readtree (JP); String s = node.astext (); Datetime parse = datetime.parse (s, dateFormatter); mengembalikan parse; }}Akhirnya, Anda dapat merangkum masalah panggilan umum di kelas resttemplateconfig, dan Anda dapat merujuknya sebagai berikut:
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%的特殊情况,这估计也是满足常见的二八定律吧。
Lihat:
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
Di atas adalah semua konten artikel ini. Saya berharap ini akan membantu untuk pembelajaran semua orang dan saya harap semua orang akan lebih mendukung wulin.com.