寫在前面
作爲一名Java開發者,我們怎麼都繞不開調用外部接口的場景,調用的方式要麼是通過Http協議來調用,要麼是通過RPC協議來調用,通過Http協議調用的話我們就需要用到Http的Api。比較常用的有Apache的HttpClient和原生的HttpURLConnection。這些Api都比較好用,但是我們今天要介紹一種更加好用API,Spring自帶的RestTemplate,能力更強,使用更方便。
怎麼用?
SpringBoot項目
SpringBoot項目中,只需要引入spring-boot-starter-web
依賴就可以了,其實spring-boot-starter-web
依賴也是SpringBoot項目必備的一個依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
設置超時時間
引入依賴之後,就來開始使用吧,任何一個Http的Api我們都可以設置請求的連接超時時間,請求超時時間,如果不設置的話,就可能會導致連接得不到釋放,造成內存溢出。這個是我們需要重點注意的點,下面就來看看RestTemplate
如何來設置超時時間呢?我們可以在SimpleClientHttpRequestFactory類中設置這兩個時間,然後將factory傳給RestTemplate實例,設置如下:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(@Qualifier("simpleClientHttpRequestFactory") ClientHttpRequestFactory factory){
//返回restTemplate的實例
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
//設置請求超時時間是6秒
factory.setReadTimeout(6000);
//設置連接超時時間是6秒
factory.setConnectTimeout(6000);
return factory;
}
}
說完了RestTemplate的相關設置,下面就來看看平時我們用的最多兩種請求方法:get方法和post方法吧。
GET請求
RestTemplate中提供的get請求的方法主要分爲兩類,一類是隻返回請求體,一類是返回ResponseEntity對象,這個對象主要是包裝了Http請求的響應狀態status,響應頭headers,和響應體body。後面我們會詳細介紹。首先來看看getForObject方法。
返回業務對象類getForObject方法
getForObject
方法的重載方法有如下三個:
/**
方法一,直接將參數添加到url上面。
* Retrieve a representation by doing a GET on the specified URL.
* The response (if any) is converted and returned.
* <p>URI Template variables are expanded using the given URI variables, if any.
* @param url the URL 請求地址
* @param responseType the type of the return value 響應體的類型
* @param uriVariables the variables to expand the template 傳入的參數
* @return the converted object
*/
@Nullable
<T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;
/**
方法二,通過Map來提交參數。
* Retrieve a representation by doing a GET on the URI template.
* The response (if any) is converted and returned.
* <p>URI Template variables are expanded using the given map.
* @param url the URL
* @param responseType the type of the return value
* @param uriVariables the map containing variables for the URI template
* @return the converted object
*/
@Nullable
<T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
/**
方法三,用URI來請求。
* Retrieve a representation by doing a GET on the URL .
* The response (if any) is converted and returned.
* @param url the URL
* @param responseType the type of the return value
* @return the converted object
*/
@Nullable
<T> T getForObject(URI url, Class<T> responseType) throws RestClientException;
下面定義了一個接口,用來測試上面三個方法的使用,這個接口有兩個參數,分別是userId和userName。
根據傳入的userId和userName來查詢用戶,如果可以查詢的到的話,則返回查詢到的用戶,如果查詢不到的話,則返回找不到數據。
響應體是JSON格式的。
/**
* get請求獲取用戶
*
* @param userName 用戶名
* @param userId 用戶id
* @return
*/
@ResponseBody
@RequestMapping(value = "/getUser.do")
public ResultData<User> getUserByName(
@RequestParam(name = "userId",required = false) Integer userId,
@RequestParam(name = "userName",required = false) String userName) {
if (StringUtils.isAnyBlank(userName)) {
return new ResultData<>(HttpStatus.BAD_REQUEST.value(), null, "參數不能爲空");
}
List<User> userList = new ArrayList<>();
for (int i = 1; i <= 2; i++) {
User user = new User();
user.setUserId(i);
user.setUserName("張三" + i);
user.setAge(20 + i);
userList.add(user);
}
for (User user : userList) {
if (userName.equals(user.getUserName()) && userId.equals(user.getUserId())) {
return new ResultData(HttpStatus.OK.value(), user, "成功");
}
}
return new ResultData<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), null, "找不到數據");
}
下面我們就分別用那三個方法請求/getUser.do
接口進行測試:
@Test
public void getForObjectTest() {
String baseUrl = "http://localhost:8081/testRestTemplateApp/getUser.do";
//方法一: 直接拼接參數,推薦使用
String url =baseUrl+"?userName=張三1&userId=1";
ResultData resultData = restTemplate.getForObject(url, ResultData.class);
System.out.println("*****GET直接拼接參數查詢返回結果={}" + JSON.toJSONString(resultData));
//方法一:傳參替換,推薦使用
url = baseUrl+"?userName={?}&userId={?}";
resultData = restTemplate.getForObject(url, ResultData.class, "張三2",2);
System.out.println("*****GET傳參替換查詢返回結果={}" + JSON.toJSONString(resultData));
//方法一:傳參替換,使用String.format,推薦使用
url = baseUrl + String.format("?userName=%s&userId=%s", "張三2",2);
resultData = restTemplate.getForObject(url, ResultData.class);
System.out.println("******GET使用String.format查詢返回結果={}" + JSON.toJSONString(resultData));
//方法二:使用Map,不推薦使用
url = baseUrl + "?userName={userName}&userId={userId}";
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("userName", "張三1");
paramMap.put("userId",1);
resultData = restTemplate.getForObject(url, ResultData.class, paramMap);
System.out.println("******GET使用Map查詢返回結果={}" + JSON.toJSONString(resultData));
//方法三:使用URI,不推薦使用
URI uri = URI.create(baseUrl+"?userName=%E5%BC%A0%E4%B8%891&userId=1");
ResultData resultData1 = restTemplate.getForObject(uri, ResultData.class);
System.out.println("******GET使用URI查詢返回結果={}" + JSON.toJSONString(resultData1));
}
運行結果如下:
需要注意的是:
- 傳參替換使用
{?}
來表示坑位,根據實際的傳參順序來填充,如下:
url = baseUrl+"?userName={?}&userId={?}";
resultData = restTemplate.getForObject(url, ResultData.class, "張三2",2);
- 使用
{xx}
來傳遞參數時,這個xx對應的就是map中的key
url = baseUrl + "?userName={userName}&userId={userId}";
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("userName", "張三1");
paramMap.put("userId",1);
- 當響應頭是
application/json;charset=UTF-8
格式的時候,返回的數據類型可以直接寫String.class
,如下
String url ="http://localhost:8081/testRestTemplateApp/getUser.do?userName=張三1&userId=1";
String resultData = restTemplate.getForObject(url, String.class);
- 不推薦直接使用方法三傳入URI,原因主要有如下兩點: 1. 傳入的參數包含中文時必須要轉碼,直接傳中文會報400的錯誤,2. 響應的結果必須要跟接口的返回值保持一致,不然回報406的錯誤。
//userName不能直接傳入張三1,不然會報400的錯誤
URI uri = URI.create(baseUrl+"?userName=%E5%BC%A0%E4%B8%891&userId=1");
//responseType不能傳入String.class,不然會報406的錯誤
ResultData resultData1 = restTemplate.getForObject(uri, ResultData.class);
說完了getForObject,下面來看看getForEntity的方法,這三個方法跟上面的getForObject三個方法分別對應,只是返回值不同。
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)
throws RestClientException;
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables)
throws RestClientException;
<T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException;
這裏只列舉一個參數拼接的方式來舉例說明:
String baseUrl = "http://localhost:8081/testRestTemplateApp/getUser.do";
//參數拼接的方式
String url =baseUrl+"?userName=張三1&userId=1";
ResponseEntity<ResultData> entity = restTemplate.getForEntity(url, ResultData.class);
System.out.println("*****參數拼接查詢返回結果={}" + JSON.toJSONString(entity));
運行後的結果如下:有響應頭heads,有響應體body,有響應狀態statusCodeValue等。
{"body":{"busCode":200,"data":{"userId":1,"userName":"張三1","age":21},"msg":"成功"},"headers":{"Content-Type":["application/json;charset=UTF-8"],"Transfer-Encoding":["chunked"],"Date":["Fri, 06 Mar 2020 05:42:08 GMT"],"Keep-Alive":["timeout=60"],"Connection":["keep-alive"]},"statusCode":"OK","statusCodeValue":200}
POST 請求
說完了get請求相關的方法之後,接下來我們來看看post請求相關的方法,首先還是來看postForObject的三個重載方法。
/**
* @param url the URL 請求地址
* @param request the Object to be POSTed (may be {@code null}) 請求體,可以傳入一個Bean對象,也可以傳入HttpEntity對象,包裝請求頭
* @param responseType the type of the return value 響應對象的類型
* @param uriVariables the variables to expand the template 傳入的參數
* @return the converted object
* @see HttpEntity
*/
@Nullable
<T> T postForObject(String url, @Nullable Object request, Class<T> responseType,
Object... uriVariables) throws RestClientException;
/**
* @param url the URL 請求地址
* @param request the Object to be POSTed (may be {@code null}) 請求體,可以傳入一個Bean對象,也可以傳入HttpEntity對象,包裝請求頭
* @param responseType the type of the return value 響應對象的類型
* @param uriVariables the variables to expand the template 傳入的map
* @return the converted object
* @see HttpEntity
*/
@Nullable
<T> T postForObject(String url, @Nullable Object request, Class<T> responseType,
Map<String, ?> uriVariables) throws RestClientException;
/**
* @param url the URL
* @param request the Object to be POSTed (may be {@code null})
* @param responseType the type of the return value
* @return the converted object
* @see HttpEntity
*/
@Nullable
<T> T postForObject(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException;
還是用上面的/getUser.do
接口進行測試。
@Test
public void testPostForObjectForForm() {
String baseUrl = "http://localhost:8081/testRestTemplateApp/getUser.do";
//方法一:表單提交
MultiValueMap<String, Object> request = new LinkedMultiValueMap<>();
request.set("userName","張三1");
request.set("userId",1);
ResultData resultData = restTemplate.postForObject(baseUrl,request, ResultData.class);
System.out.println("*****POST表單提交使用URI查詢返回結果={}" + JSON.toJSONString(resultData));
//方法二:使用Map
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("userName", "張三2");
paramMap.put("userId",2);
resultData = restTemplate.postForObject(baseUrl,paramMap, ResultData.class);
System.out.println("******POST使用Map查詢返回結果={}" + JSON.toJSONString(resultData));
//方法三:使用URI
URI uri = URI.create(baseUrl);
resultData = restTemplate.postForObject(uri,request, ResultData.class);
System.out.println("******POST使用URI查詢返回結果={}" + JSON.toJSONString(resultData));
}
運行結果如下:
從運行結果我們可以看出,
如果傳入的參數是MultiValueMap類型的對象是,Spring會通過AllEncompassingFormHttpMessageConverter轉換器來將參數通過表單提交。
如果直接傳入一個Map對象,則會通過MappingJackson2HttpMessageConverter轉換器對參數進行轉換。
說完了表單提交,下面我們看看另外一種場景,如下,這個接口是一個保存用戶數據的接口,參數需要格式化後放在請求體中。
@ResponseBody
@PostMapping("/addUserJSON.do")
public ResultData<Boolean> addUserJSON(@RequestBody User user) {
if (user == null) {
return new ResultData<>(HttpStatus.BAD_REQUEST.value(), null, "參數不能爲空");
}
return new ResultData<>(HttpStatus.OK.value(),true,"保存成功");
}
當我們需要調用接口是通過@RequestBody
來接受參數時,也就是需要傳入一個JSON對象,我們該如何請求呢?我們調用可以postForObject可以直接傳入User對象, 也可以將請求頭設置成application/json
,然後將User對象序列化,代碼如下所示:
@Test
public void testPostForObject() {
String baseUrl = "http://localhost:8081/testRestTemplateApp/addUserJSON.do";
User user = new User();
user.setUserName("李四");
user.setAge(23);
//第一種方式:不傳入JSON的參數,不設置請求頭
ResultData resultData = restTemplate.postForObject(baseUrl, user, ResultData.class);
System.out.println("*********不序列化傳入參數請求結果={}" + JSON.toJSONString(resultData));
//第二種方式:傳入JSON類型的參數,設置請求頭
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity httpEntity = new HttpEntity(JSON.toJSONString(user),headers);
resultData = restTemplate.postForObject(baseUrl, httpEntity, ResultData.class);
System.out.println("*********序列化參數請求結果={}" + JSON.toJSONString(resultData));
}
第一種方式是由於Spring內部的MappingJackson2HttpMessageConverter會將參數進行序列化並請求接口
第二種方式是直接設置好請求頭爲application/json
,並將參數序列化。所以就不需要通過MappingJackson2HttpMessageConverter進行轉換。比較推薦
運行結果如下:
postForEntity方法在此就不在贅述了。
說完了,get請求的相關方法和post請求的相關方法,接下來我們來看看另外一類方法
postForLocation
postForLocation的定義是POST 數據到一個URL,返回新創建資源的URL
同樣提供了三個方法,分別如下,需要注意的是返回結果爲URI對象,即網絡資源
public URI postForLocation(String url, @Nullable Object request, Object... uriVariables)
throws RestClientException ;
public URI postForLocation(String url, @Nullable Object request, Map<String, ?> uriVariables)
throws RestClientException ;
public URI postForLocation(URI url, @Nullable Object request) throws RestClientException ;
這類接口主要應用在需要跳轉頁面的請求,比如,登錄,註冊,支付等post請求,請求成功之後需要跳轉到成功的頁面。這種場景下我們可以使用postForLocation
了,提交數據,並獲取放回的URI,一個測試如下:
首先mock一個接口
@ResponseBody
@RequestMapping(path = "loginSuccess")
public String loginSuccess(String userName, String password) {
return "welcome " + userName;
}
/**
* @param userName
* @param password
* @return
*/
@RequestMapping(path = "login", method = {RequestMethod.GET, RequestMethod.OPTIONS, RequestMethod.POST}
,produces = "charset/utf8")
public String login(@RequestParam(value = "userName", required = false) String userName,
@RequestParam(value = "password", required = false) String password) {
return "redirect:/loginSuccess?userName=" + userName + "&password=" + password + "&status=success";
}
測試請求是:
@Test
public void testPostLocation() {
String url = "http://localhost:8081/testRestTemplateApp/login";
MultiValueMap<String, String> paramMap = new LinkedMultiValueMap<>();
paramMap.add("userName", "bob");
paramMap.add("password", "1212");
URI location = restTemplate.postForLocation(url, paramMap);
System.out.println("*******返回的數據=" + location);
}
運行結果如下:
介紹完了restTemplate的常用方法,但是,我們或許會感覺到restTemplate
的方法太多了,調用起來不太方便,爲了使用方便,我們就對restTemplate
做一個封裝。代碼如下所示:主要封裝成了四個方法,一個是通過get請求的方法,一個是通過表單提交的post請求方法,一個是通過json提交的post請求方法,最後就是上傳圖片的方法。
@Component
public class RestTemplateProxy {
@Autowired
private RestTemplate restTemplate;
/**
*
* @param url 請求地址
* 參數可以通過 http://localhost:8888/juheServer/juhe/info/queryCustomer.do?taxNo=92330424MA29G7GY5W
* 或者 http://localhost:8888/juheServer/juhe/info/queryCustomer.do+String.format("?taxNo=%s&order=%s", "92330424MA29G7GY5W","1212121212");
* @param responseType 返回值的類型
* @return
* @author xiagwei
* @date 2020/3/5 5:28 PM
*
*/
public <T> T getForObject(String url, Class<T> responseType) {
return restTemplate.getForObject(url, responseType);
}
/**
* 通過json的方式請求服務,不需要將數據格式化,直接將請求對象傳入即可
* 可以是map,可以是一個bean
* @param url 請求接口
* @param requestParam 請求實體
* @param responseType 返回對象的clazz
* @return
* @author xiagwei
* @date 2020/3/5 5:36 PM
*/
public <T> T postForObjectJSON(String url, Object requestParam,Class<T> responseType) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity httpEntity = new HttpEntity(requestParam, headers);
return restTemplate.postForObject(url, httpEntity, responseType);
}
/**
* 通過Form表單的方式提交
* @param url 請求接口
* @param requestParam 請求實體,可以是一個實體,也可以一個map
* @param responseType 返回對象的clazz
* @return
* @author xiagwei
* @date 2020/3/5 5:42 PM
*/
public <T> T postForObjectForm(String url, @NotNull Object requestParam, Class<T> responseType) {
MultiValueMap<String, Object> valueRequestMap = creatValueMap(requestParam);
return restTemplate.postForObject(url, valueRequestMap, responseType);
}
/**
* 圖片上傳
*
* @param url 請求地址
* @param body 請求體
* MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("uploadFile", new FileSystemResource(ImageUtil.downloadImgByUrl(url)));
* @param responseType 返回結果的clazz對象
* @return
* @author xiagwei
* @date 2020/3/5 6:05 PM
*/
public <T> T uploadImg(@NotNull String url, @NotNull MultiValueMap<String, Object> body,Class<T> responseType) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body,headers);
return restTemplate.postForObject(url,requestEntity,responseType);
}
private MultiValueMap creatValueMap(Object requestParam) {
MultiValueMap<String, Object> valueRequestMap = new LinkedMultiValueMap<>();
Map<String, Object> param = null;
if (requestParam instanceof Map) {
param = (Map<String, Object>) requestParam;
} else {
param = BeanUtil.beanToMap(requestParam);
}
for (String key : param.keySet()) {
valueRequestMap.add(key, param.get(key));
}
return valueRequestMap;
}
}
這裏需要重點說下,圖片上傳的方法,上傳圖片的話,我們一定要把請求頭設置成multipart/form-data
,然後其餘的參數通過MultiValueMap
來設置。
public <T> T uploadImg(@NotNull String url, @NotNull MultiValueMap<String, Object> body,Class<T> responseType) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body,headers);
return restTemplate.postForObject(url,requestEntity,responseType);
}
總結
本文主要介紹了restTemplate
類的使用,首先介紹了需要引入的依賴,然後介紹瞭如何設置超時時間,接着就是介紹了restTemplate
中get請求相關的方法和post請求相關的方法,以及這些方法如何調用。最後就是對常用的請求方法做了一個封裝。希望對讀者朋友們有所幫助。