스프링 부팅을 통해 나머지 인터페이스를 신속하게 개발할 수 있으며, 인터페이스를 구현하는 동안 비즈니스 로직을 완료하기 위해 Spring Boot를 통해 내부 및 외부 REST 인터페이스를 호출해야 할 수도 있습니다.
Spring Boot에는 REST API를 호출하는 두 가지 일반적인 방법이 있습니다.이 방법은 내장 된 RestTemplate를 통해 서비스 호출을 구현하거나 고유 한 HTTP 클라이언트 도구를 개발하는 데 사용됩니다.
RestTemplate은 매우 강력한 기본 기능을 가지고 있지만 일부 특별한 시나리오에서는 파일 업로드 파일 시스템에 파일 업로드, 인증서로 HTTPS 요청 처리 등과 같은 자체 캡슐화 된 도구 클래스를 사용하는 데 더 익숙 할 수 있습니다.
이 기사는 RestTemplate을 사용하여 RestTemplate을 사용하여 인터페이스를 호출하는 동안 발견 된 몇 가지 문제와 솔루션을 기록합니다.
1. RestTemplate 소개
1. RestTemplate이란 무엇입니까?
우리가 캡슐화하는 httpclient에는 일반적으로 연결 설정, 요청 헤더 및 요청 본문 구성, 응답을 기반으로 응답 정보를 구문 분석하고 연결을 닫는 것과 같은 템플릿 코드가 있습니다.
resttemplate는 봄에 HTTPCLIENT의 재 캡슐화로, 추상화 수준이 높고 소비자 템플릿 코드를 줄이며 중복 코드를 덜 만드는 HTTP 요청 및 처리 응답을 시작하는 프로세스를 단순화합니다.
실제로 Spring Boot에서 많은 xxxtemplate 클래스에 대해 생각하면 다양한 템플릿 방법도 제공하지만 추상화 수준이 높고 자세한 내용은 숨겨져 있습니다.
그건 그렇고, Spring Cloud에는 선언적 서비스 콜 Feign이 있으며, Netflix Feign을 기반으로 구현되고 Spring Cloud Ribbon 및 Spring Cloud Hystrix를 통합하고 선언적 웹 서비스 클라이언트 정의 방법을 구현합니다.
본질적으로 Feign은 RestTemplate를 기반으로 다시 캡슐화하여 종속 서비스 인터페이스의 정의를 정의하고 구현하는 데 도움이됩니다.
2. RestTemplate의 일반적인 방법
Get, Post, Put, Delete, Head, Head, Options 등과 같은 일반적인 휴식 서비스에 대한 많은 요청 방법이 있습니다. RestTemplate는 가장 일반적인 방법을 구현하며 가장 일반적으로 사용되는 것은 Get and Post입니다. API를 호출 할 때 소스 코드를 참조 할 수 있습니다. 다음은 몇 가지 메소드 정의 (GET, POST, DELETE)입니다.
행동 양식
public <t> t getforObject (문자열 URL, 클래스 <T> ResponseType, Object ... urivariable) public <t> responsentity <t> getforentity (String URL, Class <T> ResponseType, Object ... Urivariable) poplorObject (String URL, @Nullable 객체 요청, class <T> responsitity, urvariables). url, @nullable 객체 요청, 클래스 <t> responseType, 객체 ... Urivariable) public void delete (String URL, Object ... Urivariablebs) public void delete (uri url)
동시에, 두 개의 "유연한"메소드 교환 및 실행에주의를 기울여야합니다.
resttemplate에 노출 된 교환은 다른 인터페이스와 다릅니다.
(1) 발신자가 HTTP 요청 방법을 지정하도록 허용합니다 (GET, POST, DELETE 등).
(2) 신체 및 헤더 정보는 요청에 추가 될 수 있으며 해당 내용은 매개 변수 'httpentity <?> requestentity'로 설명됩니다.
(3) Exchange는 '매개 변수를 포함하는 유형'(예 : 일반 클래스)을 리턴 유형으로 지원 하며이 기능은 'ParameterizedTypereference <T> ResponseType'으로 설명됩니다.
resttemplate 모두 GET, POST 및 기타 메소드 및 EXECUTE 메소드의 최종 호출은 Execute 메소드입니다. 엑스 컷 방법의 내부 구현은 String-Format URI를 java.net.uri로 변환 한 다음 DoExecute 메소드를 호출하는 것입니다. DoExecute 방법의 구현은 다음과 같습니다.
execute
/*** 제공된 URI에서 주어진 방법을 실행합니다. * <p> {@link clienthttprequest}는 {@link requestCallback}을 사용하여 처리됩니다. * {@link responsextractor}에 대한 응답. * @Param URL 전체에 연결하기위한 완전히 확장 된 URL * @param 메소드에 연결하기위한 HTTP 메소드 (get, post 등) * @param requestCallback 객체를 준비하는 @param repapertectractor 객체를 준비하는 @param responsectractor 응답에서 반환 값을 추출하는 개체 (@code null}) * @arbitry a arbitry a a arbitry an a a arbitry a a arbitry ResponseCtractor} */ @Nullable Protected <T> T DOEXECUTE (uri url, @nullable httpmethod 메서드, @Nullable requestCallback requestCallback, @Nullable ResponseTractor <T> ResponseExtractor)는 RestClientException {Assert.NOTNULL (url ', "url'이 나지 않아야한다")를 던졌습니다. assert.notnull (메소드, '메소드'는 널 널 늘어나지 않아야한다 "); ClientHttPresponse 응답 = null; try {clienthttprequest request = createrequest (url, method); if (requestCallback! = null) {requestCallback.DowItRequest (request); } response = request.execute (); 핸들러 응답 (URL, 메소드, 응답); if (responsextractor! = null) {return responsextractor.extractData (응답); } else {return null; }} catch (ioexception ex) {문자열 resource = url.tostring (); 문자열 query = url.getRawquery (); resource = (query! = null? resource.substring (0, resource.indexof ( '?')) : resource); 새 ResourceAccessException ( " + method.name () +"request for/"" + resource + "/": " + ex.getMessage (), ex)의"I/O 오류 "를 던지십시오. } 마지막으로 {if (응답! = null) {response.close (); }}}DoExecute 메소드는 연결 생성, 요청 및 답변 처리, 연결 종료 등과 같은 템플릿 메소드를 캡슐화합니다.
대부분의 사람들이 이것을 볼 때, 그들은 아마도 휴게소를 캡슐화하는 것이 이와 같다고 생각할 것입니다.
3. 간단한 전화
예를 들어 포스트 전화를 받으십시오.
GoodsServiceclient
package com.power.demo.restclient; import com.power.demo.common.appconst; import com.power.demo.restclient.clientRequest.clientGoodsByGoodSidRequest; import com.power.Demo.RestClient.RestClient.ClientSponse.clientGoodsBygodsIdr respont; org.springframework.beans.bean.annotation.autowired; import org.springframework.bean.beans.annotation.value; import org.springframework.stereotyp.component; import org.sprameframework.web.client.resttemplate; upplication intertetplate (demo)*/@@@@@production intertplice (demo)*/@ GoodsServiceClient {// 서비스 소비자가 호출하는 인터페이스 URL은 다음과 같습니다. http : // localhost : 9090 @value ( "$ {spring.power.serviceurl}") private string _serviceurl; @autowired 개인 resttemplate resttemplate; public clientGoodSbyGoodSidResponse getGoodSbyGoodSid (clientGetGoodSbyGoodSidRequest 요청) {String SvCurl = getGoodsSvCurl ()/getInfoById "; ClientGetGoodSbyGoodSidResponse 응답 = NULL; {response = resttemplate.postforObject (svcurl, request, clientGetGoodSbyGoodSidResponse.class); } catch (예외 e) {e.printstacktrace (); 응답 = 새로운 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); } 반환 URL; }}데모에서 resttemplate.postforobject 메서드는 직접 호출되며 사막화 엔티티는 이러한 resttemplate 내부 캡슐화로 변환됩니다.
2. 문제 요약
1. 요청 유형 예외에 적합한 httpmessageconverter가 없습니다
이 문제는 일반적으로 들어오는 객체가 호출을 위해 포스트 포로버로 전달 될 때 발생합니다.
resttemplate 소스 코드를 분석하십시오. HttpentityRequestCallback 클래스의 DowithRequest 방법에서 MessageConverters (이 필드가 나중에 계속 언급 될 것임)가 반환 된 논리를 충족시키지 못하면 (즉, httpmessageconverter와 일치하지 않습니다) 위의 예외는 다음과 같습니다.
httpentityRequestCallback.DowitHrequest
@override @suppresswarnings ( "확인되지 않은") public void dowithrequest (clienthttprequest httprequest)는 ioexception {super.dowithrequest (httprequest); 객체 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>> entery : requestheaders.entryset ()) {httpheaders.put (enther.getKey (), new LinkedList <> (enterd.getValue ())); }} if (httpheaders.getContentLength () <0) {httpheaders.setContentLength (0L); }} else {class <?> requestBodyClass = requestBody.getClass (); 유형 requestBodyType = (this.requestentity instanceof requestEntity? httpheaders httpheaders = httprequest.getheaders (); httpheaders requestheaders = this.requestentity.getheaders (); MediaType requestContentType = requestHeaders.getContentType (); for (httpmessageconverter <?> messageconverter : getMessageConverters ()) {if (generichttpmessageconverter의 messageconverter 인스턴스) {genericttpmessageconverter <boodict> genericconverter = (generichttpmessageconverter <object>) messageconverter; if (genericConverter.canWrite (requestBodyType, requestBodyClass, requestContentType)) {if (! requestHeaders.isempty ())) {for (map.Entry <string, list <string>> entry : requestHeaders.entryset ()) {httpeaders.put (enthreke.getKey ()))) }} if (logger.isdebugenabled ()) {if (requestContentType! = null) { "writing [" + requestbody + "] as /"as /" + requestContentType +" /"[" + MessageConverter + "]; MessageConverter + "]; : requestHeaders.entryset ()) {httpheaders.put (Entry.getKey (), new LinkedList <> (enther.getValue ()); [ " + messageconverter +"] ") 사용; } else {logger.debug ( "쓰기 [" " + requestbody +"] "); }} ((httpmessageConverter <botort>) MessageConverter) .write (요청 body, requestContentType, httpRequest); 반품; }} string message = "요청을 쓸 수 없습니다 : 요청 유형에 대해 찾은 httpmessageconverter [" + requestbodyClass.getName () + "]; if (requestContentType! = null) {message + = "및 컨텐츠 유형 [" + requestContentType + "]; } 새로운 restclientException (메시지)을 던집니다. }} 가장 쉬운 솔루션은 HTTP 요청 헤더를 래핑하고 요청 객체를 문자열로 직렬화하여 매개 변수를 전달하는 것입니다. 참조 샘플 코드는 다음과 같습니다.
포스트 포스트
/ * * post 요청 호출 * */ public static string postForObject (resttemplate resttemplate, 문자열 URL, 객체 매개 변수) {httpheaders headers = new httpheaders (); MediaType 유형 = mediaType.parsEmediAtype ( "Application/JSON; charset = utf-8"); HEADERS.SETCONTENTTYPE (유형); headers.add ( "accept", mediaType.application_json.toString ()); 문자열 json = serializeutil.serialize (params); httpentity <string> formentity = new httpentity <string> (json, 헤더); 문자열 결과 = resttemplate.postforoBject (url, formentity, string.class); 반환 결과; }우리가 객체를 직접 반환하려면 반환 된 문자열을 직접화 할 수 있습니다.
포스트 포스트
/ * * post 요청 호출 * */ public static <t> t postforobject (resttemplate resttemplate, 문자열 URL, 객체 매개 변수, 클래스 <t> clazz) {t response = null; 문자열 respstr = postforobject (resttemplate, url, params); 응답 = serializeutil.deserialize (respstr, clazz); 반환 응답; }그중에는 Fastjson, Jackson 및 GSON과 같은 직렬화 및 사막화를위한 많은 도구가 있습니다.
2. 응답 유형 예외에 적합한 httpmessageconverter가 없습니다
요청을 시작할 때 예외가 발생하는 것처럼 응답을 처리 할 때 문제도 있습니다.
누군가는 stackoverflow에서 같은 질문을했습니다. 근본 원인은 HTTP 메시지 변환기 httpMessageConverter에 MIME 유형이 없기 때문입니다. 즉, HTTP가 출력 결과를 클라이언트로 전송하면 클라이언트가 출력 문서를 처리하기 위해 적절한 응용 프로그램을 시작해야합니다. 이는 여러 MIME (다기능 인터넷 메일 증강 프로토콜) 유형을 통해 수행 할 수 있습니다.
서버 측 응답의 경우 많은 HTTPMESSAGECONVERTER는 기본적으로 다양한 미디어 유형 (모방 유형)을 지원합니다. stringhttpmessageconverter에 대한 기본 지원은 mediaType.text_plain입니다. sourcehttpmessageConverter의 기본 지원은 mediaType.text_xml입니다. formhttpmessageconverter의 기본 지원은 mediaType.Application_formoded 및 mediaTipArt_FORLEDATA입니다. REST 서비스에서 우리가 가장 많이 사용하는 것은 Mapping Jackson2httpMessageConverter입니다. , 이것은 비교적 일반적인 변환기입니다 (generichttpmessageconverter 인터페이스에서 상속). 분석에 따르면 기본적으로 지원하는 Mimetype은 MediaType.application_json입니다.
매핑 Jackson2httpmessageConverter
/*** 사용자 정의 {@link ObjectMapper}를 사용하여 새 {@Link Mapping Jackson2HttpMessageConverter}를 구성합니다. * {@link jackson2objectmapperBuilder}를 사용하여 쉽게 빌드 할 수 있습니다. * @Seee Jackson2oBjectMapperBuilder#json () */ public 맵핑 jackson2httpmessageConverter (ObjectMapper ObjectMapper) {super (ObjectMapper, MediaType.Application_json, new MediaType ( "Application", " *+JSON"); }그러나 일부 응용 프로그램 인터페이스의 기본 응답 Mimetype는 Application/JSON이 아닙니다. 예를 들어, 외부 일기 예보 인터페이스를 호출하는 경우 resttemplate의 기본 구성을 사용하는 경우 문자열 응답을 직접 반환하는 것은 문제가되지 않습니다.
String url = "http://wthrcdn.etouch.cn/weather_mini?city=shanghai"; resttemplate.getForObject (url, String.class); clientWeatherResultVo vo = serializeUtil.deserialize (result, clientWeaterResultVo.class);
그러나 엔티티 객체를 직접 반환하려면 다음과 같습니다.
문자열 url = "http://wthrcdn.etouch.cn/weather_mini?city=shanghai" ;clientWeatherResultvo weatherresultvo = resttemplate.getForObject (url, clenderWeatherResultVo.class);
그런 다음 예외를 직접보고하십시오.
응답을 추출 할 수 없음 : 응답 유형 [클래스]에 적합한 httpmessageconverter가 없습니다.
및 컨텐츠 유형 [응용 프로그램/옥트 스트림]
많은 사람들 이이 문제를 겪었습니다. 그들 중 대부분은 처음 만났을 때 혼란스러워합니다. 많은 인터페이스가 JSON, XML 또는 일반 텍스트 형식으로 반환됩니다. Application/Octet-stream이란 무엇입니까?
resttemplate 소스 코드를 확인하고이를 끝까지 추적하십시오. HttpMessageConvertractor Class의 ExtractData 메소드에 구문 분석 응답 및 사형화 로직이 있음을 알 수 있습니다. 성공하지 못하면 제외 된 예외 정보는 위와 동일합니다.
httpmessageconverextractor.extractData
@override @SuppressWarnings ({ "선택 취소", "rawtypes", "resource"}) public t extressData (clientHttPresponse responsk)는 ioException {messageClientHtttTpresponseWrapper = new MessageBodyClientHttPresponsWrapper (응답); if (! responsewrapper.hasmessagebody () || responsewrapper.hasemptymessagebody ()) {return null; } mediaType contentType = getContentType (responswprapper); {for (httpmessageconverter <?> messageconverter : this.messageconverters) {if (generichttpmessageconverter의 messageconverter 인스턴스) {generichttpmessageconverter <?> genericmessageconverter = (genericttpmessageconverter <?); if (genericmessageconverter.canread (this.responsetype, null, contenttype)) {if (logger.isdebugenabled ()) {logger.debug ( "reading [" + this.responsetype + "] as /" " + contenttype +" /"사용 [" + MessageConver + "]; } return (t) genericMessAgeConverter.Read (this.ResponsEtype, null, responskWrapper); }} if (this.responseclass! = null) {if (messageconverter.canread (this.responseclass, contenttype)) {if (logger.isdebugenabled ()) {logger.debug ( "reading [" + this.responseclass.getname () + "" + " +" + " +" " +" + "" + " +" MessageConverter + "]"); } return (t) MessageConverter.Read ((클래스) this.ResponSeClass, responskWrapper); }}}} catch (ioexception | httpmessagenoTreadableException ex) {trank new restclientException ( ""유형에 대한 응답을 추출하는 동안 오류 [ " + this.responsetype +"] 및 컨텐츠 유형 [ " + contenttype +"], ex); } 새로운 RESTCLIENTEXCEPTION 던지기 ( "응답을 추출 할 수 없음 : 적절한 httpMessAgeConverter 없음 응답 유형 [" + this.ResponSetype + "] 및 컨텐츠 유형 [" + contentType + "]); } StackoverFlow에서 솔루션의 샘플 코드는 허용되지만 정확하지는 않습니다. 일반적인 모방 유형을 추가해야합니다. 나는 올바른 코드를 게시 할 것입니다.
resttemplateconfig
package com.power.demo.restclient.config; import com.fasterxml.jackson.databind.objectmapper; import com.google.collect.lists; import org.spramework.beans.annotation.AUTOWERED; import org.spramewort.boiled org.springframework.context.annotation.bean; import org.springframework.http.mediatepe; import org.sprameframework.http.converter.*; import org.springframework.http.converter.cbor.mappingjackson2cborhtpmessageconvereconvereconvereconvereconvereconvereconvereconvereconvereconvereconvercongeconvereconpmestsageconvereconvereconvercon org.springframework.http.converter.feed.atomfeedhttpmessageconverter; import org.springframework.http.converter.feed.rsschannelhtttpmessageconverter; import org.springframework.http.converter.jsonhtttp.converconver.jsonhtttp.converconverter org.springframework.http.converter.json.jsonbhttpmessageconverter; import org.springframework.http.converter.json.mappingjackson2httpmessageconverter; import org.springframework.http.converter.smile.mappingjackson2smilehttpmessageconverter; import org.springframework.http.converter.support.allenCompassingformhttpmessageconverter; import org.springframework.http.converter.xml.jaxb2rootelementhttpmessageconverter; import org.springframework.http.converter.xml.mappingjackson2xmlhttpmessageconverter; import org.springframework.http.converter.xml.sourcehttpmessageconverter; import org.spramework.stereotyp.component; import org.springframework.util.classutils; import org.springframwork.web.client.resttemplate; java.util.arrays; java.util.list; @componentpublic class resttemplateconfig {private static final boolean romepresent = classutils.ispresent ( "com.rometools.rome.feed.weed.weed.wirefeed", resttemplate .class.getcastlassLoader (); 개인 정적 최종 부울 Jaxb2present = classutils.ispresent ( "javax.xml.bind.binder", resttemplate.class.getClassLoader ()); 개인 정적 최종 부울 Jackson2Present = classutils.ispresent ( "com.fasterxml.jackson.databind.objectmapper", resttemplate.class.getclassloader ()) && classutils.ispresent ( "com.fasterxml.jackson.core.jsongernerator", resttemplate.class.class.class.getclass. 개인 정적 최종 부울 Jackson2xmlPresent = classutils.ispresent ( "com.fasterxml.jackson.dataformat.xml.xmlmapper", resttemplate.class.getClassLoader ()); 개인 정적 최종 부울 Jackson2SmilePresent = classutils.ispresent ( "com.fasterxml.jackson.dataformat.smile.smilefactory", resttemplate.class.getClassLoader ()); 개인 정적 최종 부울 Jackson2cborPresent = classutils.ispresent ( "com.fasterxml.jackson.dataformat.cbor.cborfactory", resttemplate.class.getClassLoader ()); 개인 정적 최종 Boolean gsonPresent = classutils.ispresent ( "com.google.gson.gson", resttemplate.class.getClassLoader ()); 개인 정적 최종 부울 JSONBPRESENT = classutils.ispresent ( "javax.json.bind.jsonb", resttemplate.class.getClassLoader ()); // 시작시 RestTemplate를 서비스에 주입하기 때문에 @autowired private resttemplatebuilder builder를 시작할 때이 클래스의 인스턴스를 인스턴스화해야합니다. @autowired 개인 ObjectMapper ObjectMapper; // resttemplatebuilder를 사용하여 resttemplate 객체를 인스턴스화합니다. Spring은 기본적으로 resttemplatebuilder 인스턴스를 @bean public resttemplate resttemplate () {resttemplate resttemplate = builder.build (); 목록 <httpmessageconverter <? >> messageconverters = lists.newarraylist (); 매핑 Jackson2htttpmessageConverter converter = 새로운 맵핑 잭슨 2httpmessageconverter (); conver.setObjectMapper (ObjectMapper); // 예외는 //를 추가 할 수 없습니다. 응답을 추출 할 수 없습니다 : 응답 유형에 대해 적절한 httpMessageConverter 없음 [class] mediaType [] mediaTypes = new MediaType [] {mediaType.Application_json, mediaType.Application_octet_stream, mediaType.Application_json_UTF8, mediaType.TextEpe.TEXTETML. mediaType.text_plain, mediaType.text_xml, mediaType.Application_stream_json, mediaType.Application_atom_xml, application_form_urlencoded, mediaType.Application_pdf,}; Converter.SETSUPPORTEDMEDIATYPES (Arrays.AsList (MediaTypes)); //messageconverters.add(converter); if (jackson2present) {messageconverters.add (converter); } else if (gsonPresent) {MessageConverters.add (new gsonhttpmessageconverter ()); } else if (jsonBpresent) {messageconverters.add (new jsonbhttpmessageconverter ()); } messageconverters.add (new formhttpmessageconverter ()); MessageConverters.add (new StringhttpmessageConverter ()); MessageConverters.add (new StringhttpmessageConverter ()); MessageConverters.add (새로운 resourcehttpmessageconverter (false)); MessageConverters.add (new sourcehttpmessageconverter ()); MessageConverters.add (New AllenCompassingFormhttpMessageConverter ()); if (RomePresent) {messageconverters.add (new atomfeedhttpmessageconverter ()); MessageConverters.add (새로운 rschannelhttpmessageconverter ()); } if (jackson2xmlpresent) {messageconverters.add (new Mapping Jackson2xmlhttpmessageConverter ()); } else if (jaxb2present) {messageconverters.add (new jaxb2rootelementhttpmessageconverter ()); } if (jackson2SmilePresent) {messageconverters.add (new Mapping Jackson2SmilehttpmessageConverter ()); } if (jackson2cborpresent) {messageconverters.add (new Mapping Jackson2cborhttpMessageConverter ()); } resttemplate.setMessAgeConverters (MessageConverters); RESTTEMPLATE를 반환합니다. }}위의 코드를보고 resttemplate의 내부 구현을 비교 한 후에는 resttemplate의 소스 코드를 참조했음을 알 수 있습니다. 청결에 사로 잡힌 사람들은이 코드가 약간 장점이라고 말할 수 있습니다. 위의 정적 최종 변수 및 MessageConverters 채우기 데이터 방법은 RestTemplate의 구현을 노출시킵니다. resttemplate이 수정되면 여기에서도 수정되며 매우 비우호적이며 전혀 보이지 않습니다.
분석 후 resttemplatebuilder.build ()는 resttemplate 객체를 구성합니다. 내부 맵핑 Jackson2httpmessageConverter로 지원되는 MediaType를 수정하십시오. RestTemplate의 MessageConverters 필드는 개인 최종이지만 반사를 통해 여전히 수정할 수 있습니다. 개선 된 코드는 다음과 같습니다.
resttemplateconfig
package com.power.demo.restclient.config; import com.fasterxml.jackson.databind.objectmapper; import com.google.collect.lists; import org.spramework.beans.annotation.AUTOWERED; import org.spramewort.boiled org.springframework.context.annotation.bean; import org.springframework.http.mediatepe; import org.sprameframework.http.converter.httpmessageconverter; import org.springframework.http.converter.json.json.json.2htpson2hton2htson2hton2hton2htson2hton. org.springframework.stereotyp.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.colftors;@@@@avumt java.util.list; resttemplateconfig {// resttemplate를 서비스에 주입하기 때문에 시작할 때 @autowired private resttemplatebuilder builder를 시작할 때 클래스의 인스턴스를 인스턴스화해야합니다. @autowired 개인 ObjectMapper ObjectMapper; // resttemplatebuilder를 사용하여 resttemplate 객체를 인스턴스화합니다. 스프링은 기본적으로 resttemplate 인스턴스를 주입했습니다 @bean public resttemplate resttemplate () {resttemplate resttemplate = builder.build (); 목록 <httpmessageconverter <? >> messageconverters = lists.newarraylist (); 매핑 Jackson2htttpmessageConverter converter = 새로운 맵핑 잭슨 2httpmessageconverter (); conver.setObjectMapper (ObjectMapper); // 추가 응답을 추출 할 수없는 경우 예외가 발생할 수 있습니다 : 응답 유형에 대한 적절한 httpMessageConverter 없음 [class] mediaType [] mediaTypes = new MediaType [] {mediaType.application_json, mediaType.Application_octet_stream, mediaType.Text_html, mediaType. 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)); 시도 {// 반사 필드 필드 = resttemplate.getClass (). getDeclaredfield ( "MessageConverters")를 통해 MessageConverters를 설정합니다. field.setAccessible (true); <httpmessageconverter <? >> orgconverterlist = (list <httpmessageconverter <? >>) field.get (resttemplate); 선택 사항 <httpmessageconverter <? >> opconverter = orgconverterlist.stream () .filter (x-> x.getClass (). getName (). equalSignoreCase (맵핑 jackson2httpmessageConverter.class .getname ()) .findfirst (); if (opconverter.ispresent () == false) {return resttemplate; } messageconverters.add (converter); // 맵핑 잭슨 2httpmessageconverter // 나머지 원본 추가 httpmessageconverter list <httpmessageconverter <?>> leftConverters = orgconverterlist.stream () .filter (x-> x.getClass (). getName (). equalSignoreCase (맵핑 잭슨 2HTTPMESSAGECONVERTER.CLASS .getName ()) == false) .collect (collector.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. 쓰레기 코드 문제
이것은 또한 매우 고전적인 질문입니다. 이 솔루션은 매우 간단합니다. Abstract Jackson2httpmessageConverter는 많은 httpmessageconverter의 기본 클래스이며 기본 인코딩은 UTF-8입니다.
Abstract Jackson2httpmessageConverter
공개 초록 클래스 Abstract Jackson2httpMessageConverter 확장 actractgenerichttpmessageconverter <botort> {public static final charset default_charset = Standardcharsets.utf_8;}StringhttpMessageConverter는 매우 특별합니다. 일부 사람들은 차량 문제가 기본적으로 지원하는 ISO-8859-1 인코딩으로 인해 발생한다고보고했습니다.
stringhttpmessageconverter
/*** 문자열을 읽고 쓸 수있는 {@link httpmessageconverter}의 구현. * * * <p> 기본적 으로이 컨버터는 모든 미디어 유형 ({@code})을 지원하고 {@code text/plain}의 {@code content-type}로 씁니다. {@link #setsupportedMediadiAtypes supportedMediAtypes} 속성을 설정하여 이는 재정의 할 수 있습니다. * * @Author arjen poutsma * @Author Juergen Hoeller * @since 3.0 */public class stringhttpmessageconverter abottracthtttpmessageconverter <string> {public static final charset default_chault_chault_chault_chault_chault_chault_chault_chaults.iso_8859_1; /*** {@code "iso-8859-1"}을 기본 숯으로 사용하는 기본 생성자. * @see #stringhttpmessageconverter (charset) */ public stringhttpmessageconverter () {this (default_charset); }}사용 중에 차량 코드가 발생하는 경우 방법을 통해 httpmessageconverter에서 지원하는 인코딩을 설정할 수 있습니다. 일반적으로 사용되는 것은 UTF-8, GBK 등을 포함합니다.
4. 사제화 예외
이것은 개발 과정에서 발생하기 쉬운 또 다른 문제입니다. Java에는 오픈 소스 프레임 워크와 도구가 너무 많고 버전이 자주 변경되므로 예기치 않은 함정이 종종 발생합니다.
Joda 시간을 예로 들어보십시오. Joda Time은 인기있는 Java Time and Date Framework이지만 인터페이스가 DateTime과 같은 Joda 시간의 유형을 노출하면 인터페이스 발신자 (동형 및 이종 시스템)가 직렬화 문제에 직면하여 사막화 중에 다음과 같은 예외를 직접 던질 수 있습니다.
org.springframework.http.converter.httpmessageconversionException : 유형 정의 오류 : [간단한 유형, 클래스 org.joda.time.chronology]; 중첩 예외는 com.fasterxml.jackson.databind.exc.invaliddefinitionexception :`org.joda.time.chronology '(기본 구성과 같은 제작자 없음, 존재하지 않음)의 인스턴스를 구성 할 수 없음) : 초록 유형 중 하나는 콘크리트 유형에 매핑되어야합니다.
에서 [출처 : (PurpbackInputStream);
나는 이전 공장에서 이것을 만났고 나중에 전화의 편의를 위해 Java를 직접 노출 한 날짜 유형으로 다시 변경되었습니다.
물론이 솔루션보다 더 많은 것이 있습니다. Jackson을 사용하여 커스텀 클래스의 직렬화 및 사막화를 지원할 수 있습니다. 정확도가 매우 높은 시스템에서 간단한 DateTime 사용자 정의 직렬화를 구현하십시오.
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.serializerprovinder; org.joda.time.dateTime; import org.joda.time.format.dateTimeformat; import org.joda.time.format.datetimeformatter; import java.io.ioexception;/*** 잭슨은 더 복잡한 형태로 일련의 Joda 시간을 연재하지 않고 더 큰 대상을 가지고 있습니다. * <p>* Jodatime을 직렬화 할 때 Datetime은 문자열로 직렬화 될 수 있으며, 읽기 쉽습니다. **/public class dateTimeserializer는 jsonserializer <dateTime> {private static dateMeformatter dateFormath = dateTimeFormat.forPattern ( "yyyy-mm-dd hh : mm : mm : ss"); @override public void serialize (datetime value, jsongenerator jgen, serializerprovider provider)는 ioexception, jsonprocessingexception {jgen.writestring (value.tostring (dateformatter)); }} 및 DateTime Dessorialization :
DateTimedeserializer
package com.power.demo.util; import com.fasterxml.jackson.core.jsonparser; import com.fasterxml.jackson.core.jsonProcessingException; import com.fasterxml.jackson.databind.deserializationContxt; import com.fasterxml.jackson.databind.jsonderializer; com.fasterxml.jackson.databind.jsonnode; import org.joda.time.datetime; import org.joda.time.format.datetimeformat; import org.joda.time.format.datetimeformatter; import java.io.ioexception;/***jodatime deserialization strings로 변환됩니다. DateTimedEserializer 확장 jsondeserializer <dateTime> {private static 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을 더 지원하기를 바랍니다.