Spring MVC Rest 客戶端 RestTemplate 詳解

目錄

RestTemplate Rest 模板概述

Http GET 請求

Http Post 請求

RestTemplate 超時時間

切換 OkHttpClient 客戶端


RestTemplate Rest 模板概述

帶負載均衡(@LoadBalanced)的 RestTemplate 必須使用微服務名稱發起請求,不能使用 ip:port
不帶負載均衡(@LoadBalanced)的 RestTemplate 不能使用微服務名稱發起請求,只能使用 ip:port

1、org.springframework.web.client.RestTemplate 類是 spring-web-x.x.x.RELEASE.jar 包下進行 HTTP 訪問的 REST 客戶端核心類。

2、Java 應用後臺代碼中如果想要向另外一個 Java 應用發起 Http 請求,通常使用 Apache 的 HttpClient 庫來做。而 spring cloud 微服務架構中,各個微服務之間會頻繁的發起 http 請求進行通信,所以 spring 對 http 請求進行了封裝,以便請求使用起來更加方便。

3、RestTemplate 類能夠以 rest 風格的方式,以 GET, POST, PUT, DELETE, HEAD, OPTIONS 等不同的方式向服務器發起HTTP 請求。

4、RestTemplate 對 http 的封裝類似 JdbcTemplate 對 jdbc 的封裝。本文環境:Java jdk 8 + Spring boot 2.1.3(spring 5.1.5)。

構造函數 描述
RestTemplate() 默認使用 org.springframework.http.client.SimpleClientHttpRequestFactory 客戶端,底層使用 jdk 標準的 API,即 java.net 包下的 API,如 java.net.HttpURLConnection 等。
RestTemplate(ClientHttpRequestFactory requestFactory) ClientHttpRequestFactory 接口的實現類給出底層實現的第三方 HTTP 客戶端軟件。如 OkHttp3Client、以及 RibbonClient 等
RestTemplate(List<HttpMessageConverter<?>> messageConverters) HttpMessageConverter 接口的實現對象能夠在HTTP消息與Java POJO之間進行數據轉換。

5、使用 RestTemplate 非常簡單,對於 Spring boot、Spring Cloud 項目,spring-boot-starter-web 組件內部已經包含了 spring-web-x.x.x.RELEASE.jar ,所以只需要將 RestTemplate 實例交由 Spring 容器管理,然後在需要的地方獲取使用即可。

//可以直接在啓動類(@SpringBootApplication) 中進行添加,或者在 @Configuration 標註的類中添加
@Bean
public RestTemplate restTemplate() {
	//將 RestTemplate 實例交由 Spring 容器管理
	return new RestTemplate();
}
//在使用 RestTemplate 的 Controller 或者其它地方自動注入,然後使用
@Resource
private RestTemplate restTemplate;

Http GET 請求

方法 描述
<T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) url:請求的地址,responseType:返回的數據類型,ResponseEntity 對象包含了http響應頭信息、響應正文、響應狀態等信息
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables) uriVariables:用於設置查詢參數的可變值,map 的 key 值對於url中設置的可變參數值.
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) uriVariables:專門用於處理 url 路徑中的參數 @PathVariable
<T> T getForObject(URI url, Class<T> responseType) getForObject 方法是重載的方法,區別在於返回值直接是響應正文,不含響應頭信息以及響應狀態.
<T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)  
<T> T getForObject(String url, Class<T> responseType, Object... uriVariables)  

getForEntity(URI url, Class<T> responseType)

     /** 接口提供方
     * http://localhost:8080/getInfo?uid=3030&agencyId=20807
     * @param uid
     * @param agencyId
     * @return
     */
    @GetMapping("getInfo")
    public String getInfo(@RequestParam String uid, String agencyId) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", 200);
        jsonObject.put("uid", uid);
        jsonObject.put("agencyId", agencyId);
        return jsonObject.toString();
    }
//接口調用方
@GetMapping("test1")
public String test1() {
	String body = null;
	try {
		URI uri = new URI("http://localhost:8080/getInfo?uid=3030&agencyId=20807");
		ResponseEntity<String> forEntity = restTemplate.getForEntity(uri, String.class);
		//http響應狀態碼,如成功時返回 200
		int statusCodeValue = forEntity.getStatusCodeValue();
		//http響應的正文內容,即對方返回的數據,如 {"uid":"3030","code":200,"agencyId":"20807"}
		body = forEntity.getBody();
		//http響應的頭信息,如 Content-Type=[text/plain;charset=UTF-8], Content-Length=[44], Date=[Tue, 24 Mar 2020 12:45:43 GMT]}
		HttpHeaders headers = forEntity.getHeaders();
		System.out.println("headers=" + headers + "\nstatusCodeValue=" + statusCodeValue + "\nbody=" + body);
	} catch (URISyntaxException e) {
		e.printStackTrace();
	}
	return body;
}

getForEntity(String url, Class<T> responseType, Object... uriVariables)

 /**
 * 接口提供方
 * http://localhost:8080/getData
 * @param uid
 * @param agencyId
 * @return
 */
@GetMapping("getData/{agencyId}/{uid}")
public List<Map<String, Object>> getData(@PathVariable String uid, @PathVariable String agencyId, String info) {
	List<Map<String, Object>> dataMapList = new ArrayList<>(8);
	Map<String, Object> map = new HashMap<>(8);
	map.put("code", 200);
	map.put("uid", uid);
	map.put("info", info);
	map.put("agencyId", agencyId);
	dataMapList.add(map);
	return dataMapList;
}
//接口調用方
@GetMapping("test2")
public List<Map<String, Object>> test2() {
	String uri = "http://localhost:8080/getData/{agency_id}/{u_id}?info=長沙";
	//返回數據類型爲 List 類型
	ResponseEntity<List> forEntity = restTemplate.getForEntity(uri, List.class, 20792, "999UP000");
	//http響應狀態碼,如成功時返回 200
	int statusCodeValue = forEntity.getStatusCodeValue();
	//http響應的正文內容,即對方返回的數據,如 {"uid":"3030","code":200,"agencyId":"20807"}
	List<Map<String, Object>> body = forEntity.getBody();
	//http響應的頭信息,如 Content-Type=[text/plain;charset=UTF-8], Content-Length=[44], Date=[Tue, 24 Mar 2020 12:45:43 GMT]}
	HttpHeaders headers = forEntity.getHeaders();
	System.out.println("headers=" + headers + "\nstatusCodeValue=" + statusCodeValue + "\nbody=" + body);
	return body;
}

getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)

/**
 * 接口提供方
 * http://localhost:8080/getMessage
 *
 * @param uid
 * @param agencyId
 * @return
 */
@GetMapping("getMessage")
public JSONObject getMessage(String uid, String agencyId) {
	List<Map<String, Object>> dataMapList = new ArrayList<>(8);
	Map<String, Object> map = new HashMap<>(8);
	JSONObject jsonObject = new JSONObject();
	jsonObject.put("code", 200);
	jsonObject.put("uid", uid);
	jsonObject.put("agencyId", agencyId);
	return jsonObject;
}
//接口調用方
@GetMapping("test3")
public JSONObject test3() {
	String uri = "http://localhost:8080/getMessage?uid={uid}&agencyId={agency_id}";
	Map<String, Object> paramMap = new HashMap<>(4);
	paramMap.put("uid", "9998UYT9");
	paramMap.put("agency_id", 20802);
	//getForObject 返回數據類型爲 JSONObject 類型的響應內容,即直接獲取返回值.
	JSONObject body = restTemplate.getForObject(uri, JSONObject.class, paramMap);

	System.out.println(body);
	return body;
}

Http Post 請求

方法 描述
<T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType)

url:請求的地址;

request:請求的正文(body)內容,即對方 @RequestBody 參數的值,可以爲 null。

responseType:返回的數據類型,

ResponseEntity 對象包含了http響應頭信息、響應正文、響應狀態等信息

<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request,Class<T> responseType, Map<String, ?> uriVariables) uriVariables:用於設置查詢參數的可變值,map 的 key 值對於url中設置的可變參數值.
<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request,Class<T> responseType, Object... uriVariables) uriVariables:專門用於處理 url 路徑中的參數 @PathVariable
T postForObject(URI url, @Nullable Object request, Class<T> responseType) getForObject 方法是重載的方法,區別在於返回值直接是響應正文,不含響應頭信息以及響應狀態.
T postForObject(String url, @Nullable Object request, Class<T> responseType,Map<String, ?> uriVariables)  
T postForObject(String url, @Nullable Object request, Class<T> responseType,Object... uriVariables)  

postForEntity(String url, @Nullable Object request,Class<T> responseType, Map<String, ?> uriVariables)

/**接口提供方
 * http://localhost:8080/postInfo?uid=44KKP990&agencyId=208071
 *
 * @param uid
 * @param agencyId
 * @return
 */
@PostMapping("postInfo")
public JSONObject postInfo(@RequestParam String uid, String agencyId) {
	JSONObject jsonObject = new JSONObject();
	jsonObject.put("code", 200);
	jsonObject.put("uid", uid);
	jsonObject.put("agencyId", agencyId);
	return jsonObject;
}
//接口調用方
@GetMapping("test11")
public JSONObject test11() {
	JSONObject body = null;
	String uri = "http://localhost:8080/postInfo?uid={uid}&agencyId=208071";
	Map<String, String> paramMap = new HashMap<>(4);
	paramMap.put("uid", "8899HG9");
	ResponseEntity<JSONObject> forEntity = restTemplate.postForEntity(uri, null, JSONObject.class, paramMap);
	//http響應狀態碼,如成功時返回 200
	int statusCodeValue = forEntity.getStatusCodeValue();
	//http響應的正文內容,即對方返回的數據,如 {"uid":"3030","code":200,"agencyId":"20807"}
	body = forEntity.getBody();
	//http響應的頭信息,如 Content-Type=[text/plain;charset=UTF-8], Content-Length=[44], Date=[Tue, 24 Mar 2020 12:45:43 GMT]}
	HttpHeaders headers = forEntity.getHeaders();

	System.out.println("headers=" + headers + "\nstatusCodeValue=" + statusCodeValue + "\nbody=" + body);
	return body;
}

postForObject(String url, @Nullable Object request, Class<T> responseType,Object... uriVariables)

/**
 * 接口提供方
 * http://localhost:8080/postData/{agencyId}
 */
@PostMapping("postData/{agencyId}")
public List<Map<String, Object>> postData(@PathVariable String agencyId, @RequestBody List<String> ids) {
	List<Map<String, Object>> dataMapList = new ArrayList<>(8);
	Map<String, Object> map = new HashMap<>(8);
	map.put("code", 200);
	map.put("uid", ids.toArray());
	map.put("agencyId", agencyId);
	dataMapList.add(map);
	return dataMapList;
}
//接口調用方
@GetMapping("test22")
public List<Map<String, Object>> test22() {
	String uri = "http://localhost:8080/postData/{agencyId}";
	List<String> ids = new ArrayList<>(4);
	ids.add("3000L");
	ids.add("99UH7");
	ids.add("99JHG");
	//返回數據類型爲 List 類型
	List<Map<String,Object>> body = restTemplate.postForObject(uri, ids, List.class, 20792);
	return body;
}

//其餘操作都是同理

RestTemplate 超時時間

1、new RestTemplate() 時默認使用 SimpleClientHttpRequestFactory 工廠,它底層默認使用 Java 原生的 HttpURLConnection API 做 Http 請求,默認 connectTimeout = -1(連接超時),readTimeout = -1(讀取超時)都沒有做限制,即一直等待。

2、顯然無限期的等待是絕不允許的,必須設置超時時間,以 SimpleClientHttpRequestFactory 工廠爲例:

    //設置 http 連接工廠,然後將 RestTemplate 實例交由 Spring 容器管理
    @Bean
    public RestTemplate restTemplate() {
        //先設置 http 連接工廠的連接超時時間爲 30 秒,讀取超時時間爲 120 秒
        //請求發送後,對方數據量比較大,需要長時間處理數據時,讀取超時時間需要設置的長一些,否則對方還未處理完,連接就超時了。
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(30 * 1000);
        requestFactory.setReadTimeout(120 * 1000);
        return new RestTemplate(requestFactory);
    }

3、驗證非常簡單,請求 youtube ,鐵定連不上而會超時,然後拋異常: java.net.SocketTimeoutException: Read timed out

String body = restTemplate.getForObject("https://ca.youtube.com/", String.class);

切換 OkHttpClient 客戶端

1、ClientHttpRequestFactory 有多個實現類,okhttp3 作爲現在最熱門 http 客戶端,這裏就以 OkHttpClient 爲例。

2、第一步項目中引入 OkHttpClient 的依賴,否則會報:java.lang.NoClassDefFoundError: okhttp3/OkHttpClient

        <!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.14.5</version>
        </dependency>

版本不能隨意引,org.springframework.http.client.OkHttp3ClientHttpRequestFactory 類的源碼上面通常有提示說明對應的OkHttp 版本,如本文 spring-web-5.0.7.RELEASE.jar 提示的 OkHttp 版本是 3.X,如果引入 4.X 則啓動會失敗。

3、接下來就是創建 RestTemplate 對象的時候,傳入 OkHttp3ClientHttpRequestFactory 工廠即可:

    //設置 http 連接工廠,然後將 RestTemplate 實例交由 Spring 容器管理
    @Bean
    public RestTemplate restTemplate() {
        //先設置 http 連接工廠的連接超時時間爲 30 秒,讀取超時時間爲 120 秒
        OkHttp3ClientHttpRequestFactory okRequestFactory = new OkHttp3ClientHttpRequestFactory();
        okRequestFactory.setConnectTimeout(30 * 1000);
        okRequestFactory.setReadTimeout(120 * 1000);
        return new RestTemplate(okRequestFactory);
    }

驗證底層到底用的什麼 http 客戶端也很簡單,仍然請求 youtube(https://ca.youtube.com) ,連接超時時控制層會打印異常,其中就可以看到  okhttp3.internal.xxxx 的錯誤信息。

4、其它如  Apache 的 HttpClient、以及 Netty、RibbonClient 等 http 客戶端使用都是同理。

Ribbon 的負載均衡就是在 RestTemplate 上加上 @LoadBalanced,可以參考 Netflix Ribbon 負載均衡

擴展內容可以繼續參考:https://blog.csdn.net/f641385712/article/details/100713622

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