一、REST
在互聯網中,我們會通過請求url來對網絡上的資源做增刪改查等動作,這里的請求包含兩部分:
動詞,主要包括增、刪、改、查;
名詞,就是網絡中的各種資源。
傳統的非REST風格的請求方式是把動詞和名詞全都放在url中。
例如,對設備的操作可能是這樣的:
添加設備:http://test/device/add
刪除設備:http://test/device/delete
修改設備:http://test/device/modify
查找設備:http://test/device/find
這樣就存在一個規范的問題,例如,添加設備這個動作的單詞應該是用add還是create?http方法是用GET還是用POST?等等。
REST風格的請求方式是用http請求方法表示增刪改查動作,而url中只保留名詞,也就是對資源位置的描述,這樣就避免了動作描述規范的問題。
還是以對設備的操作舉例,REST風格的請求是這樣的:
添加設備:http://test/device 請求方法是POST
刪除設備:http://test/device 請求方法是DELETE
修改設備:http://test/device 請求方法是PUT
查找設備:http://test/device/:id 請求方法是GET
二、Spring中對REST請求的處理
Spring中可以使用RestTemplate來操作REST資源,主要包含以下幾個方法:
getForEntity(),getForObject(),發送HTTP GET請求,getForEntity()返回的是ResponseEntity對象,里面包含響應實體對象及響應狀態碼,而getForObject()則直接返回響應實體對象;
postForEntity(),postForObject(),發送HTTP POST請求,postForEntity()返回的是ResponseEntity對象,里面包含響應實體對象及響應狀態碼,而postForObject()則直接返回響應實體對象;
put(),發送HTTP PUT請求;
delete(),發送HTTP DELETE請求;
exchange(),可以發送GET、POST、PUT和DELETE中的任意一種請求,同時還可以自定義請求頭。
下面舉例說明幾種方法的用法:
首先創建一個實體類Device,后面的方法會用到這個實體類:
public class Device { private String ip; private String mac; }
創建一個用於測試的Controller類:
@RestController @RequestMapping(value = "/consumer") public class ConsumerController { private RestTemplate restTemplate = new RestTemplate(); private String urlPrefix = "http://localhost:8080/test/producer"; // ... }
1. GET請求
GET請求有兩組重載方法:
1.1 getForEntity()
這個方法有多個重載方法:
方法一:
getForEntity(String url, Class<T> responseType, Object... uriVariables)
其中,url就是請求的url,responseType是返回的實體類類型,uriVariables是uri或請求參數。
代碼示例如下:
@RequestMapping(value = "/get1") public String testGetForEntity1() { String url = urlPrefix + "/get"; ResponseEntity<Device> response = restTemplate.getForEntity(url, Device.class); System.out.println("Device: " + response.getBody() + ", code: " + response.getStatusCodeValue()); return response.getBody().toString(); }
返回的response中既包含返回的實體對象(通過response.getBody()獲取),又包含返回狀態碼(通過response.getStatusCodeValue()獲取)。
這里的url中沒有任何參數,所以uriVariables為空。如果url中需要傳一些參數,可以通過以下方式傳遞:
@RequestMapping(value = "/get2") public String testGetForEntity3() { String url = urlPrefix + "/get?protocol={protocol}&operator={operator}"; ResponseEntity<Device> response = restTemplate.getForEntity(url, Device.class, "NBIot", "ChinaMobile"); System.out.println("Device: " + response.getBody() + ", code: " + response.getStatusCodeValue()); return response.getBody().toString(); }
這里傳了兩個參數protocol和operator。
方法二:
getForEntity(String url, Class<T> responseType, Map<String,?> uriVariables)
這種方法是將uriVariables用Map方式傳入,如果參數比較多的話,這種方式方便方法之間的參數傳遞。
代碼示例如下:
@RequestMapping(value = "/get3") public String testGetForEntity2() { Map<String, String> classifyMap = new HashMap<>(); classifyMap.put("protocol", "NBIot"); classifyMap.put("operator", "ChinaMobile"); String url = urlPrefix + "/get?protocol={protocol}&operator={operator}"; ResponseEntity<Device> response = restTemplate.getForEntity(url, Device.class, classifyMap); System.out.println("Device: " + response.getBody() + ", code: " + response.getStatusCodeValue()); return response.getBody().toString(); }
方法三:
getForEntity(URI url, Class<T> responseType)
這種方法是把String類型的url替換成java.net.URI類型的url。
1.2 getForObject()
這個方法和getForEntity()的3個重載方法請求參數是完全一樣的,不同的是getForObject()直接返回的是實體對象,而沒有返回狀態碼。如果不關注狀態碼,只關注返回內容,可以使用這個方法。
方法一:
getForObject(String url, Class<T> responseType, Object... uriVariables)
代碼示例如下:
不包含請求參數的方法:
@RequestMapping(value = "/get4") public String testGetForObject1() { String url = urlPrefix + "/get"; Device device = restTemplate.getForObject(url, Device.class); System.out.println("Device: " + device); return device.toString(); }
包含請求參數的方法:
@RequestMapping(value = "/get5") public String testGetForObject2() { String url = urlPrefix + "/get?protocol={protocol}&operator={operator}"; Device device = restTemplate.getForObject(url, Device.class, "NBIot", "ChinaMobile"); System.out.println("Device: " + device); return device.toString(); }
方法二:
getForObject(String url, Class<T> responseType, Map<String,?> uriVariables)
代碼示例如下:
@RequestMapping(value = "/get6") public String testGetForObject3() { Map<String, String> classifyMap = new HashMap<>(); classifyMap.put("protocol", "NBIot"); classifyMap.put("operator", "ChinaMobile"); String url = urlPrefix + "/get?protocol={protocol}&operator={operator}"; Device device = restTemplate.getForObject(url, Device.class, classifyMap); System.out.println("Device: " + device); return device.toString(); }
方法三:
getForObject(URI url, Class<T> responseType)
2. POST請求
POST請求有兩組重載方法:
postForEntity()
這個方法也有多個重載方法,和getForEntity()方法的傳入參數非常類似,只是多了一個request參數,這個參數就是POST請求body體重的參數。
方法一:
postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)
代碼示例如下:
@RequestMapping(value = "/post1") public String testPostForEntity1() { Device device = Device.builder().ip("1.2.3.4").mac("FF:FF:FF:FF:FF:FF").build(); String url = urlPrefix + "/post"; ResponseEntity<Device> response = restTemplate.postForEntity(url, device, Device.class); System.out.println("Device: " + response.getBody() + ", code: " + response.getStatusCodeValue()); return response.getBody().toString(); }
請求url中也可以包含參數,代碼示例如下:
@RequestMapping(value = "/post2") public String testPostForEntity2() { Device device = Device.builder().ip("1.2.3.4").mac("FF:FF:FF:FF:FF:FF").build(); Map<String, String> classifyMap = new HashMap<>(); classifyMap.put("protocol", "NBIot"); classifyMap.put("operator", "ChinaMobile"); String url = urlPrefix + "/post?protocol={protocol}&operator={operator}"; ResponseEntity<Device> response = restTemplate.postForEntity(url, device, Device.class, classifyMap); System.out.println("Device: " + response.getBody() + ", code: " + response.getStatusCodeValue()); return response.getBody().toString(); }
方法二:
postForEntity(String url, Object request, Class<T> responseType, Map<String,?> uriVariables)
代碼示例如下:
@RequestMapping(value = "/post3") public String testPostForEntity3() { Device device = Device.builder().ip("1.2.3.4").mac("FF:FF:FF:FF:FF:FF").build(); String url = urlPrefix + "/post?protocol={protocol}&operator={operator}"; ResponseEntity<Device> response = restTemplate.postForEntity(url, device, Device.class, "NBIot", "ChinaMobile"); System.out.println("Device: " + response.getBody() + ", code: " + response.getStatusCodeValue()); return response.getBody().toString(); }
方法三:
postForEntity(URI url, Object request, Class<T> responseType)
postForObject()
和前面的GET方法類似,這個方法也是只返回請求對象,不返回請求狀態碼。
方法一:
postForObject(String url, Object request, Class<T> responseType, Object... uriVariables)
不帶請求參數的代碼示例:
@RequestMapping(value = "/post4") public String testForObject1() { Device device = Device.builder().ip("1.2.3.4").mac("FF:FF:FF:FF:FF:FF").build(); String url = urlPrefix + "/post"; Device responseDevice = restTemplate.postForObject(url, device, Device.class); System.out.println("Device: " + responseDevice); return responseDevice.toString(); }
帶請求參數的代碼示例:
@RequestMapping(value = "/post5") public String testForObject2() { Device device = Device.builder().ip("1.2.3.4").mac("FF:FF:FF:FF:FF:FF").build(); Map<String, String> classifyMap = new HashMap<>(); classifyMap.put("protocol", "NBIot"); classifyMap.put("operator", "ChinaMobile"); String url = urlPrefix + "/post?protocol={protocol}&operator={operator}"; Device responseDevice = restTemplate.postForObject(url, device, Device.class, classifyMap); System.out.println("Device: " + responseDevice); return responseDevice.toString(); }
方法二:
postForObject(String url, Object request, Class<T> responseType, Map<String,?> uriVariables)
代碼示例如下:
@RequestMapping(value = "/post6") public String testForObject3() { Device device = Device.builder().ip("1.2.3.4").mac("FF:FF:FF:FF:FF:FF").build(); String url = urlPrefix + "/post?protocol={protocol}&operator={operator}"; Device responseDevice = restTemplate.postForObject(url, device, Device.class, "NBIot", "ChinaMobile"); System.out.println("Device: " + responseDevice); return responseDevice.toString(); }
方法三:
postForObject(URI url, Object request, Class<T> responseType)
3. PUT請求
POST請求用來創建資源,有時候在創建資源的時候會返回已創建的資源的相關信息,比如資源的ID等。
GET請求是用來查詢資源的,所以一定會返回資源信息。
而PUT請求用來修改資源,通常我們只關注修改成功與否,不需要返回相關的資源信息。因此PUT的方法沒有區分xxxForEntity()和xxxForObject()方法,它只有一種方法:
put()
它有三種重載方法,返回類型都是void。
方法一:
put(String url, Object request, Object... uriVariables)
和POST的使用方法完全一樣,只是不需要關注返回結果。
不帶請求參數的代碼示例如下:
@RequestMapping(value = "/put1") public void testPut1() { Device device = Device.builder().ip("1.2.3.4").mac("FF:FF:FF:FF:FF:FF").build(); String url = urlPrefix + "/put"; restTemplate.put(url, device); }
帶請求參數的代碼示例如下:
@RequestMapping(value = "/put2") public void testPut2() { Device device = Device.builder().ip("1.2.3.4").mac("FF:FF:FF:FF:FF:FF").build(); String url = urlPrefix + "/put?protocol={protocol}&operator={operator}"; restTemplate.put(url, device, "NBIot", "ChinaMobile"); }
方法二:
put(String url, Object request, Map<String,?> uriVariables)
代碼示例如下:
@RequestMapping(value = "/put3") public void testPut3() { Map<String, String> classifyMap = new HashMap<>(); classifyMap.put("protocol", "NBIot"); classifyMap.put("operator", "ChinaMobile"); Device device = Device.builder().ip("1.2.3.4").mac("FF:FF:FF:FF:FF:FF").build(); String url = urlPrefix + "/put?protocol={protocol}&operator={operator}"; restTemplate.put(url, device, classifyMap); }
方法三:
put(URI url, Object request)
4. DELETE請求
DELETE請求更簡單,既沒有請求body體,也沒有返回對象,它也只有一種方法:
delete()
和其它方法一樣,也有三種重載方法:
方法一:
delete(String url, Object... uriVariables)
代碼示例如下:
@RequestMapping(value = "/delete1") public void testDelete1() { String url = urlPrefix + "/delete"; restTemplate.delete(url); } @RequestMapping(value = "/delete2") public void testDelete2() { String url = urlPrefix + "/delete?protocol={protocol}&operator={operator}"; restTemplate.delete(url, "NBIot", "ChinaMobile"); }
方法二:
delete(String url, Map<java.lang.String,?> uriVariables)
代碼示例如下:
@RequestMapping(value = "/delete3") public void testDelete3() { Map<String, String> classifyMap = new HashMap<>(); classifyMap.put("protocol", "NBIot"); classifyMap.put("operator", "ChinaMobile"); String url = urlPrefix + "/delete?protocol={protocol}&operator={operator}"; restTemplate.delete(url, classifyMap); }
方法三:
delete(URI url)
5. 通用請求接口
RestTemplate還有一個更強大,而且更通用的接口exchange(),它同時支持GET、POST、PUT、DELETE方法,並且還可以添加請求參數頭。
方法一:
exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Map<String,?> uriVariables)
exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables)
exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType)
這個方法比前面講過的專用接口多了兩個參數,其中:
method表示HTTP請求方法,也就是GET、POST等;
requestEntity封裝了請求消息對象,POST和PUT會用到,同時requestEntity中還可以添加請求頭參數。
GET請求代碼示例:
@RequestMapping(value = "/exchange1") public String testExchangeForGet1() throws URISyntaxException { String url = urlPrefix + "/get?protocol={protocol}&operator={operator}"; ResponseEntity<Device> response = restTemplate.exchange(url, HttpMethod.GET, null, Device.class, "NBIot", "ChinaMobile"); System.out.println("Device: " + response.getBody() + ", code: " + response.getStatusCodeValue()); return response.getBody().toString(); }
GET請求不需要請求消息,因此requestEntity為null。
POST請求代碼示例:
@RequestMapping(value = "/exchange2") public String testExchangeForPost1() throws URISyntaxException { String url = urlPrefix + "/post"; Device device = Device.builder().ip("1.2.3.4").mac("FF:FF:FF:FF:FF:FF").build(); HttpEntity<Device> requestEntity = new HttpEntity<Device>(device, null); ResponseEntity<Device> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, Device.class); System.out.println("Device: " + response.getBody() + ", code: " + response.getStatusCodeValue()); return response.getBody().toString(); }
這里用Device構造了一個requestEntity對象。
PUT請求代碼示例:
@RequestMapping(value = "/exchange3") public void testExchangeForPut() throws URISyntaxException { String url = urlPrefix + "/put"; Device device = Device.builder().ip("1.2.3.4").mac("FF:FF:FF:FF:FF:FF").build(); HttpEntity<Device> requestEntity = new HttpEntity<Device>(device, null); ResponseEntity<Device> response = restTemplate.exchange(url, HttpMethod.PUT, requestEntity, Device.class); System.out.println("code: " + response.getStatusCodeValue()); }
DELETE請求代碼示例:
@RequestMapping(value = "/exchange4") public void testExchangeForDelete() throws URISyntaxException { String url = urlPrefix + "/delete"; ResponseEntity<Device> response = restTemplate.exchange(url, HttpMethod.DELETE, null, Device.class); System.out.println("code: " + response.getStatusCodeValue()); }
方法二:
url和method都可以封裝到requestEntity中,這樣的話方法簽名看上去就更簡潔了:
exchange(RequestEntity<?> requestEntity, Class<T> responseType)
exchange(RequestEntity<?> requestEntity, ParameterizedTypeReference<T> responseType)
GET請求代碼示例:
@RequestMapping(value = "/exchange5") public String testExchangeForGet2() throws URISyntaxException { String url = urlPrefix + "/get"; RequestEntity<Device> requestEntity = new RequestEntity<Device>(HttpMethod.GET, new URI(url)); ResponseEntity<Device> response = restTemplate.exchange(requestEntity, Device.class); System.out.println("Device: " + response.getBody() + ", code: " + response.getStatusCodeValue()); return response.getBody().toString(); }
POST請求代碼示例:
@RequestMapping(value = "/exchange6") public String testExchangeForPost2() throws URISyntaxException { String url = urlPrefix + "/post"; Device device = Device.builder().ip("1.2.3.4").mac("FF:FF:FF:FF:FF:FF").build(); RequestEntity<Device> requestEntity = new RequestEntity<Device>(device, HttpMethod.POST, new URI(url)); ResponseEntity<Device> response = restTemplate.exchange(requestEntity, Device.class); System.out.println("Device: " + response.getBody() + ", code: " + response.getStatusCodeValue()); return response.getBody().toString(); }
有時候,我們不僅僅返回一個對象,而是返回一個容器類,比如List。
如果我們使用之前的方法,代碼可能是這樣的:
@RequestMapping(value = "/exchange7") public List testExchangeForNoType() throws URISyntaxException { String url = urlPrefix + "/getall"; ResponseEntity<List> response = restTemplate.exchange(url, HttpMethod.GET, null, List.class); List deviceList = response.getBody(); System.out.println("Device: " + deviceList + ", code: " + response.getStatusCodeValue()); return response.getBody(); }
如果我們跟蹤代碼就會發現,response.getBody()方法返回的不是我們所期望的List<Device>,而是一個List<<LinkedHashMap<K,V>>。因為exchange不知道List中具體是什么類型的對象。
這時我們可以使用下面這幾個支持泛型的方法:
exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType, Map<String,?> uriVariables)
exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType, Object... uriVariables)
exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType)
代碼示例如下:
@RequestMapping(value = "/exchange8") public List<Device> testExchangeForParameterizedTypeReference() throws URISyntaxException { String url = urlPrefix + "/getall"; ParameterizedTypeReference<List<Device>> parameterizedTypeReference = new ParameterizedTypeReference<List<Device>>(){}; ResponseEntity<List<Device>> response = restTemplate.exchange(url, HttpMethod.GET, null, parameterizedTypeReference); List<Device> deviceList = response.getBody(); System.out.println("Device: " + deviceList + ", code: " + response.getStatusCodeValue()); return response.getBody(); }
如果跟蹤代碼會發現,response.getBody()這時候已經返回的是一個List<Device>類型的列表。
6. 添加頭參數
有時候需要在請求中添加一些頭參數,比如指定字符編碼、指定cookie信息等,這時候可以把頭參數塞到requestEntity對象中。
代碼示例如下:
@RequestMapping(value = "/exchange10") public Device testForHeader() throws URISyntaxException { String url = urlPrefix + "/post"; Device device = Device.builder().ip("1.2.3.4").mac("FF:FF:FF:FF:FF:FF").build(); HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.add("Content-Type", "application/json;charset=UTF-8"); RequestEntity<Device> requestEntity = new RequestEntity<Device>(device, requestHeaders, HttpMethod.POST, new URI(url)); ResponseEntity<Device> response = restTemplate.exchange(requestEntity, Device.class); System.out.println("Device: " + response.getBody() + ", code: " + response.getStatusCodeValue()); return response.getBody(); }
7. 處理返回錯誤
有時候HTTP請求並不能正常返回,這時候我們需要對異常返回做相應的處理。
雖然RestTemplate在返回的ResponseEntity中會包含錯誤碼,但是在發送請求遇到錯誤的時候並不會把錯誤碼和錯誤信息直接放在這個ResponseEntity中,而是會直接拋出異常,我們需要對異常進行處理。
錯誤返回可以分為以下幾類:
HttpClientErrorException:4xx錯誤,屬於客戶端錯誤,比如錯誤的請求,鑒權失敗等;
HttpServerErrorException:5xx錯誤,屬於服務端錯誤,比如服務端暫時不可用,服務端內部錯誤等;
UnknownHttpStatusCodeException:未知錯誤碼錯誤,這種可能屬於協議錯誤,返回的HTTP消息的沒有錯誤碼或者是錯誤碼不在HTTP協議定義的范圍內。
代碼示例如下:
@RequestMapping(value = "/exchange11") public Device testForResponse() throws URISyntaxException { String url = urlPrefix + "/get?protocol={protocol}&operator={operator}"; RequestEntity<Device> requestEntity = new RequestEntity<Device>(HttpMethod.GET, new URI(url)); try { ResponseEntity<Device> response = restTemplate.exchange(requestEntity, Device.class); System.out.println("Device: " + response.getBody() + ", code: " + response.getStatusCodeValue()); return response.getBody(); } catch(HttpClientErrorException ce) { // 處理4xx錯誤 } catch(HttpServerErrorException se) { // 處理5xx錯誤 } catch(UnknownHttpStatusCodeException ue) { // 處理未知錯誤 } return null; }