يمكننا تطوير واجهة REST بسرعة من خلال SPRING BOOT ، وقد نحتاج أيضًا إلى استدعاء واجهة الراحة الداخلية والخارجية من خلال SPRING BOOT لإكمال منطق العمل أثناء تنفيذ الواجهة.
في SPRING BOOT ، هناك طريقتان شائعتان لاتصال واجهات برمجة التطبيقات REST ، والتي يتم استخدامها لتنفيذ مكالمات الخدمة من خلال أداة RETTEMPLATE المدمجة أو تطوير أداة عميل HTTP الخاصة بك.
لدى RestTemplate وظائف أساسية قوية للغاية ، ولكن في بعض السيناريوهات الخاصة ، قد نكون أكثر اعتمادًا على استخدام فئات الأدوات المغلفة الخاصة بنا ، مثل تحميل الملفات على أنظمة الملفات الموزعة ، ومعالجة طلبات HTTPS مع الشهادات ، وما إلى ذلك.
تستخدم هذه المقالة RestTemplate كمثال لتسجيل العديد من المشكلات والحلول الموجودة أثناء استخدام RestTemplate للاتصال بالواجهة.
1. مقدمة في RestTemplate
1. ما هو resttemplate
عادةً ما يكون لدى HTTPClient الذي نغلفه أنفسنا بعض رمز القالب ، مثل إنشاء اتصال ، وبناء رأس طلب وجسم طلب ، ثم تحليل معلومات الاستجابة بناءً على الاستجابة ، وأخيراً إغلاق الاتصال.
RestTemplate هو إعادة تكوين HTTPClient في الربيع ، مما يبسط عملية بدء طلبات HTTP واستجابات المعالجة ، مع مستويات تجريد أعلى ، وتقليل رمز قالب المستهلك ، وجعل رمز أقل زائدة عن الحاجة.
في الواقع ، إذا كنت تفكر في العديد من فئات XXXTEMPLATE تحت SPRING BOOT ، فإنها توفر أيضًا طرقًا مختلفة من القالب ، ولكن مستوى التجريد أعلى ومزيد من التفاصيل مخفية.
بالمناسبة ، يحتوي Spring Cloud على Feign Call Service Service ، والذي يتم تنفيذه بناءً على Netflix Feign ، يدمج Spring Cloud Ribbon و Spring Cloud Hystrix ، وينفذ طريقة تعريف عميل خدمة الويب التعريفية.
في جوهرها ، تتمثل Feign في تغليفه مرة أخرى على أساس RestTemplate ، مما يساعدنا على تحديد وتنفيذ تعريف واجهات الخدمة التابعة.
2. الطرق المشتركة لـ RestTemplate
هناك العديد من طرق الطلب لخدمات الراحة المشتركة ، مثل GET ، POST ، PUT ، DELETE ، HEAD ، OPERSS ، إلخ. تنفذ RestTemplate الطريقة الأكثر شيوعًا ، والأكثر استخدامًا هي GET و POST. يمكنك الرجوع إلى رمز المصدر عند الاتصال بأبواج واجهة برمجة التطبيقات. فيما يلي بعض تعريفات الطريقة (الحصول ، بعد ، حذف):
طُرق
public <t> t getForObject (url string url ، class <t> responseType ، object ... urivariables) public <t> reponseentity <t> getForentity (string url ، class <t> responseType ، Object ... urivariabe) public <t> toforoBject postforentity (url url string ، @nullable request ، class <T> responseType ، Object ... urivariables) public void delete (url url ، object ... urivariables) public void delete (uri url)
في الوقت نفسه ، يجب أن تنتبه إلى طريقتان "مرنين" آخران تبادل وتنفيذ.
يختلف التبادل المكشوف بواسطة RestTemplate عن واجهات أخرى:
(1) اسمح للمتصل بتحديد طريقة طلب HTTP (Get ، Post ، Delete ، وما إلى ذلك)
(2) يمكن إضافة معلومات الجسم والرأس إلى الطلب ، ويتم وصف محتوىه بواسطة المعلمة "httpentity <؟> requestentity"
(3) يدعم Exchange "نوع المعلمات المحتوية" (أي الفئة العامة) كنوع إرجاع ، ويتم وصف هذه الميزة بواسطة "parameterizedtypereference <T> responsePe".
RestTemplate جميع GET ، POST والطرق الأخرى ، والدعوة النهائية لطريقة التنفيذ هي طريقة التنفيذ. يتمثل التنفيذ الداخلي لأسلوب Excute في تحويل URI-format ori إلى java.net.uri ، ثم تسمى طريقة doExecute. تنفيذ طريقة doExecute هو كما يلي:
DoExecute
/*** قم بتنفيذ الطريقة المحددة على URI المقدمة. * <p> تتم معالجة {link clienthttprequest} باستخدام {link requestCallback} ؛ * الاستجابة مع {link reviewExtractor}. * param url عنوان URL الذي تم توسيعه بالكامل للاتصال بـ * param طريقة http لتنفيذ (get ، post ، etc.) * param requestCallback كائن يستعد للطلب (يمكن أن يكون {code null}) {Link ResponseExtractor} */ nullable محمية <T> t doExecute (URI url ، @nullable httpmethod method ، @nullable requestCallback requestCallback ، @nullable responsextractor <T> reponsextor) RestClientException Assert.notnull (طريقة ، "طريقة" يجب ألا تكون فارغة ") ؛ clientHttpresponse استجابة = null ؛ حاول {clientHttPrequest request = createrequest (url ، method) ؛ if (requestCallback! = null) {requestCallback.dowithRequest (request) ؛ } استجابة = request.execute () ؛ المعالج (url ، الطريقة ، الاستجابة) ؛ if (responseExtractor! = null) {return reviewExtractor.extractData (response) ؛ } آخر {return null ؛ }} catch (ioException ex) {String Resource = url.toString () ؛ Query Query = url.getRawquery () ؛ Resource = (Query! = null؟ Resource.SubString (0 ، Resource.indexof ('؟')): Resource) ؛ رمي ResourceAccessException ("I/O خطأ على" + method.name () + "طلب/" " + Resource +"/":" + ex.getMessage () ، ex) ؛ } أخيرًا {if (response! = null) {response.close () ؛ }}}تغلف طريقة DoExecute طرق القالب ، مثل إنشاء اتصالات وطلبات وإجابات معالجة ، واتصالات الإغلاق ، إلخ.
عندما يرى معظم الناس هذا ، ربما يعتقدون أن تغليف RestClient مثل هذا تمامًا ، أليس كذلك؟
3. مكالمة بسيطة
قم بإجراء مكالمة منشورات كمثال:
goodsserviceClient
package com.power.demo.restclient ؛ import com.power.demo.common.appconst ؛ import com.power.demo.restclient.clientrequest.clientgoodsboodgoodsidsidsidsidsidsidsIdsIdsIdSidsIdsIdsIdsIdsIdSidseDsIdSidseDsIdSidseDsIdsIdsIdSidseDsIdSidseDseDsIdReseDsIdSidseDsIdReseDsIdSidsEdSidsEdSidseDseDsPonseDsponse org.springframework.beans.factory.annotation.autowired ؛ استيراد org.springframework.beans.factory.annotation.value **/ @componentpublic class classserviceClient {// عنوان URL للواجهة الذي يسمى المستهلك الخدمة هو كما يلي: http: // localhost: 9090 Value ("$ {spring.power.serviceurl}") سلسلة خاصة _serviceurl ؛ @autowired private resttemplate resttemplate ؛ ClientGetGoodsBoodsBoodsIdResponse getGoodsBygoodsid (ClientGetGoodsByGoodSidRequest request) {String svcurl = getGoodssvcurl () + "/getInfobyid" ؛ clientgetGoodsBygoodSidResponse استجابة = فارغة ؛ TREE {response = restTemplate.postForObject (SVCURL ، request ، clientgetGoodsByGoodSidResponse.class) ؛ } catch (استثناء e) {E.PrintStackTrace () ؛ الاستجابة = clientgetGoodsBoodSidSidResponse () ؛ استجابة. استجابة. } استجابة العودة ؛ } سلسلة خاصة 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/goods" ، _serviceurl) ؛ } آخر {url = string.format ("٪ s/api/v1/goods" ، _ServiceUrl) ؛ } URL Return ؛ }}في العرض التوضيحي ، تسمى طريقة restTemplate.PostForObject مباشرة ، ويتم تحويل كيان التخلص من التغليف الداخلي.
2. ملخص للمشاكل
1. لا يوجد httpmessageConverter مناسب لطلب استثناء نوع الطلب
تحدث هذه المشكلة عادةً عند تمرير كائن وارد في ما بعد الولادة للمكالمات.
تحليل رمز المصدر RestTemplate. في طريقة DowithRequest لفئة HttPentityRequestCallback ، إذا لم يستمر هذا الحقل في وقت لاحق) في وقت لاحق) ، فلا يفي بالمنطق الذي تم إرجاعه (أي أنه لا يوجد httpmessageconverter) ، يتم إلقاء الاستثناء أعلاه:
httpentityrequestcallback.dowithRequest
OverrideSuppressWarnings ("Unchecked") public void dowithRequest (clientHttprequest httprequest) يلقي ioException {super.dowithrequest (httprequest) ؛ Object 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 (intrade.getKey () ، new LinkedList <> (enter.getValue ())) ؛ }} if (httpheaders.getContentLength () <0) {httpheaders.setContEntLength (0l) ؛ }} آخر {class <؟> requestBodyClass = requestBody.getClass () ؛ اكتب requestbodyType = (this.requestentity extreative requertity؟ ((requestentity <؟>) this.requestentity) .getType (): requestBodyClass) ؛ httpheaders httpheaders = httprequest.getheaders () ؛ httpheaders requestHeaders = this.requestentity.getheaders () ؛ MediaType requestContentType = requestHeaders.getContentType () ؛ لـ (httpmessageConverter <؟> messageConverter: getMessageConverters ()) {if (messageConverter مثيل generichttpmessageConverter) {generichttpmessageConverter <Object> genericConverter = (generichtpmessageConverter <object>) messageConverter ؛ if (genericConverter.canwrite (requestbodyType ، requestBodyClass ، requestContentType)) {if (! requestHeaders.isempty ()) {for (map.entry <string ، list <string>) }} if (logger.isdebugenabled ()) {if (requestContentType! = null) {logger.debug ("الكتابة [" + requestbody + "] as /" + requestContEntType + " /" usiter [" + messageConverter +"] ؛ messageConverter + "]") ؛ : requestHeaders.EntrySet ()) {httpheaders.put (entry.getKey () ، LinkedList <> (Entry.getValue ())) ؛ [" + messageConverter +"] ") ؛ } آخر {logger.debug ("الكتابة [" + requestbody + "]") ؛ }} ((httpmessageConverter <Object>) messageConverter) .write (requestbody ، requestContentType ، httprequest) ؛ يعود؛ }} string message = "لا يمكن كتابة الطلب: لا يوجد httpmessageConverter مناسب مناسبة لنوع الطلب [" + requestBodyClass.getName () + "]" ؛ if (requestContentType! = null) {message + = "ونوع المحتوى [" + requestContentType + "]" ؛ } رمي RestClientException (Message) ؛ }} الحل الأسهل هو لف رأس طلب HTTP وتسلسل كائن الطلب في سلسلة لتمرير المعلمات. رمز العينة المرجعية كما يلي:
postforoBject
/ * * post request call * */ public static string postforoBject (restTemplate restTemplate ، url url ، url ، الكائن) {httpheaders headers = new httpheaders () ؛ mediaType type = mediaType.ParsemediAtype ("application/json ؛ charset = utf-8") ؛ headers.setContentType (type) ؛ headers.add ("قبول" ، mediaType.application_json.toString ()) ؛ سلسلة json = serializeutil.serialize (params) ؛ httpentity <string> formentity = new httpentity <string> (json ، headers) ؛ String result = restTemplate.postForObject (url ، formentity ، string.class) ؛ نتيجة العودة }إذا أردنا أيضًا إرجاع الكائن مباشرةً ، فيمكننا تمييز السلسلة التي تم إرجاعها مباشرة:
postforoBject
/ * * Post request Call * */ public static <T> t postforObject (restTemplate restTemplate ، url url ، params object ، class <t> clazz) {t stripte = null ؛ سلسلة repsstr = postforObject (restTemplate ، url ، params) ؛ الاستجابة = serializeUtil.Deserialize (RESPSTR ، clazz) ؛ استجابة العودة ؛ }من بينها ، هناك العديد من الأدوات للتسلسل والتسلسل ، مثل Fastjson و Jackson و Gson.
2. لا يوجد HTTPMESSAGECONVERTER مناسب لاستثناء نوع الاستجابة
مثلما يحدث استثناء عند بدء طلب ما ، ستكون هناك أيضًا مشاكل عند التعامل مع الاستجابة.
سأل أحدهم السؤال نفسه على Stackoverflow. السبب الجذري هو أن محول رسالة HTTP httpmessageconverter يفتقر إلى نوع MIME. بمعنى أنه عندما يقوم HTTP بنقل نتيجة الإخراج إلى العميل ، يجب على العميل بدء التطبيق المناسب لمعالجة مستند الإخراج. يمكن القيام بذلك من خلال أنواع MIME المتعددة (بروتوكول تكبير البريد عبر الإنترنت متعدد الوظائف).
بالنسبة لاستجابات من جانب الخادم ، يدعم العديد من httpmessageConverter أنواع الوسائط المختلفة (Mimetypes) افتراضيًا. الدعم الافتراضي لـ stringHttpMessageConverter هو mediaType.text_plain ، الدعم الافتراضي لـ sourcehttpmessageconverter هو mediaType.text_xml ، الدعم الافتراضي لـ formhttpmessageconverter is mediaType.application_form_urlencoded و mediape.mult.mult.mult.multipart_dordipart. في خدمة REST ، فإن أكثر ما نستخدمه هو MappingJackson2httpmessageConverter. ، هذا محول عام نسبيًا (موروثة من واجهة GenerichttpMessageConverter). وفقًا للتحليل ، فإن Mimetype التي يدعمها افتراضيًا هي MediaType.application_json:
MapPingJackson2httpmessageConverter
/*** إنشاء {link mappingjackson2httpmessageConverter} مع {link ObjectMapper}. * يمكنك استخدام {Link Jackson2ObjectMapperBuilder} لبناءه بسهولة. *seee jackson2ObjectMapperBuilder#json () */ public mappingjackson2httpmessageConverter (ObjectMapper ObjectMapper) {super (objectMapper ، mediaType.application_json ، new mediaType ("application" ، "،" *+json ")) ؛ }ومع ذلك ، فإن الاستجابة الافتراضية لبعض واجهات التطبيقات غير Application/JSON. على سبيل المثال ، إذا أطلقنا على واجهة تنبؤات الطقس الخارجية ، إذا استخدمنا التكوين الافتراضي لـ RestTemplate ، فلا توجد مشكلة في إرجاع استجابة السلسلة مباشرة:
String url = "http://wthrcdn.etouch.cn/weather_mini؟city=Shanghai" ؛
ومع ذلك ، إذا أردنا إعادة كائن كيان مباشرة:
String url = "http://wthrcdn.etouch.cn/weather_mini؟city=shanghai" ؛ celientweatherresultvo weatherresultvo = resttemplate.getForObject (url ، clientweherresultvo.class) ؛
ثم أبلغ عن استثناء مباشرة:
لا يمكن استجابة الاستجابة: لا يوجد HTTPMESSAGECONVERTER مناسب لنوع الاستجابة [الفئة]
ونوع المحتوى [التطبيق/ثماني الثماني]
لقد واجه الكثير من الناس هذه المشكلة. معظمهم مرتبكون عندما يواجهونها لأول مرة. يتم إرجاع العديد من الواجهات بتنسيق نص JSON أو XML أو النص العادي. ما هو التطبيق/الثماني الثامن؟
تحقق من رمز المصدر RestTemplate وتتبعه على طول الطريق ، ستجد أن طريقة extractdata لفئة HttpMessageConverterextractor لديها استجابة للاستجابة ومستحضر. إذا لم تنجح ، فإن معلومات الاستثناء التي تم طرحها هي نفسها أعلاه:
httpmessageconverterterextractor.extractData
OverRideSuppressWarnings ({"Unchected" ، "RawTypes" ، "Resource"}) T extractData (ClientHttpresponse Response) يلقي ioException {messageBodyClientHtpRespersionWrapper responseWrapper = new MessageBodyClientHtpresponswrapper (resprope) ؛ if (! responseWrapper.hasmessageBody () || responseWrapper.hasemptyMessageBody ()) {return null ؛ } mediaType contentType = getContentType (responseWrapper) ؛ جرب {for (httpmessageConverter <؟> messageconverter: this.messageConverters) {if (messageconverter مثيل generichttpmessageconverter) {generichttpmessageconverter <؟ if (generIcMessageConverter.canread (this.responsetype ، null ، contentType)) {if (logger.isdebugenabled ()) {logger.debug ("reading [ + this.responsetype +"] as /" + conventtype +" /"باستخدام [" + messageconverter + ") ؛ } return (t) genericmessageConverter.Read (this.responsetype ، null ، desponseWrapper) ؛ }} if (this.responseclass! = null) {if (messageConverter.CanRead (this.responseclass ، contentType)) {if (logger.isdeBugenabled ()) {logger.debug ("read [ + this.responseclass.getname () messageConverter + "]") ؛ } return (t) messageConverter.Read ((class) this. responseclass ، desponseWrapper) ؛ }}}} catch (ioException | httpmessagenotreadableexception ex) {رمي restClientException جديد ("خطأ أثناء استخراج الاستجابة للنوع [" + this.responsepe + "] ونوع المحتوى [" + contentTyType + "]" ، ex) ؛ } رمي RestClientException جديد ("لا يمكن استخراج الاستجابة: لا يوجد httpmessageConverter مناسب مناسبة" + "لنوع الاستجابة [" + this.responsetype + "] ونوع المحتوى [" + contentType + "]") ؛ } رمز عينة الحل على StackOverflow مقبول ، لكنه ليس دقيقًا. يجب إضافة محاكاة شائعة. سأقوم بنشر الرمز الذي أعتقد أنه صحيح:
RestTemplateConfig
package com.power.demo.restclient.config ؛ استيراد com.fasterxml.jackson.databind.objectmapper ؛ استيراد com.google.common.collect.lists ؛ استيراد org.springframework.beans.factory.annotation.autowired ؛ استيراد org.springframework.boot.web.client.restTemplateBuilder ؛ استيراد org.springframework.context.annotation.bean ؛ استيراد org.springframework.http.mediatepe ؛ استيراد org.springframework.http.converter. org.springframework.http.converter.cbor.mappingjackson2cborhttpmessageconverter ؛ استيراد org.springframework.http.converter.feed.atomfeedhtpmessageconverter ؛ استيراد org.springframework.http.converter.feed.rsschannelhttpmessageconverter org.springframework.http.converter.json.jsonbhttpmessageconverter ؛ استيراد org.springframework.http.converter.json.mappingjackson2htpmessageconverter ؛ استيراد org.springframework.http.converter.smile.mappingjackson2smilehttpmessageconverter org.springframework.http.converter.xml.jaxb2rootelementhttpmessageconverter org.springframework.http.converter.xml.sourcehttpmessageconverter ؛ استيراد org.springframework.stereotepee.component ؛ import org.springframework.util.classutils java.util.list ؛ componentpublic class resttemplateconfig {private static boolean romepresent = classutils.ispresent ("com.rometools.rome.feed.wirefeed" ، restTemplate .class.getClassLoader ()) ؛ 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 ()) 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 Boolean Gsonpresent = classUtils.ispresent ("com.google.gson.gson" ، resttemplate.class.getClassLoader ()) ؛ jsonbpresent نهائي ثابت jsonbpresent = classUtils.ispresent ("javax.json.bind.jsonb" ، restTemplate.Class.getClassLoader ()) ؛ // لاحظ عند بدء ذلك نظرًا لأننا حقن RestTemplate في الخدمة ، نحتاج إلى إنشاء مثيل لهذه الفئة عند بدء تشغيل Builder الخاص بـ AutWired. @autowired objectMapper ObjectMapper ؛ // استخدم RestTemplateBuilder لتشكيل كائن RestTemplate. حقن Spring مثيل RestTemplateBuilder بشكل افتراضي RestTemplate RestTemplate () {restTemplate RestTemplate = builder.build () ؛ قائمة <httpmessageConverter <؟ >> messageConverters = lists.newarraylist () ؛ MapPingJackson2httpMessageConverter Converter = جديد MapPingJackson2HttpMessageConverter () ؛ converter.setObjectMapper (ObjectMapper) ؛ // سيظهر الاستثناء دون إضافة // لا يمكن استخلاص الاستجابة: لا يوجد httpmessageConverter مناسب من نوع الاستجابة [class] mediaType [] MediaType.text_plain ، mediaType.text_xml ، mediaType.application_stream_json ، mediaType.application_atom_xml ، mediaType.application_form_urlencoded ، mediaType.application_pdf ،} ؛ Converter.SetSupportedMediatepes (Arrays.Aslist (MediaTypes)) ؛ //messageconverters.add(converter) ؛ if (Jackson2Present) {messageConverters.add (Converter) ؛ } آخر إذا (gsonpresent) {messageConverters.add (new gsonhttpmessageConverter ()) ؛ } آخر إذا (jsonbpresent) {messageConverters.add (new jsonbhttpmessageConverter ()) ؛ } messageconverters.add (formhttpmessageConverter ()) ؛ messageConverters.Add (New StringHttpMessageConverter ()) ؛ messageConverters.Add (New StringHttpMessageConverter ()) ؛ messageConverters.Add (New ResourceHttpMessageConverter (false)) ؛ messageConverters.Add (SourceHttpMessageConverter ()) ؛ messageConverters.Add (new AllenCompassingFormHttpMessageConverter ()) ؛ if (romepresent) {messageConverters.add (new AtomfeedHttpMessageConverter ()) ؛ messageConverters.Add (RsschannelhttpmessageConverter ()) ؛ } if (jackson2xmlpresent) {messageconverters.add (mappingjackson2xmlhttpmessageConverter ()) ؛ } آخر إذا (jaxb2present) {messageconverters.add (new jaxb2rootelementhttpmessageConverter ()) ؛ } if (Jackson2SmilePresent) {messageConverters.add (MapPingJackson2SmileHttpMessageConverter ()) ؛ } if (Jackson2CborPresent) {messageConverters.add (جديد MapPingJackson2CborHttpMessageConverter ()) ؛ } restTemplate.setMessageConverters (messageConverters) ؛ إرجاع resttemplate ؛ }}بعد رؤية الكود أعلاه ومقارنة التنفيذ الداخلي لـ RestTemplate ، ستعرف أنني أشرت إلى رمز المصدر لـ RestTemplate. قد يقول الأشخاص المهووسين بالنظافة أن قطعة الكود هذه مطوّلة بعض الشيء. تملأ المجموعة المذكورة أعلاه من المتغيرات النهائية الثابتة و MessageConverters طرق البيانات الكشف عن تنفيذ RestTemplate. إذا تم تعديل RestTemplate ، فسيتم تعديله أيضًا هنا ، وهو أمر غير ودي للغاية ولا يبدو OO على الإطلاق.
بعد التحليل ، يقوم RestTemplateBuilder.build () ببناء كائن RestTemplate. فقط قم بتعديل MediaType المدعوم مع MapPingJackson2HttpMessageConverter. على الرغم من أن مجال MessageConverters في RestTemplate هو نهائي خاص ، لا يزال بإمكاننا تعديله من خلال التفكير. الرمز المحسن كما يلي:
RestTemplateConfig
package com.power.demo.restclient.config ؛ استيراد com.fasterxml.jackson.databind.objectmapper ؛ استيراد com.google.common.collect.lists ؛ استيراد org.springframework.beans.factory.annotation.autowired ؛ استيراد org.springframework.boot.web.client.resttemplateBuilder ؛ استيراد org.springframework.context.annotation.bean ؛ استيراد org.springframework.http.mediatepe org.springframework.http.converter.json.mappingjackson2httpmessageConverter ؛ استيراد org.springframework.stereopee.component ؛ import org.springframework.web.client.resttemplate ؛ import java.lang.refelct.field ؛ java.util.list ؛ استيراد java.util.optional ؛ استيراد java.util.stream.collectors ؛ componentpublic class resttemplateConfig {// ملاحظة عند بدء ذلك لأننا حقن resttemplate @autowired objectMapper ObjectMapper ؛ // استخدم RestTemplateBuilder لتشكيل كائن RestTemplate. حقن Spring مثيل RestTemplate بشكل افتراضي RestTemplate RestTemplate () {restTemplate RestTemplate = builder.build () ؛ قائمة <httpmessageConverter <؟ >> messageConverters = lists.newarraylist () ؛ MapPingJackson2httpMessageConverter Converter = جديد MapPingJackson2HttpMessageConverter () ؛ converter.setObjectMapper (ObjectMapper) ؛ // قد يحدث الاستثناء إذا لم يتمكن أي إضافة // من استخراج الاستجابة: لا يوجد httpmessageConverter مناسب من نوع الاستجابة [class] mediaType [] MediaType.text_xml ، mediaType.application_stream_json ، mediaType.application_atom_xml ، mediaType.application_form_urlencoded ، mediaType.application_json_utf8 ، mediatype.application_pdf ،} ؛ Converter.SetSupportedMediatepes (Arrays.Aslist (MediaTypes)) ؛ حاول {// تعيين messageConverters من خلال حقل الانعكاس = restTemplate.getClass (). getDeclaredField ("messageConverters") ؛ Field.SetAccessible (صحيح) ؛ قائمة <httpmessageConverter <؟ >> orgConverterList = (list <httpmessageConverter <؟ >>) field.get (restTemplate) ؛ اختياري <httpmessageConverter <؟ >> opConverter = orgConverterList.stream () .filter (x -> x.getclass (). getName (). if (opConverter.ispresent () == false) {return restTemplate ؛ } messageConverters.add (Converter) ؛ // إضافة mappingjackson2httpmessageconverter // أضف قائمة httpmessageConverter الأصلية المتبقية الأصلية <httpmessageconverter <؟ X.GetClass (). getName (). equalsIngIsnoreCase (MapPingJackson2httpMessageConverter.class .getName ()) == خطأ) .collect (collectors.tolist ()) ؛ messageConverters.Addall (LeftConverters) ؛ System.out.println (string.format ("【httpmessageconverter】 الكمية الأصلية: ٪ s ، الكمية المعاد بناؤها: ٪ s" ، orgConverterList.size () ، messageconverters.size ())) ؛ } catch (استثناء e) {E.PrintStackTrace () ؛ } restTemplate.setMessageConverters (messageConverters) ؛ إرجاع resttemplate ؛ }}بصرف النظر عن حقل MessageConverters ، يبدو أننا لم نعد نهتم بحزم التبعية الخارجية وعمليات البناء الداخلية لـ RestTemplate. كما هو متوقع ، فهي نظيفة وموجزة وسهلة الصيانة.
3. مشكلة رمز القمامة
هذا أيضًا سؤال كلاسيكي للغاية. الحل بسيط للغاية ، ابحث عن httpmessageConverter وألقي نظرة على Charset الافتراضي المدعوم. Abstractjackson2httpmessageConverter هو فئة أساسية للعديد من httpmessageconverter ، والترميز الافتراضي هو UTF-8:
Abstractjackson2httpmessageConverter
الجمهور التجريبي الملخص jackson2httpmessageConverter يمتد الملخص agrectgenerichttpmessageConverter <object> {public static charset default_charset = standardcharsets.utf_8 ؛}StringHttpMessageConverter هو خاص جدا. أبلغ بعض الأشخاص أن المشكلة المشوهة ناتجة عن ترميز ISO-8859-1 الذي يدعمه افتراضيًا:
StringHttpMessageConverter
/*** تنفيذ {link httpmessageConverter} التي يمكنها قراءة الأوتار وكتابةها. * * <p> افتراضيًا ، يدعم هذا المحول جميع أنواع الوسائط ({code}) ، * ويكتب باستخدام {code content-type} من {code text/plain}. يمكن تجاوز هذا * عن طريق تعيين خاصية {link #SetSupportedMediTyPes PureDediDiatePes}. * * Author Arjen Poutsma * Author Juergen Hoeller * since 3.0 */فئة عامة stringhtttpmessageconverter يمتد ArgtracthTtpMessageConverter <string> {public static 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 إطارًا شائعًا للوقت والتاريخ Java ، ولكن إذا كشفت واجهتك عن نوع Joda ، مثل DateTime ، فإن متصل الواجهة (الأنظمة المتماثلة وغير المتجانسة) قد يواجه مشاكل التسلسل ، وحتى رمي الاستثناءات التالية مباشرة أثناء التخلص من ذلك:
org.springframework.http.converter.httpmessageConversionException: نوع خطأ في التعريف: [نوع بسيط ، فئة org.joda.time.chronology] ؛ استثناء متداخل هو com.fasterxml.jackson.databind.exc.invalidDefinitionException: لا يمكن بناء مثيل "org.joda.time
في [المصدر: (pushbackInputStream) ؛
لقد واجهت هذا في المصنع السابق ، وبعد ذلك ، لراحة الاتصال ، لقد قمت بالعودة إلى نوع التاريخ الذي كشف مباشرة جافا.
بالطبع ، هناك أكثر من هذا الحل. يمكنك استخدام Jackson لدعم التسلسل والخروج من الطبقات المخصصة. في الأنظمة التي لا تحتوي على متطلبات دقة عالية جدًا ، قم بتنفيذ التسلسل المخصص للوقت البسيط:
Datetimeserializer
package com.power.demo.util ؛ استيراد com.fasterxml.jackson.core.jsongenerator ؛ استيراد com.fasterxml.jackson.core.jsonprocessingexception ؛ import com.fasterxml.jackson.databind.jsonserializer org.joda.time.dateTime ؛ استيراد org.joda.time.format.dateTimeFormat ؛ استيراد org.joda.time.format.datetimeformatter ؛ الاستيراد java.io.ioException ؛/*** بشكل افتراضي ، سوف يسلط جاكسون وقتًا تجنيدًا في شكل أكثر تعقيدًا ، وهو لا يتراوح من القراءة ولديه كائن أكبر. * <p>* عند تسلسل jodatime ، يمكن تسلسل DateTime في سلسلة ، والتي من الأسهل قراءة **/فئة عامة datetimeserializer يمتد jsonserializer <TateTime> {private datetimeformatter dateFormatter = dateTimeFormat.forpattern ("Yyyy-mm-dd hh: mm: ss") ؛ Override public void Serialize (قيمة DateTime ، JSongenerator Jgen ، SerializerProvider) يلقي ioException ، JsonProcessingException {jgen.writeString (value.toString (dateFormatter)) ؛ }} و DateTime Deserialization:
datetimedeserializer
package com.power.demo.util ؛ استيراد com.fasterxml.jackson.core.jsonparser ؛ استيراد com.fasterxml.jackson.core.jsonprocessingexception ؛ import com.fasterxml.jackson.databind.deserialization ؛ com.fasterxml.jackson.databind.jsonnode ؛ import org.joda.time.dateTime ؛ import org.joda.time.format.datetimeformat DateTimedEserializer يمتد JSondeserializer <DateTime> {private dateTimeFormatter dateFormatter = dateTimeFormat.forpattern ("Yyyy-MM-DD HH: MM: SS") ؛ Override Public DateTime Deserialize (Jsonparser JP ، DeserializationContext Context) يلقي IoException ، JsonProcessingException {jsonnode node = jp.getcodec (). readtree (jp) ؛ سلسلة s = node.astext () ؛ DateTime parse = dateTime.parse (s ، dateFormatter) ؛ عودة التحليل }}أخيرًا ، يمكنك تلخيص مشكلات الاتصال الشائعة في فئة 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 (استثناء 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 أكثر.