[SpringBoot]-使用RestTemplate实现Restful接口客户端 一、什么是RestTemplate 二、创建GET请求 三、创建POST请求 四、创建PUT请求 五、创建DELTE请求 六、exchange方法 七、手动指定转换器(HttpMessageConverter) 八、设置拦截器(如用于自定义请求头) 九、设置底层连接方式
参考:
https://blog.****.net/jinjiniao1/article/details/100849237
https://www.jianshu.com/p/90ec27b3b518
传统情况下在Java代码里访问restful服务,一般使用使用Java自带的HttpUrlConnection、或Apache的HttpClient。不过此种方法使用起来太过繁琐。Spring提供了一种简单便捷的模板类来进行操作,这就是RestTemplate。
RestTemplate是从Spring3.0开始支持的一个HTTP请求工具,它提供了常见的REST请求方案的模版,例如GET、POST、PUT、DELETE请求,以及一些通用的请求执行方法 exchange以及execute。
RestTemplate继承自InterceptingHttpAccessor并且实现了RestOperations接口,其中RestOperations接口定义了基本的RESTful操作,这些操作在RestTemplate中都得到了实现。
二、创建GET请求
RestTemplate类在org.springframework.web.client.RestTemplate;包中定义。
RestTemplate中的GET请求相关的方法有如下几个:
//getForObject方法
public <T> T getForObject(String URI, Class<T> responeType, Object... uriVariables) throws RestClientException {...}
public <T> T getForObject(String URI, Class<T> responeType, Map<String, ?> uriVariables) throws RestClientException {...}
public <T> T getForObject(URI URI, Class<T> responeType) throws RestClientException {...}
//getForEntity方法
public <T> ResponeEntity<T> getForEntity(String URI, Class<T> responeType, Object... uriVariables) throws RestClientException {...}
public <T> ResponeEntity<T> getForEntity(String URI, Class<T> responeType, Map<String, ?> uriVariables) throws RestClientException {...}
public <T> ResponeEntity<T> getForEntity(URI URI, Class<T> responeType) throws RestClientException {...}
- getForEntity和getForObject都有三个重载方法,其重载方法的参数一样。方法的返回值都表示HTTP请求的返回值。
- getForEntity和getForObject的差异:主要体现在返回值不同。RestTemplate 发送的是 HTTP 请求,响应消息中必然有响应头。
- 如需要获取HTTP响应消息头,则需使用getForEntity()方法。getForEntity()此方法返回的是一个ResponseEntity实例,此实例中包含了响应头+响应数据。
- getForObject()方法的返回值就是Restful接口服务方返回的的数据,无法获取到响应头。
- GET 请求的参数只能在URI中传送。uriVariables参数就适用于设置URI中携带的参数。
1、getForEntity()方法使用
getForEntity()方法返回的值类型为ResponeEntity。其在org.springframework.http包中定义。其定义如下:
public class ResponeEntity<T> Extern HttpEntity<T> {
private final Object status;
public HttpStatus getStatusCode {...}
}
基类HttpEntity中定义了getBody()方法。
public class HttpEntity<T> {
....
private final HttpHeaders headers;
private final T Body;
...
public HttpHeaders getHeaders() {...}
public T getBody() {...}
}
以Restful接口为http://www.test.com/hello?name=%s为例。则Resful客户端使用getForEntity()方法的代码片段如下:
public String hello(String name) {
String url = "http://www.test.com/hello?name/hello?name={1}";
ResponseEntity<String> responseEntity = RestTemplate.getForEntity(url, String.class, name);
StringBuffer sb = new StringBuffer();
HttpStatus statusCode = responseEntity.getStatusCode();
String body = responseEntity.getBody();
sb.append("statusCode:")
.append(statusCode)
.append("</br>")
.append("body:")
.append(body)
.append("</br>");
HttpHeaders headers = responseEntity.getHeaders();
Set<String> keySet = headers.keySet();
for (String s : keySet) {
sb.append(s)
.append(":")
.append(headers.get(s))
.append("</br>");
}
return sb.toString();
}
getForEntity 方法。
- 第一个参数:是 url,url中有一个占位符 {1},如果有多个占位符分别用 {2} 、{3} … 去表示
- 第二个参数:Restful接口返回的数据类型
- 第三个参数:变长参数,用来给占位符填值。
在返回的 ResponseEntity 实体中,可以获取响应头中的信息。其中: - getStatusCode 方法用来获取响应状态码;
- getBody 方法用来获取响应数据;
- getHeaders 方法用来获取响应头。
getForEntity()还有另外两种重载方法介绍:
1、第一个是占位符不使用数字,而是使用参数的 key。同时将参数放入到一个 map 中,map 中的 key 和占位符的 key 相对应,map 中的 value 就是参数的具体值。
样例:
public String hello(String name) {
...
String url = "http://www.test.com/hello?name/hello?name={name}";
Map<String, Object> map = new HashMap<>();
map.put("name", name);
ResponseEntity<String> responseEntity = RestTemplate.getForEntity(url, String.class, map);
...
}
这种方式传参可能看起来更直观一些。
2、使用 Uri 对象,此时参数可以直接拼接在地址中
样例:
public String hello(String name) {
...
String url = "http://www.test.com/hello?name/hello?name=“+URLEncoder.encode(name,"UTF-8");
URI uri = URI.create(url);
ResponseEntity<String> responseEntity = RestTemplate.getForEntity(uri, String.class);
...
}
注意:这种传参方式,参数如果是中文的话,需要 URLEncoder.encode()方法对参数进行编码。
2、getForObject()方法使用
getForObject 方法和 getForEntity 方法类似。
样例
public String hello(String name) {
...
String url = "http://www.test.com/hello?name/hello?name=“+URLEncoder.encode(name,"UTF-8");
URI uri = URI.create(url);
String str = restTemplate.getForObject(uri, String.class);
...
}
注意:这里str就是Restful服务端返回的数据。如果开发者Restful接口返回的数据,不关系 HTTP响应头,那么可以使用getForObject()方法。
三、创建POST请求
GET 请求相比,RestTemplate中的 POST 请求多了一个类型的方法。其定义如下:
//psotForObject方法
public <T> T postForObject(String URI, Object request,Class<T> responeType, Object... uriVariables) throws RestClientException {...}
public <T> T getForObject(String URI, Object request,Class<T> responeType, Map<String, ?> uriVariables) throws RestClientException {...}
public <T> T getForObject(URI URI, Object request,Class<T> responeType) throws RestClientException {...}
//postForEntity方法
public <T> ResponeEntity<T> postForEntity(String URI, Object request, Class<T> responeType, Object... uriVariables) throws RestClientException {...}
public <T> ResponeEntity<T> postForEntity(String URI, Object request, Class<T> responeType, Map<String, ?> uriVariables) throws RestClientException {...}
public <T> ResponeEntity<T> postForEntity(URI URI, Object request, Class<T> responeType) throws RestClientException {...}
//psotForLocation方法
public URI postForLocation(String URI, Object request,Object... uriVariables) throws RestClientException {...}
public URI postForLocation(String URI, Object request,Map<String, ?> uriVariables) throws RestClientException {...}
public URI postForLocation(URI URI, Object request) throws RestClientException {...}
**如上方法可以看出,Post 请求的参数可以在URI中传送,也可以通过消息Body传送:
- uriVariables参数就适用于设置URI中携带的参数。
- request参数设置的就是在HTTP请求的Body中携带的参数。**
1、postForEntity()方法
在POST请求中,当然可以可以通过URI传递参数(类似GET方法)
public String hello(String name) {
...
String url = "http://www.test.com/hello?name/hello?name={1}";
ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, null, String.class, name);
return responseEntity.getBody();
}
在 POST 请求中,也可以通过Body传递数据,数据可以是 key/value的形式、也可以是 JSON 格式。
使用的额是PostForEntity方法的request参数。
- 方法的request参数使用key/value形式
public String hello(String name) {
...
String url = "http://www.test.com/hello";
MultiValueMap map = new LinkedMultiValueMap();
map.add("name", name);
ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, map, String.class);
return responseEntity.getBody();
...
}
如上:
- 第一个参数:请求地址;
- 第二个参数 :请求参数。map 对象中存放着请求参数 key/value,RestTemplate会自动把参数转换为JSON; (详见后续章节”转换器说明“)
- 第三个参数:HTTP响应数据被转换成的对象类型。
当然,第一个参数也可以换成一个 URI 对象。
- 方法的request参数使用JSON形式
以发送的JSON对应的Java类型是User,Restful服务端响应中也原样返回User类型的JSON字符串为例。
User定义如下参考:
public class User {
private String username;
private String address;
//省略getter/setter
}
postForEntity()方法调用参考:
public String hello(String name) {
...
String url = "http://www.test.com/hello";
User u1 = new User();
u1.setUsername("TestName");
u1.setAddress("shenzhen");
ResponseEntity<User> responseEntity = restTemplate.postForEntity(url, u1, User.class);
return responseEntity.getBody();
...
}
如上:
- 第一个参数:请求地址;
- 第二个参数 :请求参数。RestTemplate会自动将一个对象转换成JSON进行传输。
- 第三个参数:HTTP响应数据被转换成的对象类型。
2、postForObject()方法
postForObject 和 postForEntity 基本一致,就是返回类型不同而已,这里再赘述。
3、postForLocation()方法
POST 请求一般用来添加数据,但有时候需要将刚刚添加成功的数据的URL返回来,此时就可以使用这个方法。postForLocation 方法的返回值是一个 URI对象。
一个常见的使用场景如用户注册功能,用户注册成功之后,可能就自动跳转到登录页面了,此时就可以使用该方法。
样例:
- Restful接口服务端提供用户注册功能 + 用户登录功能。如下:
...
@RequestMapping("/register")
public String register(User user) throws UnsupportedEncodingException {
return "redirect:/loginPage?username=" + URLEncoder.encode(user.getUsername(),"UTF-8") + "&address=" + URLEncoder.encode(user.getAddress(),"UTF-8");
}
@GetMapping("/loginPage")
@ResponseBody
public String loginPage(User user) {
return "loginPage:" + user.getUsername() + ":" + user.getAddress();
}
...
- 在Restful服务客户端调用注册接口。如下:
@GetMapping("/Test")
public String test() {
List<ServiceInstance> list = discoveryClient.getInstances("provider");
...
String url = "http://www.test.com/register";
MultiValueMap map = new LinkedMultiValueMap();
map.add("username", "TestName");
map.add("address", "shenzhen");
URI uri = restTemplate.postForLocation(url, map);
String s = restTemplate.getForObject(uri, String.class);
return s;
}
这里首先调用postForLocation()获取URI地址,然后再利用getForObject()方法请求新的URI。
注意:postForLocation() 方法返回的URI实际上是指响应头的Location字段。接口服务端的 register的响消息头中必须要有Location字段,否则postForLocation方法的返回值为null。
四、创建PUT请求
PUT 请求方法比较少,只有三个:
//put方法
public void put(String URI, Object request,Object... uriVariables) throws RestClientException {...}
public void put(String URI, Object request,Map<String, ?> uriVariables) throws RestClientException {...}
public void put(URI URI, Object request) throws RestClientException {...}
三个重载的方法其参数与 POST方法类似。
- 第一个参数:请求地址;
- 第二个参数:请求参数。可以是mao格式保存的 key/value,也可以是JSON格式。
- 第三个参数:如果存在,则用于表示URI中占位服符。
**如上方法可以看出,put请求的参数可以在URI中传送,也可以通过消息Body传送:
- uriVariables参数就适用于设置URI中携带的参数。
- request参数设置的就是在HTTP请求的Body中携带的参数。**
样例:
public String hello(String name) {
...
String url = "http://www.test.com/hello";
MultiValueMap map = new LinkedMultiValueMap();
map.add("username", "TestName");
map.add("address", "shenzhen");
restTemplate.put(url, map);
...
}
五、创建DELTE请求
DELETE请求也只有三个方法,如下:
//delete方法
public void delete(String URI, Object... uriVariables) throws RestClientException {...}
public void delete(String URI, Map<String, ?> uriVariables) throws RestClientException {...}
public void delete(URI URI) throws RestClientException {...}
DELETE 请求的参数只能在地址栏传送。
- 可以是直接放在路径中。
- 也可以用URI占位符方式。
样例:
public String Delete(String name) {
...
String url1 = "http://www.test.com/user/{id}";
restTemplate.delete(url1, 1);
...
String url2 = "http://www.test.com/user/?username={username}";
Map<String,String> map = new HashMap<>();
map.put("username", name);
restTemplate.delete(url2, map);
...
}
六、exchange方法
在 RestTemplate 中还有一个通用的方法 exchange。其通用在于:它既能做 GET 请求,也能做 POST 请求,也能做其它各种类型的请求,这个方法需要你在调用的时候去指定请求类型。
如果开发者需要对请求进行封装,使用它再合适不过了,举个简单例子:
public String hello(String name) {
...
RestTemplate restTemplate = new RestTemplate();
String url = "http://www.test.com/hello?name=“+URLEncoder.encode(name,"UTF-8");
HttpHeaders headers = new HttpHeaders();
headers.add("cookie","justdojava");
HttpEntity<MultiValueMap<String,String>> request = new HttpEntity<>(null,headers);
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, request, String.class);
System.out.println(responseEntity.getBody());
}
七、手动指定转换器(HttpMessageConverter)
- 前面可以看到,通过RestTemplate发送POST和PUT消息时,可以向方法传入“map格式的key/value”、或“Java对象实例”,方法自动把其转换成JSON字符串附在HTTP请求的Body中。
- 同样,收到HTTP响应后,Body中也是JSON字符串。但是postForObject()方法的返回值也是Java对象。
RestTemplate是如何实现请求中把Java对象转换成JSON字符串,响应中把JSON字符串转换成Java对象呢?
这就涉及到转换器。
RestTemplate通过HttpMessageConverter自动帮我们做了转换的操作。
默认情况下,RestTemplate自动帮我们注册了一组HttpMessageConverter用来处理一些不同的contentType的请求。如:
- StringHttpMessageConverter:来处理text/plain类型HTTP的body的转换;
- MappingJackson2HttpMessageConverter:来处理application/json类型HTTP的body的转换;
- MappingJackson2XmlHttpMessageConverter:来处理application/xml类型HTTP的body的转换。
可以在org.springframework.http.converter包下找到所有spring帮我们实现好的转换器。
如果默认的转换器不能满足需求,你也可以自定义转换器:实现org.springframework.http.converter.HttpMessageConverter接口。详情参考官方api。
实现好HttpMessageConverter后,如何把它注册到RestTemplate中呢?
以已经实现好一个名字叫GsonHttpMessageConverter的转换器为例。
//创建RestTemplate实例
RestTemplate restTemplate = new RestTemplate();
//获取RestTemplate默认配置好的所有转换器
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
//默认的MappingJackson2HttpMessageConverter在第7个 先把它移除掉
messageConverters.remove(6);
//添加上GSON的转换器
messageConverters.add(6, new GsonHttpMessageConverter());
八、设置拦截器(如用于自定义请求头)
有的时候我们会有一些特殊的需求,如:模拟 cookie需自定义请求头。
此时我们可以使用拦截器(ClientHttpRequestInterceptor) 。
自定义拦截器需要我们自己实现org.springframework.http.client.ClientHttpRequestInterceptor接口。
样例:
实现一个拦截器,新增cookie消息头。
public class TestTokenInterceptor implements ClientHttpRequestInterceptor
{
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException
{
HttpHeaders headers = request.getHeaders();
headers.add("cookie","justdojava");
return execution.execute(request, body);
}
}
创建RestTemplate实例,并向其添加拦截器:
public String hello(String name) {
...
RestTemplate restTemplate = new RestTemplate();
String url = "http://www.test.com/hello?name=“+URLEncoder.encode(name,"UTF-8");
URI uri = URI.create(url);
//向restTemplate中添加自定义的拦截器
restTemplate.getInterceptors().add(new TestTokenInterceptor());
//参考:也可把链接器设置为只有自定定义的
//restTemplate.setInterceptors(Collections.singletonList(new TestTokenInterceptor());
String str = restTemplate.getForObject(uri, String.class);
...
}
九、设置底层连接方式
创建一个RestTemplate的实例,您可以像上述例子中简单地调用默认的无参数构造函数。这将使用java.net包中的标准Java类作为底层实现来创建HTTP请求。
但有些时候,我们需要像传统的HttpClient那样设置HTTP请求的一些属性。RestTemplate使用了一种很偷懒的方式实现了这个需求,那就是直接使用一个HttpClient作为底层实现。
样例:
//生成一个设置了连接超时时间、请求超时时间、异常最大重试次数的httpClient
RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build();
HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(config).setRetryHandler(new DefaultHttpRequestRetryHandler(5, false));
HttpClient httpClient = builder.build();
//使用httpClient创建一个ClientHttpRequestFactory的实现
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
//ClientHttpRequestFactory作为参数构造一个使用作为底层的RestTemplate
RestTemplate restTemplate = new RestTemplate(requestFactory);