小白一看就會的Spring的resetTemplate的使用方法

寫在前面

作爲一名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));
    }

運行結果如下:
在這裏插入圖片描述
需要注意的是:

  1. 傳參替換使用{?}來表示坑位,根據實際的傳參順序來填充,如下:
  url = baseUrl+"?userName={?}&userId={?}";
  resultData = restTemplate.getForObject(url, ResultData.class, "張三2",2);
  1. 使用{xx}來傳遞參數時,這個xx對應的就是map中的key
       url = baseUrl + "?userName={userName}&userId={userId}";
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("userName", "張三1");
        paramMap.put("userId",1);
  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);
  1. 不推薦直接使用方法三傳入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請求相關的方法,以及這些方法如何調用。最後就是對常用的請求方法做了一個封裝。希望對讀者朋友們有所幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章