使用spring的restTemplate注意點(diǎn)
使用spring的restTemplate注意點(diǎn)
spring的restTemplate可以向一個(gè)url發(fā)送請(qǐng)求并接收服務(wù)器端的響應(yīng)信息。但在發(fā)請(qǐng)求時(shí),會(huì)對(duì)請(qǐng)求的url值進(jìn)行編碼再發(fā)送。
下面看spring的RestTemplate的源碼
restTemplate基本上發(fā)送請(qǐng)求的方法內(nèi)部都會(huì)調(diào)用到execute()方法:

expand()方法的代碼如下:

encode()方法的代碼如下:

所以如果使用非spring的服務(wù)器接收時(shí),需要進(jìn)行解碼才能接收到RestTemplate發(fā)送的內(nèi)容。(spring的服務(wù)器接收到參數(shù)時(shí)會(huì)自動(dòng)進(jìn)行一次解碼,所以使用restTemplate發(fā)送消息,Spring的服務(wù)器接收時(shí)不會(huì)出現(xiàn)問題)。
spring的RestTemplate使用指南
前言:現(xiàn)在restful接口越來越廣泛,而如今很多接口摒棄了傳統(tǒng)的配置復(fù)雜的webService開發(fā)模式,在java領(lǐng)域只需要很簡(jiǎn)單的springMvc就可以聲明為一個(gè)控制器,再加上service層,就可以直接操作數(shù)據(jù)庫成為一個(gè)靈活的接口。
而我們請(qǐng)求接口的次數(shù)也會(huì)越來越多(最近我在和一個(gè)工具對(duì)接的時(shí)候,對(duì)方公司提供的接口全部由我們主動(dòng)去調(diào)用),一般我們請(qǐng)求接口,都采用Apache Httpclient工具,這個(gè)工具穩(wěn)定,既可以建立長(zhǎng)連接,保持不錯(cuò)的性能,而它唯一的不足就是使用起來麻煩多變,并且要很多層判斷處理,今天我要談的就是spring對(duì)httpClient的再封裝工具類,restTemplate,采用模板模式抽象出來的高效工具。
有點(diǎn)類似于jdbcTemplate,今天我們就來一步步揭開它的使用方法
一:restTemplate簡(jiǎn)介
1.1:restTemplate的類結(jié)構(gòu)

可以看出它繼承自HttpAccessor這個(gè)統(tǒng)一的處理器,然后再繼承自InterceptingHttpAccessor,這個(gè)攔截轉(zhuǎn)換器,最終RestTemplate實(shí)現(xiàn)了封裝httpClient的模板工具類
1.2:restTemplate的方法
Spring用于同步客戶端HTTP訪問的中心類。它簡(jiǎn)化了與HTTP服務(wù)器的通信,并執(zhí)行RESTful原則。它處理HTTP連接,使應(yīng)用程序代碼提供URL,使用可能的模板變量,并提取結(jié)果。
注意:默認(rèn)情況下,RestTemplate依賴于標(biāo)準(zhǔn)的JDK來建立HTTP連接。你可以切換使用不同的HTTP庫,如Apache HttpComponents,Netty和OkHttp通過setRequestFactory屬性。內(nèi)部模板使用HttpMessageConverter實(shí)例將HTTP消息轉(zhuǎn)換為POJO和從POJO轉(zhuǎn)換。主要MIME類型的轉(zhuǎn)換器是默認(rèn)注冊(cè)的,但您也可以注冊(cè)其他轉(zhuǎn)換器通過setMessageConverters
以下是http方法和restTempalte方法的比對(duì)映射,可以看出restTemplate提供了操作http的方法,其中exchange方法可以用來做任何的請(qǐng)求,一般我們都是用它來封裝不同的請(qǐng)求方式。

二:restTemplate的配置方法
2.1:在springboot中的配置
springboot是一款簡(jiǎn)化傳統(tǒng)xml配置式的開發(fā)方式,主要采用注解的方式來代替?zhèn)鹘y(tǒng)繁瑣的xml配置,接下來我們就用springboot提供的注解來配置restTemplate:
@Configuration
public class RestTemplateConfig {
private static final Logger logger= LoggerFactory.getLogger(RestTemplateConfig.class);
@Bean
public RestTemplate restTemplate() {
// 添加內(nèi)容轉(zhuǎn)換器,使用默認(rèn)的內(nèi)容轉(zhuǎn)換器
RestTemplate restTemplate = new RestTemplate(httpRequestFactory());
// 設(shè)置編碼格式為UTF-8
List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
HttpMessageConverter<?> converterTarget = null;
for (HttpMessageConverter<?> item : converterList) {
if (item.getClass() == StringHttpMessageConverter.class) {
converterTarget = item;
break;
}
}
if (converterTarget != null) {
converterList.remove(converterTarget);
}
HttpMessageConverter<?> converter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
converterList.add(1,converter);
LOGGER.info("-----restTemplate-----初始化完成");
return restTemplate;
}
@Bean
public ClientHttpRequestFactory httpRequestFactory() {
return new HttpComponentsClientHttpRequestFactory(httpClient());
}
@Bean
public HttpClient httpClient() {
// 長(zhǎng)連接保持30秒
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
//設(shè)置整個(gè)連接池最大連接數(shù) 根據(jù)自己的場(chǎng)景決定
connectionManager.setMaxTotal(500);
//同路由的并發(fā)數(shù),路由是對(duì)maxTotal的細(xì)分
connectionManager.setDefaultMaxPerRoute(500);
//requestConfig
RequestConfig requestConfig = RequestConfig.custom()
//服務(wù)器返回?cái)?shù)據(jù)(response)的時(shí)間,超過該時(shí)間拋出read timeout
.setSocketTimeout(10000)
//連接上服務(wù)器(握手成功)的時(shí)間,超出該時(shí)間拋出connect timeout
.setConnectTimeout(5000)
//從連接池中獲取連接的超時(shí)時(shí)間,超過該時(shí)間未拿到可用連接,會(huì)拋出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
.setConnectionRequestTimeout(500)
.build();
//headers
List<Header> headers = new ArrayList<>();
headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"));
headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
headers.add(new BasicHeader("Accept-Language", "zh-CN"));
headers.add(new BasicHeader("Connection", "Keep-Alive"));
headers.add(new BasicHeader("Content-type", "application/json;charset=UTF-8"));
return HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(connectionManager)
.setDefaultHeaders(headers)
// 保持長(zhǎng)連接配置,需要在頭添加Keep-Alive
.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
//重試次數(shù),默認(rèn)是3次,沒有開啟
.setRetryHandler(new DefaultHttpRequestRetryHandler(2, true))
.build();
}
}
首先解釋以下@configuration,它的主要作用就是在spring容器啟動(dòng)的時(shí)候,初始化IOC,使用了這個(gè)注解,那么該類就會(huì)在spring啟動(dòng)的時(shí)候,把@Bean注解標(biāo)識(shí)的類進(jìn)行依賴注入。@Bean理解的話,就好比在配置文件中配置<bean>.接下來就是在restTemplate的構(gòu)造方法中添加httpRequest的工廠,使用連接池來優(yōu)化http通信,默認(rèn)使用長(zhǎng)連接時(shí)間為30秒,再設(shè)置路由讓http連接定向到指定的IP,然后設(shè)置并發(fā)數(shù)。再就是設(shè)置請(qǐng)求配置的超時(shí)時(shí)間,為了防止請(qǐng)求時(shí)間過長(zhǎng)而引起資源的過渡浪費(fèi)。如果在超過設(shè)置的timeout還沒有數(shù)據(jù)返回,就直接斷開連接。headers是添加默認(rèn)的請(qǐng)求頭,這里設(shè)置了傳送的格式為json,語言為中-英等等屬性。HttpClientBuilder.create設(shè)置請(qǐng)求頭到HttpClient,然后在設(shè)置保持的時(shí)間,重試的次數(shù),注入給httpClient進(jìn)行封裝。
在bean中的HttpMessageConverter,就是http信息轉(zhuǎn)換器,它的主要作用就是轉(zhuǎn)換和解析返回來的json數(shù)據(jù),restTemplate默認(rèn)使用jackson來作為底層的解析工具,而其它的比如Gson,fastjson等等第三方開源庫放在headers這個(gè)list中,如果要使用,可以通過以下代碼進(jìn)行改變:
this.restTemplate.getMessageConverters().clear();
final List<HttpMessageConverter<?>> myHttpMessageConverter = new ArrayList<HttpMessageConverter<?>>();
//自己實(shí)現(xiàn)的messgeConverter
HttpMessageConverter<Object> messageConverter = new MyHttpMessageConverter<Object>();
myHttpMessageConverter.add(messageConverter);
this.restTemplate.setMessageConverters(myHttpMessageConverter);
三:restUtil工具類
restUtil就是通過包裝restTemplate暴露出面向外界的方法,通過高度封裝,可以隱藏內(nèi)部細(xì)節(jié),簡(jiǎn)單使用,在使用它的時(shí)候,我們只需要傳入請(qǐng)求的url和對(duì)應(yīng)的參數(shù),然后就可以取到結(jié)果了。參數(shù)一般有兩種形式,一種是直接傳入json,另一種是key、value形式的,key/value形式的,可以直接使用execute方法,傳入url和請(qǐng)求的方法類型就可以了。在開頭看到了restTemplate基本上是支持所有http請(qǐng)求的,接下來的工具類就介紹一下post和get請(qǐng)求的主要封裝方法
@Component
public class RestUtil {
@Autowired
private RestTemplate restTemplate;
//一些自定義的請(qǐng)求頭參數(shù)
public static final String supplierID="";
public static final String interfacekey= "";
/**
* DLT專用執(zhí)行方法
* @param param 請(qǐng)求參數(shù):可以添加一些常量請(qǐng)求值
* @param url 訪問的url
* @param method 請(qǐng)求的方法
* @return
*/
public String execute(Map<String,Object> param, String url, HttpMethod method){
HttpHeaders headers = this.getDefaultHeader();
Map<String,Object> requestor = this.getDefaultParam();
param.put("requestor",requestor);
param.put("supplierID",supplierID);
HttpEntity<Map<String,Object>> requestEntity = new HttpEntity<>(param, headers);
ResponseEntity<String> response = restTemplate.exchange(url,method, requestEntity, String.class);
return response.getBody();
}
/**
* 獲取默認(rèn)的頭請(qǐng)求信息
* @return
*/
public HttpHeaders getDefaultHeader(){
String timestamp = ""+System.currentTimeMillis();
String signature = EncoderByMd5(supplierID + timestamp + interfacekey);
HttpHeaders headers = new HttpHeaders();
headers.add("signature", signature);
headers.add("timestamp", timestamp);
return headers;
}
/**
* 獲取默認(rèn)的參數(shù)
* @return
*/
public Map<String,Object> getDefaultParam(){
Map<String,Object> defParam = new HashMap<>();
defParam.put("invoker","xx");
defParam.put("operatorName","xx");
return defParam;
}
/**
* 通過MD5加密
* @param str
* @return
*/
public static String EncoderByMd5(String str){
if (str == null) {
return null;
}
try {
// 確定計(jì)算方法
MessageDigest md5 = MessageDigest.getInstance("MD5");
BASE64Encoder base64en = new BASE64Encoder();
// 加密后的字符串
return base64en.encode(md5.digest(str.getBytes("utf-8"))).toUpperCase();
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
return null;
}
}
/**
* get請(qǐng)求
* @param url 請(qǐng)求的url
* @param jsonData 請(qǐng)求的json
* @return
*/
public String restGet(String url,String jsonData){
return request(url, jsonData,HttpMethod.GET);
}
/**
* @param url 請(qǐng)求的url
* @param jsonData json數(shù)據(jù)
* @param httpMethod
* @return
*/
private String request(String url, String jsonData,HttpMethod httpMethod) {
ResponseEntity<String> response=null;
try {
if (Check.isEmpty(url)) {
throw new IllegalArgumentException();
}
HttpEntity<String> requestEntity = new HttpEntity<String>(jsonData);
response = restTemplate.exchange(url, httpMethod, requestEntity, String.class);
}catch (Exception ex){
ex.printStackTrace();
return "";
}
return response.getBody().toString();
}
/**
* Get請(qǐng)求獲取實(shí)體類
* @param url 請(qǐng)求的url
* @param responseType 返回的類型
* @param parms 不限定個(gè)數(shù)的參數(shù)
* @param <T> 泛型
* @return
*/
public <T> T getForEntity(String url,Class<T> responseType,Object... parms){
return (T) restTemplate.getForEntity(url,responseType,parms);
}
/**
* Get請(qǐng)求
* @param url
* @param parm
* @return
*/
public String get(String url,Map<String,Object> parm){
return restTemplate.getForEntity(url,String.class,parm).getBody();
}
}
四:使用示例
4.1:首先我們用springBoot來搭建一個(gè)簡(jiǎn)單的rest請(qǐng)求鏈接
我們來模擬一個(gè)請(qǐng)求,傳入年齡和性別、身高,計(jì)算出標(biāo)準(zhǔn)體重的接口,這段代碼比較簡(jiǎn)單,我只給出示范代碼:
@SpringBootApplication
@RestController
public class HealApplication {
@RequestMapping(value = "weight", method = RequestMethod.GET)
public ResultModel getWeight(@RequestParam(value = "height", required = false) Integer height, @RequestParam(value = "sex", required = false) Integer sex, @RequestParam(value = "age", required = false) Integer age) {
if (height == null || age == null || sex == null || (!sex.equals(0) && !sex.equals(1))) {
return new ResultModel(400, "缺少請(qǐng)求參數(shù)或者參數(shù)錯(cuò)誤", 0d);
}
double condition = getStandardWeight(sex, age, height);
return new ResultModel(200, "請(qǐng)求成功", condition);
}
/**
* 獲取標(biāo)準(zhǔn)體重
*
* @param sex 性別 1:男 2:女
* @param age 年齡
* @param height
* @return 體重(單位:kg)
*/
public double getStandardWeight(int sex, int age, int height) {
double weight = 0.0;
switch (sex) {
//男性
case 1:
if (age < 12 && age > 2) {
weight = age * 2 + 12;
} else if (age > 12) {
weight = (height - 150) * 0.6 + 50;
}
break;
case 0:
if (age < 12 && age > 2) {
weight = age * 2 + 12;
} else if (age > 12) {
weight = (height - 100) * 0.6 + 50;
}
break;
default:
weight = 0;
break;
}
return weight;
}
可以看到我們的控制器有個(gè)映射weight請(qǐng)求的方法,通過傳入年齡、身高、性別,就可以計(jì)算出標(biāo)準(zhǔn)體重,我們來啟動(dòng)springBoot,先試著用瀏覽器訪問一下,可以看出如下結(jié)果:


4.2:為了表明接口是通的,我們?cè)儆胮ostman來試一下,可以看到返回結(jié)果正確:

4.3:在springboot里引入testNg單元測(cè)試類,測(cè)試一下訪問這個(gè)鏈接的結(jié)果:
public class TestRestManager extends OrderProviderApplicationTests {
@Autowired
private RestUtil restUtil;
/**
* 請(qǐng)求方法為GEt
* @return
*/
@Test
private void requestGet(){
String url="http://localhost:8080/weight?age={age}&sex={sex}&height={height}";
//組裝請(qǐng)求參數(shù)
Map<String,Object> parmMap =new HashMap<String,Object>();
parmMap.put("age",35);
parmMap.put("sex",1);
parmMap.put("height",178);
String result = restUtil.get(url, parmMap);
System.out.println(result);
}
}
結(jié)果返回以下內(nèi)容:

五:總結(jié)
本篇博客講述了RestTemplate的簡(jiǎn)介,還有配置方法和使用示例,作為一款非常不錯(cuò)的rest請(qǐng)求工具,屏蔽了復(fù)雜的HttpClient的實(shí)現(xiàn)細(xì)節(jié),向外暴露出簡(jiǎn)單、易于使用的接口,使得我們的開發(fā)工作越來越簡(jiǎn)單、高效,更多的方法工具可以研究一下restTemplate的具體Api,打開源碼,一切都了如指掌。在平時(shí)的工作中,應(yīng)該多發(fā)現(xiàn)這種工具類,從而來代替一些傳統(tǒng)的工具,對(duì)于提升工作效率有著突飛猛進(jìn)的效果和不可言喻的方便。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
MyBatisPlus使用${ew.customSqlSegment}別名問題解決
在使用MyBatisPlus進(jìn)行連表查詢時(shí),可能遇到因${ew.customSqlSegment}無法加別名的問題,本文就來介紹一下如何解決,感興趣的可以了解一下2024-10-10
java拼接字符串時(shí)去掉最后一個(gè)多余逗號(hào)的方法
這篇文章主要介紹了java拼接字符串時(shí)去掉最后一個(gè)多余逗號(hào)的方法,實(shí)例分析了java操作字符串的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03
SpringBoot開發(fā)實(shí)戰(zhàn)之自動(dòng)配置
SpringBoot的核心就是自動(dòng)配置,自動(dòng)配置又是基于條件判斷來配置Bean,下面這篇文章主要給大家介紹了關(guān)于SpringBoot開發(fā)實(shí)戰(zhàn)之自動(dòng)配置的相關(guān)資料,需要的朋友可以參考下2021-08-08
Java實(shí)現(xiàn)大文件的分割與合并的方法詳解
這篇文章主要為大家詳細(xì)介紹了如何利用Java語言實(shí)現(xiàn)大文件的分割與合并,以及分割后又再次合并操作,文中示例代碼講解詳細(xì),感興趣的可以了解一下2022-08-08
Java編程思想里的泛型實(shí)現(xiàn)一個(gè)堆棧類 分享
這篇文章介紹了Java編程思想里的泛型實(shí)現(xiàn)一個(gè)堆棧類,有需要的朋友可以參考一下2013-07-07
Java讀取json數(shù)據(jù)并存入數(shù)據(jù)庫的操作代碼
很多朋友問大佬們JAVA怎么把json存入數(shù)據(jù)庫啊,這一問題就把我難倒了,糾結(jié)如何操作呢,下面小編把我的經(jīng)驗(yàn)分享給大家,感興趣的朋友一起看看吧2021-08-08
Struts2之Action接收請(qǐng)求參數(shù)和攔截器詳解
這篇文章主要介紹了Struts2之Action接收請(qǐng)求參數(shù)和攔截器詳解,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05

