一、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; }