Spring Bootを介してRESTインターフェイスをすばやく開発できます。また、インターフェイスの実装中にビジネスロジックを完了するには、Spring Bootを介して内部および外部RESTインターフェイスを呼び出す必要がある場合があります。
Spring Bootには、Rest APIを呼び出す2つの一般的な方法があります。これは、組み込みのRestemplateを介してサービスコールを実装するか、独自のHTTPクライアントツールを開発するために使用されます。
RestTemplateには非常に強力な基本機能がありますが、いくつかの特別なシナリオでは、分散ファイルシステムにファイルをアップロードする、証明書を使用してHTTPSリクエストを処理するなど、独自のカプセル化されたツールクラスを使用することに慣れている場合があります。
この記事では、RestTemplateを例として使用して、RestTemplateを使用してインターフェイスを呼び出す際に見つかったいくつかの問題とソリューションを記録します。
1。RestTemplateの紹介
1。レストテンプレートとは何ですか
私たちがカプセル化するhttpClientには、通常、接続の確立、要求ヘッダーとリクエスト本体の構築、応答に基づいて応答情報を解析し、最終的に接続を閉じるなど、いくつかのテンプレートコードがあります。
RESTTEMPLATEは、春のHTTPClientの再エンコール化であり、HTTP要求と処理応答の開始プロセスを簡素化し、抽象化レベルが高く、消費者テンプレートコードの削減、冗長コードの作成が少なくなります。
実際、Spring Bootの下で多くのXXXTEMPLATEクラスについて考えると、さまざまなテンプレートメソッドも提供しますが、抽象化レベルはより高く、詳細は隠されています。
ちなみに、Spring Cloudには、Netflix Peignに基づいて実装され、Spring Cloud RibbonとSpring Cloud Hystrixを統合し、宣言的なWebサービスクライアント定義方法を実装している宣言的サービスコールフェインがあります。
本質的に、feignは、レストテンプレートに基づいて再びカプセル化することです。これは、従属サービスインターフェイスの定義を定義および実装するのに役立ちます。
2。レストテンプレートの一般的な方法
Get、Post、Put、Delete、Head、Optionsなど、一般的なレストサービスには多くのリクエスト方法があります。レストテンプレートは最も一般的な方法を実装し、最も一般的に使用されるのはGETと投稿です。 APIを呼び出すときにソースコードを参照できます。いくつかのメソッド定義を次に示します(GET、投稿、削除):
方法
public <t> getForObject(string url、class <t> responseType、object ... urivariables)public <t> responseNtity <t> getForentity(string url、class <t> responseType、object ... urivariables)public <t> postforObject(string url、@nullable object、class <t> ResponseTepe、urivariables postforentity(string url、@nullableオブジェクト要求、クラス<t> responseType、object ... urivariables)public void delete(string url、object ... urivariables)public void delete(uri url)
同時に、さらに2つの「柔軟な」メソッドの交換と実行に注意を払う必要があります。
RestTemplateによって公開された交換は、他のインターフェイスとは異なります。
(1)発信者がHTTP要求の方法を指定できるようにします(取得、投稿、削除など)
(2)ボディとヘッダーの情報はリクエストに追加でき、そのコンテンツはパラメーター「httpentity <?> requestentity」によって説明されます。
(3)交換サポート「パラメーターを含むタイプ」(すなわち、一般的なクラス)は戻り型として、この機能は「parameterizedtypereference <t> responsetype」で説明されています。
RESTTEMPLATEはすべて取得、投稿、その他のメソッド、および実行方法の最終呼び出しは実行メソッドです。 Excuteメソッドの内部実装は、文字列format URIをjava.net.uriに変換することであり、DoExecuteメソッドが呼び出されます。 DoExecuteメソッドの実装は次のとおりです。
doexecute
/***提供されたURIで指定されたメソッドを実行します。 * <p> {@link clienthttprequest}は、{@link requestcallback}を使用して処理されます。 * {@link responsextractor}を使用した応答。 * @param url完全拡張URLに接続する完全拡張URL * @paramメソッドhttpメソッドを実行する(get、postなど) {@Link ResponseExtractor} */ @nullable Protected <t> t doexecute(uri url、@nullable httpmethodメソッド、 @nullable requestcallback requestcallback、@nullable responsextractor <t> responsextractor)スローRestClientException {assert.notnull(url ") assert.notnull(method、 "'method'はnullでなければなりません"); clienthttpresponse応答= null; try {clienthttprequest request = createrequest(url、method); if(requestCallback!= null){requestCallback.DowithRequest(request); } response = request.execute(); Handleresponse(url、method、response); if(responsextractor!= null){repurn responsextractor.extractdata(response); } else {return null; }} catch(ioException ex){string resource = url.toString();文字列query = url.getRawQuery(); resource =(query!= null?resource.substring(0、resource.indexof( '?')):resource);新しいresourceaccessexception( " + method.name() +" lequest for/"" + resource + "/": " + ex.getMessage()、ex); }最後に{if(response!= null){respons.close(); }}}DoExecuteメソッドは、接続の作成、リクエストと回答の処理、接続の閉鎖などのテンプレートメソッドをカプセル化します。
ほとんどの人がこれを見るとき、彼らはおそらく、休憩クライアントをカプセル化することはまさにこのようなものだと思いますよね?
3。簡単な呼び出し
例として、郵便電話をかけてください。
GoodsServiceClient
パッケージcom.power.demo.restclient; Import com.power.demo.common.appconst; Import com.power.demo.restclient.clientRequest.clientgoodsbygoodsidrequest; Import com.power.demo.restclient.clientresponse. org.springframework.beans.factory.annotation.autowired; Import org.springframework.beans.factory.annotation.value; Import org.springframework.stereotype.component; Import org.springframework.web.web.client.restemplate; goodsserviceclient {//サービスコンシューマーが呼び出すインターフェイスURLは次のとおりです。http:// localhost:9090 @value( "$ {spring.power.serviceurl}")private string _serviceurl; @autowired privateRestemplateStemplate; public clientgegoodsbygoodsidResponse getGoodsbygoodsid(clientGetGoodSbyGoodSidRequest request){string svcurl = getGoodsSVCurl() + "/getInfobyid"; clientGetGoodSBYGOODSIDRESPONSE Response = null; try {Response = retttemplate.postforobject(svcurl、request、clientgoodsbygoodsidresponse.class); } catch(Exception e){e.printstacktrace(); response = new ClientGetGoodSbyGoodSidResponse(); Response.setCode(appconst.fail); Response.setMessage(e.toString()); }返信応答; } 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/goods"、_ serviceurl); } else {url = string.format( "%s/api/v1/goods"、_serviceurl); } return url; }}デモでは、reptemplate.postforobjectメソッドが直接呼び出され、脱派化エンティティがこれらのレストテンプレート内部カプセル化に変換されます。
2。問題の概要
1.リクエストタイプの例外には適切なhttpmessageconverterが見つかりません
この問題は通常、コールのために受信オブジェクトがPostforObjectに渡されるときに発生します。
RESTTEMPLATEソースコードを分析します。 httpentityRequestCallbackクラスのDowithRequestメソッドでは、MessageConverters(このフィールドが後で言及され続ける)が、戻ってきたロジックを満たしていない場合(つまり、httpmessageconverterに一致するものはありません)、上記の例外はスローされます。
httpentityRequestCallback.dowithRequest
@override @suppresswarnings( "unchecked")public void dowithrequest(clienthttprequest httprequest)throws 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(entry.getKey()、new linkedlist <>(entry.getValue()); }} if(httpheaders.getContentLength()<0){httpheaders.setContentLength(0L); }} else {class <?> requestBodyClass = requestBody.getClass();タイプrequestBodyType =(this.requestentity instance of requestentity? httpheaders httpheaders = httprequest.getheaders(); httpheaders requestheaders = this.requestentity.getheaders(); mediatype requestContentType = requestHeaders.getContentType(); (httpmessageconverter <? if(genericconverter.canwrite(requestbodytype、requestbodyclass、requestcontenttype)))){if(!requestheaders.isempty()){for(map.entry <string、list <string >> entry:requestheaders.entryset(){httpheaders.put(entry.getValue(); new linkedlist <>(); }} if(logger.isdebugenabled()){if(requestContentType!= null){logger.debug( "writing [" + requestbody + "] as /" + requestContentType + " /" [" + messageconverter +"] "); MessageConverter + "]; requestheaders.entryset()){httpheaders.put(entry.getKey()、new linkedlist <>(entry.getValue()); [" + MessageConverter +"] "); } else {logger.debug( "writing [" + requestbody + "]"); }}((httpmessageconverter <Object>)mesageConverter).write(requestbody、requestContentType、httprequest);戻る; }} string message = "requestを書き込むことができませんでした:リクエストタイプに見つかった適切なhttpmessageconverterはありません[" + requestbodyclass.getname() + "]"; if(requestContentType!= null){message + = "およびcontent type [" + requestContentType + "]"; }新しいRESTCLIENTEXCEPTION(メッセージ); }}最も簡単な解決策は、HTTP要求ヘッダーをラップし、リクエストオブジェクトを文字列にシリアル化してパラメーターを渡すことです。参照サンプルコードは次のとおりです。
PostforObject
/ * *要求リクエストコール * */ public static string postforobject(rettemplate rettemplate、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、headers); string result = resttemplate.postforobject(url、formentity、string.class);返品結果; }また、オブジェクトを直接返却したい場合は、返された文字列を直接ゆるくすることができます。
PostforObject
/ * *リクエストコールを投稿 * */ public static <t> t postforobject(rettemplate rettemplate、string url、object params、class <t> clazz){t response = null; string respstr = postforobject(rettemplate、url、params); Response = serializeutil.deserialize(respstr、clazz);返信応答。 }その中には、FastJson、Jackson、GSONなど、シリアル化と脱登りのための多くのツールがあります。
2.応答タイプの例外には、適切なhttpmessageconverterが見つかりません
リクエストを開始するときに例外が発生するように、応答を処理するときにも問題が発生します。
誰かがStackoverflowで同じ質問をしました。根本的な原因は、HTTPメッセージコンバーターhttpmessageconverterにMIMEタイプがないことです。つまり、HTTPが出力結果をクライアントに送信する場合、クライアントは出力ドキュメントを処理するために適切なアプリケーションを開始する必要があります。これは、複数のMIME(多機能インターネットメール増強プロトコル)タイプを介して実行できます。
サーバー側の応答の場合、多くのHTTPMessageConverterは、デフォルトでさまざまなメディアタイプ(マイメタイプ)をサポートしています。 stringhttpmessageconverterのデフォルトサポートはmediatype.text_plainです。sourcehttpmessageconverterのデフォルトサポートはmediatype.text_xmlです。 RESTサービスでは、私たちが使用するほとんどはMappingJackson2HTTPMessageConverterです。 、これは比較的一般的なコンバーターです(GenerichttpmessageConverterインターフェイスから継承)。分析によると、デフォルトでサポートするMIMETYPEはmediatype.application_jsonです。
mappingjackson2httpmessageconverter
/*** custom {@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=shanghai"; string result = retttemplate.getforobject(url、string.class); clientweatherreresultvo vo = serializeutil.deserialize(result、client weatherersultvo.class);
ただし、エンティティオブジェクトを直接返したい場合:
string url = "http://wthrcdn.etouch.cn/weather_mini?city=shanghai"; clientweatherreresultvo weatherreresultvo = retttemplate.getforobject(url、clientweatherreresultvo.class);
次に、例外を直接報告します。
応答を抽出できませんでした:応答タイプに見つかった適切なhttpmessageconverter [class]
コンテンツタイプ[アプリケーション/オクテットストリーム]
多くの人々がこの問題に遭遇しました。それらのほとんどは、最初に遭遇すると混乱しています。 JSON、XML、またはプレーンテキスト形式で多くのインターフェイスが返されます。アプリケーション/オクテットストリームとは何ですか?
RESTTEMPLATEソースコードをチェックして、それをずっと追跡すると、httpmessageconverterextractorクラスの抽出型メソッドには、解析応答と脱力化ロジックがあることがわかります。それが成功しない場合、スローされた例外情報は上記と同じです。
httpmessageconverterextractor.extractdata
@override @suppresswarnings({"un -checked"、 "rawTypes"、 "resource"})extractdata(clienthttppresponse response)をスロー{messagebodyclienthttpresponsewrapper responsewrapper = new messagebodybodyclienthtspspspspspspspspspspspspspspspspsproperspers(reasping); if(!responsewrapper.hassessagebody()|| responsewrapper.hasemptymessagebody()){return null; } mediatype contentType = getContentType(ResponseWrapper); try {for(httpmessageconverter <?> messageconverter:this.messageconverter){if(generichttpmessageconverterのmessageconverterインスタンス){generichttpmessageconverter <?> genericmessageconverter =(generichtttpmessageconterter; if(genericmessageconverter.canread(this.responsetype、null、contentType)))){if(logger.isdebugenabled()){oggger.debug( "ready [" + this.responsetype + "] as /" " + contenttype +" /"" + mesageconverter + "); } return(t)genericmessageconverter.read(this.responsetype、null、ressonswrapper); }} if(this.responseclass!= null){if(messageconverter.canread(this.responseclass、contentType)){if(logger.isdebugenabled()){logger.debug( "reading [" + this.responseclass.getName() + " +" + MessageConverter + "]"); } return(t)mesageconverter.read((class)this.responseclass、responswwrapper); }}}} catch(ioException | httpmessagenotreadableException ex){throw new restclientException( "型[" + this.responsetype + "]およびコンテンツタイプ[" + contentType + "]"、ex) }新しいRESTCLIENTEXCEPTION(「応答を抽出できませんでした:適切なHTTPMESSAGECONVERTERが応答タイプ[" + this.responsetype +"]およびコンテンツタイプ[" + contentType +"] ")に「 +」が見つかりませんでした。 } StackOverFlow上のソリューションのサンプルコードは許容されますが、正確ではありません。一般的なマイメタイプを追加する必要があります。正しいと思うコードを投稿します:
RESTTEMPLATECONFIG
パッケージcom.power.demo.restclient.config;インポート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。*;インポートorg.springframework.http.converter.cbor.mappingjackson2cborhttpmessageconverter; Import org.springframework.http.converter.feed.atomfeedhttpmessageconverter; Import springframework.http.converter.feed.rsschannelhttpmessageconverter; Import org.springframework.http.converter.json.gsonhttpmessageconverter; org.springframework.http.converter.jsonbhhstppemhthhthtp.converter.jsongenbhtp.converter.jsongenbhthtp.converter.jsonverteconverte org.springframework.http.converter.json.mappingjackson2httpmessageconverter; import org.springframework.http.converter.smile.mappingJackson2SMileHTTPMESSAGECONVERTER;インポートorg.springframework.http.converter.support.Allencassingformhttpmessageconverter; import org.springframework.http.converter.xml.jaxb2rootelementhttpmessageconverter; Import; springframework.http.converter.xml.mappingjackson2xmlhttpmessageconverter; Import org.springframework.http.converter.xml.sourcehttpmessageconverter; Import org.springframework.Strignework.Stertepe.componet; org.springframework.util.classutils; import org.springframework.web.client.resttemplate; Import java.util.arrays; Import java.util.list; @componentpublic class restemplateconfig {private static final romeprenetent = classutils.ispresent( "com.rometools.rome.feed.wirefeed"、resttemplate .class.getClassLoader()); private static final boolean jaxb2present = classutils.ispresent( "javax.xml.bind.binder"、repttemplate.class.getClassoloader()); Private static final boolean jackson2present = classutils.ispresent( "com.fasterxml.jackson.databind.objectmapper"、repttemplate.class.getClassloader()) Private Static Final Boolean Jackson2xmlpresent = classutils.ispresent( "com.fasterxml.jackson.dataformat.xml.xmlmapper"、repttemplate.class.getClassLoader());プライベートスタティック最終ブールjackson2Smilepresent = classutils.ispresent( "com.fasterxml.jackson.dataformat.smile.smileFactory"、repttemplate.class.getClassoloader(); Private Static Final Boolean Jackson2cborpresent = classutils.ispresent( "com.fasterxml.jackson.dataformat.cborfactory"、repttemplate.class.getClassoloader()); private static final boolean gsonpresent = classutils.ispresent( "com.google.gson.gson"、retttemplate.class.getClassLoader()); private static final boolean jsonbpresent = classutils.ispresent( "javax.json.bind.jsonb"、retttemplate.class.getClassLoader()); //注意RESTEMPLATEをサービスに挿入するため、それを開始するときは、@Autowired PrivateRestTemplateBuilderビルダーを開始するときに、このクラスのインスタンスをインスタンスする必要があります。 @autowired private objectmapper objectmapper; // RestTemplateBuilderを使用して、RestTemplateオブジェクトをインスタンス化します。 Springは、デフォルトでrettemplateBuilderインスタンスを@Bean public RestTemplate restTemplate(){retttemplate rettemplate = builder.build();リスト<httpmessageconverter <? mappingjackson2httpmessageconverter converter = new MappingJackson2HTTPMESSAGECONVERTER(); converter.setObjectMapper(objectMapper); //追加せずに例外が表示されます//応答が抽出できませんでした:応答タイプに見つかった適切なhttpmessageConverterはありません[class] mediatype [] mediatype = 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 converter.setsupportedmediatypes(arrays.aslist(mediatypes)); //messageconverters.add(converter); if(jackson2present){mesageconverters.add(converter); } else if(gsonpresent){messageconverters.add(new gsonhttpmessageconverter()); } else if(jsonbpresent){messageconverters.add(new jsonbhttpmessageconverter()); } mesageconverters.add(new formhttpmessageconverter()); mesageconverters.add(new StringHttpmessageConverter()); mesageconverters.add(new StringHttpmessageConverter()); mesageConverters.add(new ResourceHttpmessageConverter(false)); mesageConverters.add(new sourcehttpmessageconverter()); MessageConverters.Add(new AllEncompassingformhttpmessageconverter()); if(romepresent){messageconverters.add(new atomfeedhttpmessageconverter()); mesageConverters.add(new rsschannelhttpmessageconverter()); } if(jackson2xmlpresent){messageconverters.add(new mappingjackson2xmlhttpmessageconverter()); } else if(jaxb2present){messageconverters.add(new jaxb2rootelementhttpmessageconverter()); } if(jackson2smilepresent){messageconverters.add(new mappingjackson2smilehttpmessageconverter()); } if(jackson2cborpresent){messageconverters.add(new MappingJackson2CborhttpmessageConverter()); } RESTTEMPLATE.SESTMESSAGECONVERTERS(MESSAGECONVERTERS); RESTTEMPLATEを返します。 }}上記のコードを見て、RestTemplateの内部実装を比較した後、RestTemplateのソースコードを参照したことがわかります。清潔さに夢中になっている人は、このコードが少し冗長であると言うかもしれません。上記の静的な最終変数とMessageConvertersの埋めるデータは、RestTemplateの実装を公開します。 RestTemplateが変更されている場合、ここでも変更されます。これは非常に友好的ではなく、まったく見かけません。
分析後、RESTTEMPLATEBUILDER.BUILD()はRESTTEMPLATEオブジェクトを作成します。内部mappingjackson2httpmessageconverterを使用して、サポートされているmediatypeを変更するだけです。 RestTemplateのMessageConvertersフィールドはプライベートファイナルですが、反射を通じてそれを変更することができます。改善されたコードは次のとおりです。
RESTTEMPLATECONFIG
パッケージcom.power.demo.restclient.config;インポート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.htpmessagonerter; org.springframework.http.converter.json.mappingjackson2httpmessageconverter; import org.springframework.stereotype.component; import org.springframework.web.client.restemplate; import java.lang.reff.field; import.filled. java.util.list; import java.util.optional; import java.util.stream.collectors; @componentpublic class rettemplateconfig {//サービスを開始するときにそれを開始するときにそれを開始するときに、@autowired cowired cowired cowired weartemplateBuilderを開始するときにクラスをインスタンスにする必要があります。 @autowired private objectmapper objectmapper; // RestTemplateBuilderを使用して、RestTemplateオブジェクトをインスタンス化します。 springは、デフォルトでrettemplateインスタンスを@bean public rettemplate resttemplate(){retttemplate resttemplate = builder.build();リスト<httpmessageconverter <? mappingjackson2httpmessageconverter converter = new MappingJackson2HTTPMESSAGECONVERTER(); converter.setObjectMapper(objectMapper); //追加がない場合は例外が発生する可能性があります//応答を抽出できなかった場合:応答タイプには適切なHTTPMESSAGECONVERTERが見つかりませんでした[クラス] MediAType = new MediAtype [] {Mediatype.Application_json、Mediatype.Application_octet_stream、Mediatepe.text_html、mediatype.text_html、mediatype.text_html、 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 {//反射フィールド= rettemplate.getClass()。getDeclaredField( "MessageConverters"); field.setAccessible(true);リスト<httpmessageconverter <?>> orgconverterlist =(list <httpmessageconverter <?オプション<httpmessageconverter <? if(opconverter.ispresent()== false){return rettemplate; } messageConverters.add(converter); // mappingjackson2httpmessageconverter //元の残りのhttpmessageconverterリスト<? x.getClass()。getName()。equalSignOreCase(mappingjackson2httpmessageconverter.class .getName())== false).collect(collectors.tolist()); MessageConverters.Addall(leftConverters); System.out.println(String.Format( "【HTTPMESSAGECONVERTER】元の数量:%S、再構成数量:%S"、orgconverterlist.size()、mesageConverters.size()); } catch(Exception e){e.printstacktrace(); } RESTTEMPLATE.SESTMESSAGECONVERTERS(MESSAGECONVERTERS); RESTTEMPLATEを返します。 }}MessageConvertersフィールドとは別に、外部の依存関係パッケージとレストテンプレートの内部構造プロセスについてはもはや気にしないようです。予想通り、それは非常にきれいで簡潔で、メンテナンスが簡単です。
3.ゴミコードの問題
これも非常に古典的な質問です。ソリューションは非常にシンプルで、httpmessageconverterを見つけて、デフォルトのサポートされているcharsetを見てみましょう。 AbstractJackson2HTTPMessageConverterは、多くのhttpmessageconverterの基本クラスであり、デフォルトのエンコーディングはUTF-8です。
AbstractJackson2HTTPMESSAGECONVERTER
パブリックアブストラクトクラスAbstractJackson2HTTPMESSAGECONVERTER EXTENTS ABSTRACTGENERICHTTPMESSAGECONVERTER <OBJECT> {public static final charset default_charset = stardandcharsets.utf_8;}stringhttpmessageconverterは非常に特別です。一部の人々は、文字化けの問題は、デフォルトでサポートするISO-8859-1をエンコードすることによって引き起こされると報告しています。
stringhttpmessageconverter
/***文字列を読み書きできる{@link httpmessageconverter}の実装。 * * <p>デフォルトでは、このコンバーターはすべてのメディアタイプ({@code})、 *をサポートし、{@code content-type}の{@code Text/Plain}で書き込みます。これは、{@link #setsuptedmediatypes supportedmediatypes}プロパティを設定することにより、オーバーライドできます。 * * @author arjen poutsma * @author juergen hoeller * @since 3.0 */public classhttpmessageconverter extends abstracthttpmessageconverter <string> {public static final charset default_charset = stardandcharsets.iso_8859_1; /*** {@code "ISO-8859-1"}をデフォルトのcharsetとして使用するデフォルトのコンストラクター。 * @see #stringhtpmessageconverter(charset) */ public stringhttpmessageconverter(){this(default_charset); }}使用中に文字化けコードが発生した場合、メソッドを介してHTTPMessageConverterによってサポートされているエンコードを設定できます。一般的に使用されるものにはUTF-8、GBKなどが含まれます。
4。脱介入例外
これは、開発プロセス中に遭遇しやすい別の問題です。 Javaには非常に多くのオープンソースフレームワークとツールがあり、バージョンが頻繁に変更されるため、予期しない落とし穴が頻繁に発生します。
例としてジョーダの時間を取ります。 Joda Timeは人気のあるJavaの時間と日付のフレームワークですが、インターフェイスがDateTimeなどのJoda時間のタイプを公開する場合、インターフェイス発信者(同型および不均一なシステム)がシリアル化の問題に遭遇する可能性があります。
org.springframework.http.converter.httpmessageconversionexception:タイプ定義エラー:[シンプルなタイプ、クラスorg.joda.time.time.phroology];ネストされた例外はcom.fasterxml.jackson.databind.exc.invaliddefinitionexception: `org.joda.time.prosology`(デフォルト構成のような作成者はいない、存在しない)のインスタンスを構築できません:コンクリートタイプにマッピングする必要はありません。
at [source:(pushbackinputStream);
私は前の工場でこれに遭遇しましたが、後で呼び出しの利便性のために、私はJavaを直接露出した日付型に戻りました。
もちろん、この解決策以上のものがあります。 Jacksonを使用して、カスタムクラスのシリアル化と敏aserializationをサポートできます。精度がそれほど高くないシステムでは、単純なDateTimeカスタムシリアル化を実装してください。
DateTimeRializer
パッケージcom.power.demo.util; import com.fasterxml.jackson.core.jsongenerator; Import com.fasterxml.jackson.core.jsonprocessingincection; Import com.fasterxml.jackson.databind.jsonserializer; Import com.jackson.jackson.databind.databinderializerizerizer org.joda.time.datetime; Import org.joda.time.format.dateTimeformat; Import org.joda.time.format.datetimeformatter;/***デフォルトでは、JacksonはJoda時間をより複雑な形にシリアル化します。 * <p>* Jodatimeをシリアル化すると、DateTimeを文字列にシリアル化できます。 @Override public void serialize(datetime value、jsongenerator jgen、serializerproviderプロバイダー)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.jsonprocessingception; import com.fasterxml.jackson.databind.databind.deserializationcontext; com.fasterxml.jackson.databind.jsonnode; Import org.joda.time.datetime; import org.joda.time.format.datetimeformat; import org.joda.time.format.datetimeformatter; Import java.io.ioexception; DateTimeDeSerializer拡張jsondeSerializer <datetime> {private static dateTimeFormatter dateformation = 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 (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をもっとサポートすることを願っています。