使用spring的restTemplate註意點
使用spring的restTemplate註意點
spring的restTemplate可以向一個url發送請求並接收服務器端的響應信息。但在發請求時,會對請求的url值進行編碼再發送。
下面看spring的RestTemplate的源碼
restTemplate基本上發送請求的方法內部都會調用到execute()方法:
expand()方法的代碼如下:
encode()方法的代碼如下:
所以如果使用非spring的服務器接收時,需要進行解碼才能接收到RestTemplate發送的內容。(spring的服務器接收到參數時會自動進行一次解碼,所以使用restTemplate發送消息,Spring的服務器接收時不會出現問題)。
spring的RestTemplate使用指南
前言:現在restful接口越來越廣泛,而如今很多接口摒棄瞭傳統的配置復雜的webService開發模式,在java領域隻需要很簡單的springMvc就可以聲明為一個控制器,再加上service層,就可以直接操作數據庫成為一個靈活的接口。
而我們請求接口的次數也會越來越多(最近我在和一個工具對接的時候,對方公司提供的接口全部由我們主動去調用),一般我們請求接口,都采用Apache Httpclient工具,這個工具穩定,既可以建立長連接,保持不錯的性能,而它唯一的不足就是使用起來麻煩多變,並且要很多層判斷處理,今天我要談的就是spring對httpClient的再封裝工具類,restTemplate,采用模板模式抽象出來的高效工具。
有點類似於jdbcTemplate,今天我們就來一步步揭開它的使用方法
一:restTemplate簡介
1.1:restTemplate的類結構
可以看出它繼承自HttpAccessor這個統一的處理器,然後再繼承自InterceptingHttpAccessor,這個攔截轉換器,最終RestTemplate實現瞭封裝httpClient的模板工具類
1.2:restTemplate的方法
Spring用於同步客戶端HTTP訪問的中心類。它簡化瞭與HTTP服務器的通信,並執行RESTful原則。它處理HTTP連接,使應用程序代碼提供URL,使用可能的模板變量,並提取結果。
註意:默認情況下,RestTemplate依賴於標準的JDK來建立HTTP連接。你可以切換使用不同的HTTP庫,如Apache HttpComponents,Netty和OkHttp通過setRequestFactory屬性。內部模板使用HttpMessageConverter實例將HTTP消息轉換為POJO和從POJO轉換。主要MIME類型的轉換器是默認註冊的,但您也可以註冊其他轉換器通過setMessageConverters
以下是http方法和restTempalte方法的比對映射,可以看出restTemplate提供瞭操作http的方法,其中exchange方法可以用來做任何的請求,一般我們都是用它來封裝不同的請求方式。
二:restTemplate的配置方法
2.1:在springboot中的配置
springboot是一款簡化傳統xml配置式的開發方式,主要采用註解的方式來代替傳統繁瑣的xml配置,接下來我們就用springboot提供的註解來配置restTemplate:
@Configuration public class RestTemplateConfig { private static final Logger logger= LoggerFactory.getLogger(RestTemplateConfig.class); @Bean public RestTemplate restTemplate() { // 添加內容轉換器,使用默認的內容轉換器 RestTemplate restTemplate = new RestTemplate(httpRequestFactory()); // 設置編碼格式為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() { // 長連接保持30秒 PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS); //設置整個連接池最大連接數 根據自己的場景決定 connectionManager.setMaxTotal(500); //同路由的並發數,路由是對maxTotal的細分 connectionManager.setDefaultMaxPerRoute(500); //requestConfig RequestConfig requestConfig = RequestConfig.custom() //服務器返回數據(response)的時間,超過該時間拋出read timeout .setSocketTimeout(10000) //連接上服務器(握手成功)的時間,超出該時間拋出connect timeout .setConnectTimeout(5000) //從連接池中獲取連接的超時時間,超過該時間未拿到可用連接,會拋出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) // 保持長連接配置,需要在頭添加Keep-Alive .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()) //重試次數,默認是3次,沒有開啟 .setRetryHandler(new DefaultHttpRequestRetryHandler(2, true)) .build(); } }
首先解釋以下@configuration,它的主要作用就是在spring容器啟動的時候,初始化IOC,使用瞭這個註解,那麼該類就會在spring啟動的時候,把@Bean註解標識的類進行依賴註入。@Bean理解的話,就好比在配置文件中配置<bean>.接下來就是在restTemplate的構造方法中添加httpRequest的工廠,使用連接池來優化http通信,默認使用長連接時間為30秒,再設置路由讓http連接定向到指定的IP,然後設置並發數。再就是設置請求配置的超時時間,為瞭防止請求時間過長而引起資源的過渡浪費。如果在超過設置的timeout還沒有數據返回,就直接斷開連接。headers是添加默認的請求頭,這裡設置瞭傳送的格式為json,語言為中-英等等屬性。HttpClientBuilder.create設置請求頭到HttpClient,然後在設置保持的時間,重試的次數,註入給httpClient進行封裝。
在bean中的HttpMessageConverter,就是http信息轉換器,它的主要作用就是轉換和解析返回來的json數據,restTemplate默認使用jackson來作為底層的解析工具,而其它的比如Gson,fastjson等等第三方開源庫放在headers這個list中,如果要使用,可以通過以下代碼進行改變:
this.restTemplate.getMessageConverters().clear(); final List<HttpMessageConverter<?>> myHttpMessageConverter = new ArrayList<HttpMessageConverter<?>>(); //自己實現的messgeConverter HttpMessageConverter<Object> messageConverter = new MyHttpMessageConverter<Object>(); myHttpMessageConverter.add(messageConverter); this.restTemplate.setMessageConverters(myHttpMessageConverter);
三:restUtil工具類
restUtil就是通過包裝restTemplate暴露出面向外界的方法,通過高度封裝,可以隱藏內部細節,簡單使用,在使用它的時候,我們隻需要傳入請求的url和對應的參數,然後就可以取到結果瞭。參數一般有兩種形式,一種是直接傳入json,另一種是key、value形式的,key/value形式的,可以直接使用execute方法,傳入url和請求的方法類型就可以瞭。在開頭看到瞭restTemplate基本上是支持所有http請求的,接下來的工具類就介紹一下post和get請求的主要封裝方法
@Component public class RestUtil { @Autowired private RestTemplate restTemplate; //一些自定義的請求頭參數 public static final String supplierID=""; public static final String interfacekey= ""; /** * DLT專用執行方法 * @param param 請求參數:可以添加一些常量請求值 * @param url 訪問的url * @param method 請求的方法 * @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(); } /** * 獲取默認的頭請求信息 * @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; } /** * 獲取默認的參數 * @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 { // 確定計算方法 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請求 * @param url 請求的url * @param jsonData 請求的json * @return */ public String restGet(String url,String jsonData){ return request(url, jsonData,HttpMethod.GET); } /** * @param url 請求的url * @param jsonData json數據 * @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請求獲取實體類 * @param url 請求的url * @param responseType 返回的類型 * @param parms 不限定個數的參數 * @param <T> 泛型 * @return */ public <T> T getForEntity(String url,Class<T> responseType,Object... parms){ return (T) restTemplate.getForEntity(url,responseType,parms); } /** * Get請求 * @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來搭建一個簡單的rest請求鏈接
我們來模擬一個請求,傳入年齡和性別、身高,計算出標準體重的接口,這段代碼比較簡單,我隻給出示范代碼:
@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, "缺少請求參數或者參數錯誤", 0d); } double condition = getStandardWeight(sex, age, height); return new ResultModel(200, "請求成功", condition); } /** * 獲取標準體重 * * @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; }
可以看到我們的控制器有個映射weight請求的方法,通過傳入年齡、身高、性別,就可以計算出標準體重,我們來啟動springBoot,先試著用瀏覽器訪問一下,可以看出如下結果:
4.2:為瞭表明接口是通的,我們再用postman來試一下,可以看到返回結果正確:
4.3:在springboot裡引入testNg單元測試類,測試一下訪問這個鏈接的結果:
public class TestRestManager extends OrderProviderApplicationTests { @Autowired private RestUtil restUtil; /** * 請求方法為GEt * @return */ @Test private void requestGet(){ String url="http://localhost:8080/weight?age={age}&sex={sex}&height={height}"; //組裝請求參數 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); } }
結果返回以下內容:
五:總結
本篇博客講述瞭RestTemplate的簡介,還有配置方法和使用示例,作為一款非常不錯的rest請求工具,屏蔽瞭復雜的HttpClient的實現細節,向外暴露出簡單、易於使用的接口,使得我們的開發工作越來越簡單、高效,更多的方法工具可以研究一下restTemplate的具體Api,打開源碼,一切都瞭如指掌。在平時的工作中,應該多發現這種工具類,從而來代替一些傳統的工具,對於提升工作效率有著突飛猛進的效果和不可言喻的方便。
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- 關於RestTemplate的使用深度解析
- java實用型-高並發下RestTemplate的正確使用說明
- Spring遠程調用HttpClient/RestTemplate的方法
- spring boot RestTemplate 發送get請求的踩坑及解決
- 解決RestTemplate加@Autowired註入不瞭的問題