国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|VB|R語(yǔ)言|JavaScript|易語(yǔ)言|vb.net|

服務(wù)器之家 - 編程語(yǔ)言 - Java教程 - Spring Boot使用RestTemplate消費(fèi)REST服務(wù)的幾個(gè)問題記錄

Spring Boot使用RestTemplate消費(fèi)REST服務(wù)的幾個(gè)問題記錄

2021-05-07 11:19Jeff Wong Java教程

這篇文章主要介紹了Spring Boot使用RestTemplate消費(fèi)REST服務(wù)的幾個(gè)問題記錄,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧

我們可以通過spring boot快速開發(fā)rest接口,同時(shí)也可能需要在實(shí)現(xiàn)接口的過程中,通過spring boot調(diào)用內(nèi)外部rest接口完成業(yè)務(wù)邏輯。

在spring boot中,調(diào)用rest api常見的一般主要有兩種方式,通過自帶的resttemplate或者自己開發(fā)http客戶端工具實(shí)現(xiàn)服務(wù)調(diào)用。

resttemplate基本功能非常強(qiáng)大,不過某些特殊場(chǎng)景,我們可能還是更習(xí)慣用自己封裝的工具類,比如上傳文件至分布式文件系統(tǒng)、處理帶證書的https請(qǐng)求等。

本文以resttemplate來舉例,記錄幾個(gè)使用resttemplate調(diào)用接口過程中發(fā)現(xiàn)的問題和解決方案。

一、resttemplate簡(jiǎn)介

1、什么是resttemplate

我們自己封裝的httpclient,通常都會(huì)有一些模板代碼,比如建立連接,構(gòu)造請(qǐng)求頭和請(qǐng)求體,然后根據(jù)響應(yīng),解析響應(yīng)信息,最后關(guān)閉連接。

resttemplate是spring中對(duì)httpclient的再次封裝,簡(jiǎn)化了發(fā)起http請(qǐng)求以及處理響應(yīng)的過程,抽象層級(jí)更高,減少消費(fèi)者的模板代碼,使冗余代碼更少。

其實(shí)仔細(xì)想想spring boot下的很多xxxtemplate類,它們也提供各種模板方法,只不過抽象的層次更高,隱藏了更多細(xì)節(jié)而已。

順便提一下,spring cloud有一個(gè)聲明式服務(wù)調(diào)用feign,是基于netflix feign實(shí)現(xiàn)的,整合了spring cloud ribbon與 spring cloud hystrix,并且實(shí)現(xiàn)了聲明式的web服務(wù)客戶端定義方式。

本質(zhì)上feign是在resttemplate的基礎(chǔ)上對(duì)其再次封裝,由它來幫助我們定義和實(shí)現(xiàn)依賴服務(wù)接口的定義。

2、resttemplate常見方法

常見的rest服務(wù)有很多種請(qǐng)求方式,如get,post,put,delete,head,options等。resttemplate實(shí)現(xiàn)了最常見的方式,用的最多的就是get和post了,調(diào)用api可參考源碼,這里列舉幾個(gè)方法定義(get、post、delete):

methods

?
1
2
3
4
5
6
7
8
9
10
11
public <t> t getforobject(string url, class<t> responsetype, object... urivariables)
 
public <t> responseentity<t> getforentity(string url, class<t> responsetype, object... urivariables)
 
public <t> t postforobject(string url, @nullable object request, class<t> responsetype,object... urivariables)
 
public <t> responseentity<t> postforentity(string url, @nullable object request,class<t> responsetype, object... urivariables)
 
public void delete(string url, object... urivariables)
 
public void delete(uri url)

同時(shí)要注意兩個(gè)較為“靈活”的方法 exchange 和 execute 。

resttemplate暴露的exchange與其它接口的不同:

(1)允許調(diào)用者指定http請(qǐng)求的方法(get,post,delete等)

(2)可以在請(qǐng)求中增加body以及頭信息,其內(nèi)容通過參數(shù)‘httpentity<?>requestentity'描述

(3)exchange支持‘含參數(shù)的類型'(即泛型類)作為返回類型,該特性通過‘parameterizedtypereference<t>responsetype'描述。

resttemplate所有的get,post等等方法,最終調(diào)用的都是execute方法。excute方法的內(nèi)部實(shí)現(xiàn)是將string格式的uri轉(zhuǎn)成了java.net.uri,之后調(diào)用了doexecute方法,doexecute方法的實(shí)現(xiàn)如下:

doexecute

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
 * execute the given method on the provided uri.
 * <p>the {@link clienthttprequest} is processed using the {@link requestcallback};
 * the response with the {@link responseextractor}.
 * @param url the fully-expanded url to connect to
 * @param method the http method to execute (get, post, etc.)
 * @param requestcallback object that prepares the request (can be {@code null})
 * @param responseextractor object that extracts the return value from the response (can be {@code null})
 * @return an arbitrary object, as returned by the {@link responseextractor}
 */
@nullable
protected <t> t doexecute(uri url, @nullable httpmethod method, @nullable requestcallback requestcallback,
  @nullable responseextractor<t> responseextractor) throws restclientexception {
 
 assert.notnull(url, "'url' must not be null");
 assert.notnull(method, "'method' must not be null");
 clienthttpresponse response = null;
 try {
  clienthttprequest request = createrequest(url, method);
  if (requestcallback != null) {
   requestcallback.dowithrequest(request);
  }
  response = request.execute();
  handleresponse(url, method, response);
  if (responseextractor != null) {
   return responseextractor.extractdata(response);
  }
  else {
   return null;
  }
 }
 catch (ioexception ex) {
  string resource = url.tostring();
  string query = url.getrawquery();
  resource = (query != null ? resource.substring(0, resource.indexof('?')) : resource);
  throw new resourceaccessexception("i/o error on " + method.name() +
    " request for \"" + resource + "\": " + ex.getmessage(), ex);
 }
 finally {
  if (response != null) {
   response.close();
  }
 }
}

doexecute方法封裝了模板方法,比如創(chuàng)建連接、處理請(qǐng)求和應(yīng)答,關(guān)閉連接等。

多數(shù)人看到這里,估計(jì)都會(huì)覺得封裝一個(gè)restclient不過如此吧?

3、簡(jiǎn)單調(diào)用

以一個(gè)post調(diào)用為例:

goodsserviceclient

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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;
import org.springframework.beans.factory.annotation.value;
import org.springframework.stereotype.component;
import org.springframework.web.client.resttemplate;
 
/**
 * 商品rest接口客戶端 (demo測(cè)試用)
 **/
@component
public class goodsserviceclient {
 
 //服務(wù)消費(fèi)者調(diào)用的接口url 形如:http://localhost:9090
 @value("${spring.power.serviceurl}")
 private string _serviceurl;
 
 @autowired
 private resttemplate resttemplate;
 
 public clientgetgoodsbygoodsidresponse getgoodsbygoodsid(clientgetgoodsbygoodsidrequest request) {
  string svcurl = getgoodssvcurl() + "/getinfobyid";
 
  clientgetgoodsbygoodsidresponse response = null;
 
  try {
   response = resttemplate.postforobject(svcurl, request, clientgetgoodsbygoodsidresponse.class);
  } catch (exception e) {
   e.printstacktrace();
   response = new clientgetgoodsbygoodsidresponse();
   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/goods", _serviceurl);
  } else {
   url = string.format("%s/api/v1/goods", _serviceurl);
  }
 
  return url;
 }
}

demo里直接resttemplate.postforobject方法調(diào)用,反序列化實(shí)體轉(zhuǎn)換這些resttemplate內(nèi)部封裝搞定。

二、問題匯總

1、no suitable httpmessageconverter found for request type異常

這個(gè)問題通常會(huì)出現(xiàn)在postforobject中傳入對(duì)象進(jìn)行調(diào)用的時(shí)候。

分析resttemplate源碼,在httpentityrequestcallback類的dowithrequest方法中,如果 messageconverters (這個(gè)字段后面會(huì)繼續(xù)提及)列表字段循環(huán)處理的過程中沒有滿足return跳出的邏輯(也就是沒有匹配的httpmessageconverter),則拋出上述異常:

httpentityrequestcallback.dowithrequest

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
@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();
  type requestbodytype = (this.requestentity instanceof requestentity ?
    ((requestentity<?>)this.requestentity).gettype() : requestbodyclass);
  httpheaders httpheaders = httprequest.getheaders();
  httpheaders requestheaders = this.requestentity.getheaders();
  mediatype requestcontenttype = requestheaders.getcontenttype();
  for (httpmessageconverter<?> messageconverter : getmessageconverters()) {
   if (messageconverter instanceof generichttpmessageconverter) {
    generichttpmessageconverter<object> genericconverter =
      (generichttpmessageconverter<object>) messageconverter;
    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 + "]");
      }
 
     }
     genericconverter.write(requestbody, requestbodytype, requestcontenttype, httprequest);
     return;
    }
   }
   else if (messageconverter.canwrite(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 + "]");
     }
 
    }
    ((httpmessageconverter<object>) messageconverter).write(
      requestbody, requestcontenttype, httprequest);
    return;
   }
  }
  string message = "could not write request: no suitable httpmessageconverter found for request type [" +
    requestbodyclass.getname() + "]";
  if (requestcontenttype != null) {
   message += " and content type [" + requestcontenttype + "]";
  }
  throw new restclientexception(message);
 }
}

最簡(jiǎn)單的解決方案是,可以通過包裝http請(qǐng)求頭,并將請(qǐng)求對(duì)象序列化成字符串的形式傳參,參考示例代碼如下:

postforobject

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
 * post請(qǐng)求調(diào)用
 * */
public static string postforobject(resttemplate resttemplate, string url, object params) {
 httpheaders headers = new httpheaders();
 mediatype type = mediatype.parsemediatype("application/json; charset=utf-8");
 headers.setcontenttype(type);
 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);
 
 return result;
}

如果我們還想直接返回對(duì)象,直接反序列化返回的字符串即可:

postforobject

?
1
2
3
4
5
6
7
8
9
10
11
12
/*
 * post請(qǐng)求調(diào)用
 * */
public static <t> t postforobject(resttemplate resttemplate, string url, object params, class<t> clazz) {
 t response = null;
 
 string respstr = postforobject(resttemplate, url, params);
 
 response = serializeutil.deserialize(respstr, clazz);
 
 return response;
}

其中,序列化和反序列化工具比較多,常用的比如fastjson、jackson和gson。

2、no suitable httpmessageconverter found for response type異常

和發(fā)起請(qǐng)求發(fā)生異常一樣,處理應(yīng)答的時(shí)候也會(huì)有問題。

stackoverflow上有人問過相同的問題,根本原因是http消息轉(zhuǎn)換器httpmessageconverter缺少 mime type ,也就是說http在把輸出結(jié)果傳送到客戶端的時(shí)候,客戶端必須啟動(dòng)適當(dāng)?shù)膽?yīng)用程序來處理這個(gè)輸出文檔,這可以通過多種mime(多功能網(wǎng)際郵件擴(kuò)充協(xié)議)type來完成。

對(duì)于服務(wù)端應(yīng)答,很多httpmessageconverter默認(rèn)支持的媒體類型(mimetype)都不同。stringhttpmessageconverter默認(rèn)支持的則是mediatype.text_plain,sourcehttpmessageconverter默認(rèn)支持的則是mediatype.text_xml,formhttpmessageconverter默認(rèn)支持的是mediatype.application_form_urlencoded和mediatype.multipart_form_data,在rest服務(wù)中,我們用到的最多的還是 mappingjackson2httpmessageconverter ,這是一個(gè)比較通用的轉(zhuǎn)化器(繼承自generichttpmessageconverter接口),根據(jù)分析,它默認(rèn)支持的mimetype為mediatype.application_json:

mappingjackson2httpmessageconverter

?
1
2
3
4
5
6
7
8
/**
 * construct a new {@link mappingjackson2httpmessageconverter} with a custom {@link objectmapper}.
 * you can use {@link jackson2objectmapperbuilder} to build it easily.
 * @see jackson2objectmapperbuilder#json()
 */
public mappingjackson2httpmessageconverter(objectmapper objectmapper) {
 super(objectmapper, mediatype.application_json, new mediatype("application", "*+json"));
}

但是有些應(yīng)用接口默認(rèn)的應(yīng)答mimetype不是application/json,比如我們調(diào)用一個(gè)外部天氣預(yù)報(bào)接口,如果使用resttemplate的默認(rèn)配置,直接返回一個(gè)字符串應(yīng)答是沒有問題的:

?
1
2
3
string url = "http://wthrcdn.etouch.cn/weather_mini?city=上海";
string result = resttemplate.getforobject(url, string.class);
clientweatherresultvo vo = serializeutil.deserialize(result, clientweatherresultvo.class);

但是,如果我們想直接返回一個(gè)實(shí)體對(duì)象:

?
1
2
3
string url = "http://wthrcdn.etouch.cn/weather_mini?city=上海";
 
clientweatherresultvo weatherresultvo = resttemplate.getforobject(url, clientweatherresultvo.class);

則直接報(bào)異常:

could not extract response: no suitable httpmessageconverter found for response type [class ]

and content type [application/octet-stream]

很多人碰到過這個(gè)問題,首次碰到估計(jì)大多都比較懵吧,很多接口都是json或者xml或者plain text格式返回的,什么是application/octet-stream?

查看resttemplate源代碼,一路跟蹤下去會(huì)發(fā)現(xiàn) httpmessageconverterextractor 類的extractdata方法有個(gè)解析應(yīng)答及反序列化邏輯,如果不成功,拋出的異常信息和上述一致:

httpmessageconverterextractor.extractdata

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@override
@suppresswarnings({"unchecked", "rawtypes", "resource"})
public t extractdata(clienthttpresponse response) throws ioexception {
 messagebodyclienthttpresponsewrapper responsewrapper = new messagebodyclienthttpresponsewrapper(response);
 if (!responsewrapper.hasmessagebody() || responsewrapper.hasemptymessagebody()) {
  return null;
 }
 mediatype contenttype = getcontenttype(responsewrapper);
 
 try {
  for (httpmessageconverter<?> messageconverter : this.messageconverters) {
   if (messageconverter instanceof generichttpmessageconverter) {
    generichttpmessageconverter<?> genericmessageconverter =
      (generichttpmessageconverter<?>) messageconverter;
    if (genericmessageconverter.canread(this.responsetype, null, contenttype)) {
     if (logger.isdebugenabled()) {
      logger.debug("reading [" + this.responsetype + "] as \"" +
        contenttype + "\" using [" + messageconverter + "]");
     }
     return (t) genericmessageconverter.read(this.responsetype, null, responsewrapper);
    }
   }
   if (this.responseclass != null) {
    if (messageconverter.canread(this.responseclass, contenttype)) {
     if (logger.isdebugenabled()) {
      logger.debug("reading [" + this.responseclass.getname() + "] as \"" +
        contenttype + "\" using [" + messageconverter + "]");
     }
     return (t) messageconverter.read((class) this.responseclass, responsewrapper);
    }
   }
  }
 }
 catch (ioexception | httpmessagenotreadableexception ex) {
  throw new restclientexception("error while extracting response for type [" +
    this.responsetype + "] and content type [" + contenttype + "]", ex);
 }
 
 throw new restclientexception("could not extract response: no suitable httpmessageconverter found " +
   "for response type [" + this.responsetype + "] and content type [" + contenttype + "]");
}

stackoverflow上的解決的示例代碼可以接受,但是并不準(zhǔn)確,常見的mimetype都應(yīng)該加進(jìn)去,貼一下我認(rèn)為正確的代碼:

resttemplateconfig

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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.*;
import org.springframework.http.converter.cbor.mappingjackson2cborhttpmessageconverter;
import org.springframework.http.converter.feed.atomfeedhttpmessageconverter;
import org.springframework.http.converter.feed.rsschannelhttpmessageconverter;
import org.springframework.http.converter.json.gsonhttpmessageconverter;
import 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.springframework.stereotype.component;
import org.springframework.util.classutils;
import org.springframework.web.client.resttemplate;
 
import java.util.arrays;
import java.util.list;
 
@component
public class resttemplateconfig {
 
 private static final boolean romepresent = classutils.ispresent("com.rometools.rome.feed.wirefeed", resttemplate
   .class.getclassloader());
 private static final boolean jaxb2present = classutils.ispresent("javax.xml.bind.binder", resttemplate.class.getclassloader());
 private static final boolean jackson2present = classutils.ispresent("com.fasterxml.jackson.databind.objectmapper", resttemplate.class.getclassloader()) && classutils.ispresent("com.fasterxml.jackson.core.jsongenerator", 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 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());
 
 // 啟動(dòng)的時(shí)候要注意,由于我們?cè)诜?wù)中注入了resttemplate,所以啟動(dòng)的時(shí)候需要實(shí)例化該類的一個(gè)實(shí)例
 @autowired
 private resttemplatebuilder builder;
 
 @autowired
 private objectmapper objectmapper;
 
 // 使用resttemplatebuilder來實(shí)例化resttemplate對(duì)象,spring默認(rèn)已經(jīng)注入了resttemplatebuilder實(shí)例
 @bean
 public resttemplate resttemplate() {
 
  resttemplate resttemplate = builder.build();
 
  list<httpmessageconverter<?>> messageconverters = lists.newarraylist();
  mappingjackson2httpmessageconverter converter = new mappingjackson2httpmessageconverter();
  converter.setobjectmapper(objectmapper);
 
  //不加會(huì)出現(xiàn)異常
  //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) {
   messageconverters.add(converter);
  } else if (gsonpresent) {
   messageconverters.add(new gsonhttpmessageconverter());
  } else if (jsonbpresent) {
   messageconverters.add(new jsonbhttpmessageconverter());
  }
 
  messageconverters.add(new formhttpmessageconverter());
 
  messageconverters.add(new bytearrayhttpmessageconverter());
  messageconverters.add(new stringhttpmessageconverter());
  messageconverters.add(new resourcehttpmessageconverter(false));
  messageconverters.add(new sourcehttpmessageconverter());
  messageconverters.add(new allencompassingformhttpmessageconverter());
  if (romepresent) {
   messageconverters.add(new atomfeedhttpmessageconverter());
   messageconverters.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.setmessageconverters(messageconverters);
 
  return resttemplate;
 }
 
}

看到上面的代碼,再對(duì)比一下resttemplate內(nèi)部實(shí)現(xiàn),就知道我參考了resttemplate的源碼,有潔癖的人可能會(huì)說這一坨代碼有點(diǎn)啰嗦,上面那一堆static final的變量和messageconverters填充數(shù)據(jù)方法,暴露了resttemplate的實(shí)現(xiàn),如果resttemplate修改了,這里也要改,非常不友好,而且看上去一點(diǎn)也不oo。

經(jīng)過分析,resttemplatebuilder.build()構(gòu)造了resttemplate對(duì)象,只要將內(nèi)部mappingjackson2httpmessageconverter修改一下支持的mediatype即可,resttemplate的messageconverters字段雖然是private final的,我們依然可以通過反射修改之,改進(jìn)后的代碼如下:

resttemplateconfig

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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;
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;
 
@component
public class resttemplateconfig {
 
 // 啟動(dòng)的時(shí)候要注意,由于我們?cè)诜?wù)中注入了resttemplate,所以啟動(dòng)的時(shí)候需要實(shí)例化該類的一個(gè)實(shí)例
 @autowired
 private resttemplatebuilder builder;
 
 @autowired
 private objectmapper objectmapper;
 
 // 使用resttemplatebuilder來實(shí)例化resttemplate對(duì)象,spring默認(rèn)已經(jīng)注入了resttemplatebuilder實(shí)例
 @bean
 public resttemplate resttemplate() {
 
  resttemplate resttemplate = builder.build();
 
  list<httpmessageconverter<?>> messageconverters = lists.newarraylist();
  mappingjackson2httpmessageconverter converter = new mappingjackson2httpmessageconverter();
  converter.setobjectmapper(objectmapper);
 
  //不加可能會(huì)出現(xiàn)異常
  //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 {
   //通過反射設(shè)置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);//添加mappingjackson2httpmessageconverter
 
   //添加原有的剩余的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】原有數(shù)量:%s,重新構(gòu)造后數(shù)量:%s"
     , orgconverterlist.size(), messageconverters.size()));
 
  } catch (exception e) {
   e.printstacktrace();
  }
 
  resttemplate.setmessageconverters(messageconverters);
 
  return resttemplate;
 }
}

除了一個(gè)messageconverters字段,看上去我們不再關(guān)心resttemplate那些外部依賴包和內(nèi)部構(gòu)造過程,果然干凈簡(jiǎn)潔好維護(hù)了很多。

3、亂碼問題

這個(gè)也是一個(gè)非常經(jīng)典的問題。解決方案非常簡(jiǎn)單,找到httpmessageconverter,看看默認(rèn)支持的charset。abstractjackson2httpmessageconverter是很多httpmessageconverter的基類,默認(rèn)編碼為utf-8:

abstractjackson2httpmessageconverter

?
1
2
3
4
5
public abstract class abstractjackson2httpmessageconverter extends abstractgenerichttpmessageconverter<object> {
 
 public static final charset default_charset = standardcharsets.utf_8;
 
}

而stringhttpmessageconverter比較特殊,有人反饋過發(fā)生亂碼問題由它默認(rèn)支持的編碼 iso-8859-1 引起:

stringhttpmessageconverter

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * implementation of {@link httpmessageconverter} that can read and write strings.
 *
 * <p>by default, this converter supports all media types ({@code }),
 * and writes with a {@code content-type} of {@code text/plain}. this can be overridden
 * by setting the {@link #setsupportedmediatypes supportedmediatypes} property.
 *
 * @author arjen poutsma
 * @author juergen hoeller
 * @since 3.0
 */
public class stringhttpmessageconverter extends abstracthttpmessageconverter<string> {
 
 public static final charset default_charset = standardcharsets.iso_8859_1;
 
 /**
  * a default constructor that uses {@code "iso-8859-1"} as the default charset.
  * @see #stringhttpmessageconverter(charset)
  */
 public stringhttpmessageconverter() {
  this(default_charset);
 }
 
}

如果在使用過程中發(fā)生亂碼,我們可以通過方法設(shè)置httpmessageconverter支持的編碼,常用的有utf-8、gbk等。

4、反序列化異常

這是開發(fā)過程中容易碰到的又一個(gè)問題。因?yàn)閖ava的開源框架和工具類非常之多,而且版本更迭頻繁,所以經(jīng)常發(fā)生一些意想不到的坑。

以joda time為例,joda time是流行的java時(shí)間和日期框架,但是如果你的接口對(duì)外暴露joda time的類型,比如datetime,那么接口調(diào)用方(同構(gòu)和異構(gòu)系統(tǒng))可能會(huì)碰到序列化難題,反序列化時(shí)甚至直接拋出如下異常:

org.springframework.http.converter.httpmessageconversionexception: type definition error: [simple type, class org.joda.time.chronology]; nested exception is com.fasterxml.jackson.databind.exc.invaliddefinitionexception: cannot construct instance of `org.joda.time.chronology` (no creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [source: (pushbackinputstream);

我在前廠就碰到過,后來為了調(diào)用方便,改回直接暴露java的date類型。

當(dāng)然解決的方案不止這一種,可以使用jackson支持自定義類的序列化和反序列化的方式。在精度要求不是很高的系統(tǒng)里,實(shí)現(xiàn)簡(jiǎn)單的datetime自定義序列化:

datetimeserializer

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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;
 
/**
 * 在默認(rèn)情況下,jackson會(huì)將joda time序列化為較為復(fù)雜的形式,不利于閱讀,并且對(duì)象較大。
 * <p>
 * jodatime 序列化的時(shí)候可以將datetime序列化為字符串,更容易讀
 **/
public class datetimeserializer extends jsonserializer<datetime> {
 
 private static datetimeformatter dateformatter = datetimeformat.forpattern("yyyy-mm-dd hh:mm:ss");
 
 @override
 public void serialize(datetime value, jsongenerator jgen, serializerprovider provider) throws ioexception, jsonprocessingexception {
  jgen.writestring(value.tostring(dateformatter));
 }
}

以及datetime反序列化:

datetimedeserializer

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.power.demo.util;
 
import com.fasterxml.jackson.core.jsonparser;
import com.fasterxml.jackson.core.jsonprocessingexception;
import com.fasterxml.jackson.databind.deserializationcontext;
import com.fasterxml.jackson.databind.jsondeserializer;
import 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 反序列化將字符串轉(zhuǎn)化為datetime
 **/
public class datetimedeserializer extends jsondeserializer<datetime> {
 
 private static datetimeformatter dateformatter = datetimeformat.forpattern("yyyy-mm-dd hh:mm:ss");
 
 @override
 public datetime deserialize(jsonparser jp, deserializationcontext context) throws ioexception, jsonprocessingexception {
  jsonnode node = jp.getcodec().readtree(jp);
  string s = node.astext();
  datetime parse = datetime.parse(s, dateformatter);
  return parse;
 }
}

最后可以在resttemplateconfig類中對(duì)常見調(diào)用問題進(jìn)行匯總處理,可以參考如下:

resttemplateconfig

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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;
 
@component
public class resttemplateconfig {
 
 // 啟動(dòng)的時(shí)候要注意,由于我們?cè)诜?wù)中注入了resttemplate,所以啟動(dòng)的時(shí)候需要實(shí)例化該類的一個(gè)實(shí)例
 @autowired
 private resttemplatebuilder builder;
 
 @autowired
 private objectmapper objectmapper;
 
 // 使用resttemplatebuilder來實(shí)例化resttemplate對(duì)象,spring默認(rèn)已經(jīng)注入了resttemplatebuilder實(shí)例
 @bean
 public resttemplate resttemplate() {
 
  resttemplate resttemplate = builder.build();
 
  //注冊(cè)model,用于實(shí)現(xiàn)jackson joda time序列化和反序列化
  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);
 
  //不加會(huì)出現(xiàn)異常
  //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 {
   //通過反射設(shè)置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);//添加mappingjackson2httpmessageconverter
 
   //添加原有的剩余的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】原有數(shù)量:%s,重新構(gòu)造后數(shù)量:%s"
     , orgconverterlist.size(), messageconverters.size()));
 
  } catch (exception e) {
   e.printstacktrace();
  }
 
  resttemplate.setmessageconverters(messageconverters);
 
  return resttemplate;
 }
}

目前良好地解決了resttemplate常用調(diào)用問題,而且不需要你寫resttemplate幫助工具類了。

上面列舉的這些常見問題,其實(shí).net下面也有,有興趣大家可以搜索一下微軟的httpclient常見使用問題,用過的人都深有體會(huì)。更不用提 restsharp 這個(gè)開源類庫(kù),幾年前用的過程中發(fā)現(xiàn)了非常多的bug,到現(xiàn)在還有一個(gè)反序列化數(shù)組的問題困擾著我們,我只好自己造個(gè)簡(jiǎn)單輪子特殊處理,給我最深刻的經(jīng)驗(yàn)就是,很多看上去簡(jiǎn)單的功能,真的碰到了依然會(huì)花掉不少的時(shí)間去排查和解決,甚至要翻看源碼。所以,我們寫代碼要認(rèn)識(shí)到,越是通用的工具,越需要考慮到特例,可能你需要花80%以上的精力去處理20%的特殊情況,這估計(jì)也是滿足常見的二八定律吧。

參考:

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

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。

原文鏈接:http://www.cnblogs.com/jeffwongishandsome/p/spring-boot-consume-rest-api-by-resttemplate.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 久久久精品久久久 | 日韩中文字幕无码一区二区三区 | 在线视频 中文字幕 | av中文字幕观看 | 日本在线不卡视频 | 久久性色 | 午夜a级理论片915影院 | 羞羞网站在线 | 简单av网| 国产精品99久久久久久动医院 | 精品一区二区三区在线观看 | 日本理伦片午夜理伦片 | 久久影院免费观看 | 精品国产91| 中文字幕 亚洲视频 | 成人在线播放 | 国产精品福利午夜在线观看 | 国产真实乱全部视频 | 精品久久久久久久久久 | 欧美日本精品 | 最近2019中文字幕大全视频10 | 一区二区在线不卡 | av国产精品 | 国产精品久久久久久久久免费高清 | 日本一区二区高清视频 | 日韩在线看片 | 一区二区三区四区日韩 | 欧美一区二区三区免费 | 亚洲成人精品在线观看 | 色爱区综合 | 亚洲视频在线观看视频 | 成人av免费观看 | 亚洲视频在线免费观看 | 日本精品一区二区三区在线观看视频 | 成年人免费网站 | av在线免费观看网站 | 一区二区三区四区在线 | 亚洲精品乱码久久久久久金桔影视 | 天天操夜夜操av | 成人精品一区亚洲午夜久久久 | 爱色av网址 |