還不知道spring的RestTemplate的妙用嗎


為什么要使用RestTemplate?

隨着微服務的廣泛使用,在實際的開發中,客戶端代碼中調用RESTful接口也越來越常見。在系統的遺留代碼中,你可能會看見有一些代碼是使用HttpURLConnection來調用RESTful接口的,類似於下面這樣:

URL url = ...	
// 打開連接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
try {
    conn.setRequestMethod("POST");
    conn.setDoInput(true);
    conn.setDoOutput(true);
    conn.connect();
    // 發送數據...
    BufferedWriter bw = new BufferedWriter(
        new OutputStreamWriter(conn.getOutputStream(), "utf-8"));
    bw.write(str);
    // 接收數據 ...
    BufferedReader br = new BufferedReader(
        new InputStreamReader(conn.getInputStream(), "utf-8"));
    String line = null;
    while ((line = br.readLine()) != null) {
        ...
    }
} finally {
    conn.disconnect();
}

從上面的代碼可以看出,使用HttpURLConnection調用RESTful接口是比較麻煩的,假如要調用30個接口,每個接口都使用類似於上面的代碼 進行調用,那簡直是一場災難(寫這么多無聊的樣板代碼,內心絕對是崩潰的)。有人可能會想,將常用的RESTful操作(例如GET、POST、DELETE)封裝成工具類,再調用不是也可以嗎!這樣做確實可行,但是要封裝成通用的工具類不是那么簡單的(需要一定的經驗)。調用RESTful接口,還有另外一種選擇:Apache HttpComponents。關於如何調用,可以參考我之前寫過的一篇文章:學習HttpClient,從兩個小例子開始。雖然使用它發送HTTP請求確實挺方便的,但是使用它調用RESTful接口好像也挺麻煩的!

直到我遇到了RestTemplate,世界頓時都明亮了,腰也不酸了,腿也不疼了,一下子就對接好了10個RESTful接口。寫的代碼可能變成是這樣子的:

RestTemplate template = ...
// 請求地址
String url = ...
// 請求參數
UserParams request = ...
// 執行POST請求
User u = template.postForObject(url, request, User.class);
...

上面的調用代碼變的簡潔了很多,是不是很爽啊!RestTemplate是spring提供用來調用RESTful接口的類,里面提供了大量便捷的方法,如下:

請求方式 RestTemplate中的方法
DELETE delete(java.lang.String, java.lang.Object...)
GET getForObject(java.lang.String, java.lang.Class, java.lang.Object...)
getForEntity(java.lang.String, java.lang.Class, java.lang.Object...)
HEAD headForHeaders(java.lang.String, java.lang.Object...)
OPTIONS optionsForAllow(java.lang.String, java.lang.Object...)
POST postForLocation(java.lang.String, java.lang.Object, java.lang.Object...)
postForObject(java.lang.String, java.lang.Object, java.lang.Class, java.lang.Object...)
PUT put(java.lang.String, java.lang.Object, java.lang.Object...)
any exchange(java.lang.String, org.springframework.http.HttpMethod,
org.springframework.http.HttpEntity,
java.lang.Class, java.lang.Object...)
execute(java.lang.String, org.springframework.http.HttpMethod,
org.springframework.web.client.RequestCallback,
org.springframework.web.client.ResponseExtractor, java.lang.Object...)

執行不同的請求,只需找到這種請求方式所對應的方法就行,上例中的postForObject就是發送的POST請求。如果上面的請求沒有找到對應的方法,可以使用更加通用的exchangeexecute方法。

RestTemplate的可擴展性也很強(下面列出比較常用的幾種方式):

  1. RestTemplate默認使用的是JDK中的HttpURLConnection實現的,如果你想要切換到其他的HTTP庫,例如,Apache HttpComponents, Netty和OkHttp,只需要調用setRequestFactory方法來進行設置,甚至可以使用自己實現的類型,只需要繼承自ClientHttpRequestFactory。(一般情況下,使用默認的實現就好)
  2. RestTemplate默認使用的是DefaultResponseErrorHandler響應錯誤處理器,你可以調用setErrorHandler來定制自己的響應錯誤處理器。
  3. 客戶端請求攔截器:ClientHttpRequestInterceptor,實現這個接口,並在org.springframework.web.client.RestTemplate#setInterceptors(java.util.List)中進行注冊,能對請求頭和請求體的內容進行修改,並能對響應的內容進行修改。(下面將會講解)
  4. RestTemplate中的doExecute方法,所有請求方式最終都會執行這個方法,重寫此方法能完成一些必要的操作。

RestTemplate的妙用

考慮這樣一個場景:假如說A部門開發的用戶相關的微服務接口,提供給B部門來使用,在使用時需要在請求頭中加入基本的認證頭信息,認證頭的格式如下:

Authorization=Basic {token}

token的格式是:base64(username:password)。假如username是zfx,密碼是123,結果是先拼接用戶名和密碼:zfx:123,再用utf-8格式獲取其字節碼,進行base64編碼的結果為:emZ4OjEyMw==

假如要調用A部門的接口,根據id來獲取用戶的信息,代碼如下:

String userId = "11";
String url = "http://127.0.0.1:8080/v1/users/{id}";

RestTemplate restTemplate = new RestTemplate();

// 基本的認證頭信息
String username  = "zfx";
String password = "123";
String token = Base64Utils.encodeToString(
    (username + ":" + password).getBytes(StandardCharsets.UTF_8));
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + token);
HttpEntity<Void> requestEntity = new HttpEntity<>(headers);

ResponseEntity<User> exchange = restTemplate.exchange(url, HttpMethod.GET, 
                                                      requestEntity, User.class, userId);
User result = exchange.getBody();

首先先創建RestTemplate的實例,在實際的開發中,最好不要每次都創建RestTemplate的實例,最好在spring中以單例的方式來配置,要使用的地方直接注入。用base64來編碼了用戶名和密碼,然后在請求頭中進行設置。restTemplate.exchange執行請求,並獲取返回的結果。上面的代碼其實並不難,但是這樣寫會不會有啥問題呢!假如說,叫你對接A部門的根據機構id來獲取機構信息的接口,你豈不是又要把上面的代碼再重新復制一下嗎?這樣不是很麻煩,代碼會很臃腫。

spring提供了ClientHttpRequestInterceptor這個類,適合用來處理這種情況,加入基本的認證頭信息,可以使用spring提供的這個類:BasicAuthorizationInterceptor。使用配置的方式來配置RestTemplate:

@Configuration
public class RestTemplateConfig {
	
	@Value("${zfx.username}")
	private String username;
	
	@Value("${zfx.password}")
	private String password;
	
	@Bean("basicAuthRestTemplate")
	public RestTemplate createBasicAuthRestTemplate() {
		RestTemplate restTemplate = new RestTemplate();
		
		List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
		interceptors.add(new BasicAuthorizationInterceptor(username, password));
		restTemplate.setInterceptors(interceptors);
		return restTemplate;
	}
	
}

上面的代碼在創建basicAuthRestTemplate時,會加入基本的認證頭信息的攔截器,來設置基本認證頭信息。

再次調用上面的接口時,代碼可以這樣寫:

@Autowired
RestTemplate basicAuthRestTemplate;
...
String userId = "11";
String url = "http://127.0.0.1:8080/v1/users/{id}";
User result = basicAuthRestTemplate.getForObject(url, User.class, userId);

代碼一下子簡潔了這么多,假如說要調用根據機構id來獲取機構信息的接口呢?如下:

@Autowired
RestTemplate basicAuthRestTemplate;
...
String orgId = "11";
String url = "http://127.0.0.1:8080/v1/orgs/{id}";
Org result = basicAuthRestTemplate.getForObject(url, Org.class, orgId);

代碼一下子簡潔了很多,對接這些接口的程序員不用再關心認證是怎么一回事,認證這件事對於他們完全是透明的,他們只需要專注於編寫他們自己的邏輯代碼就可以了。ClientHttpRequestInterceptor的實現也很簡單,代碼如下:

@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
		ClientHttpRequestExecution execution) throws IOException {

	String token = Base64Utils.encodeToString(
			(this.username + ":" + this.password).getBytes(StandardCharsets.UTF_8));
	request.getHeaders().add("Authorization", "Basic " + token);
	return execution.execute(request, body);
}

在intercept方法中加入了基本的認證頭信息。

假如說,有一天認證方式變了,改成OAuth2.0了,那么我們只需要實現自己的請求攔截器就行了,如下:

public class BearerAuthorizationIntercept
		implements ClientHttpRequestInterceptor {

	@Override
	public ClientHttpResponse intercept(HttpRequest request, byte[] body,
			ClientHttpRequestExecution execution) throws IOException {
		String token = 這些寫獲取token的邏輯;
		request.getHeaders().add("Authorization", "Bearer " + token);
		return execution.execute(request, body);
	}

}

然后配置restTemplate:

	@Bean("bearerAuthRestTemplate")
	public RestTemplate createBearerAuthRestTemplate() {
		RestTemplate restTemplate = new RestTemplate();
		
		List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
		interceptors.add(new BearerAuthorizationIntercept());
		restTemplate.setInterceptors(interceptors);
		return restTemplate;
	}

那么只需要注入bearerAuthRestTemplate,就能使用他了:

@Autowired
RestTemplate bearerAuthRestTemplate;
...
String userId = "11";
String url = "http://127.0.0.1:8080/v1/users/{id}";
User result = bearerAuthRestTemplate.getForObject(url, User.class, userId);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM