為什么要使用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接口的類,里面提供了大量便捷的方法,如下:
執行不同的請求,只需找到這種請求方式所對應的方法就行,上例中的postForObject
就是發送的POST請求。如果上面的請求沒有找到對應的方法,可以使用更加通用的exchange
和execute
方法。
RestTemplate的可擴展性也很強(下面列出比較常用的幾種方式):
- RestTemplate默認使用的是JDK中的HttpURLConnection實現的,如果你想要切換到其他的HTTP庫,例如,Apache HttpComponents, Netty和OkHttp,只需要調用
setRequestFactory
方法來進行設置,甚至可以使用自己實現的類型,只需要繼承自ClientHttpRequestFactory
。(一般情況下,使用默認的實現就好) - RestTemplate默認使用的是
DefaultResponseErrorHandler
響應錯誤處理器,你可以調用setErrorHandler
來定制自己的響應錯誤處理器。 - 客戶端請求攔截器:
ClientHttpRequestInterceptor
,實現這個接口,並在org.springframework.web.client.RestTemplate#setInterceptors(java.util.List)
中進行注冊,能對請求頭和請求體的內容進行修改,並能對響應的內容進行修改。(下面將會講解) - 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);