1、RestTemplate 概述
发送 http 请求,估计很多人用过 httpclient 和 okhttp,确实挺好用的,而 Spring web 中的 RestTemplate 和这俩的功能类似,也是用来发送 http 请求的,不过用法上面比前面的 2 位要容易很多。
spring 框架提供的 RestTemplate 类可用于在应用中调用 rest 服务,它简化了与 http 服务的通信方式,统一了 RESTful 的标准,封装了 http 链接, 我们只需要传入 url 及返回值类型即可。相较于之前常用的 HttpClient,RestTemplate 是一种更优雅的调用 RESTful 服务的方式。
在 Spring 应用程序中访问第三方 REST 服务与使用 Spring RestTemplate 类有关。RestTemplate 类的设计原则与许多其他 Spring 模板类(例如 JdbcTemplate、JmsTemplate)相同,为执行复杂任务提供了一种具有默认行为的简化方法。
RestTemplate 默认依赖 JDK 提供 http 连接的能力(HttpURLConnection),如果有需要的话也可以通过 setRequestFactory 方法替换为例如 Apache HttpComponents、Netty 或 OkHttp 等其它 HTTP library。
考虑到 RestTemplate 类是为调用 REST 服务而设计的,因此它的主要方法与 REST 的基础紧密相连就不足为奇了,后者是 HTTP 协议的方法:HEAD、GET、POST、PUT、DELETE 和 OPTIONS。例如,RestTemplate 类具有 headForHeaders()、getForObject()、postForObject()、put()和 delete()等方法。
下面给大家上案例,案例是重点,通过案例,把我知道的用法都给盘出来。
2、案例代码
2.1、git 地址
https://gitee.com/javacode2018/springmvc-series
2.2、关键代码位置
文中的所有 controller 代码,在RestTemplateTestController
类中。
所有@Test 用例的代码,在RestTemplateTest
。
2.3、如何运行测试用例?
- 拉取项目
- 将 chat16-RestTemplate 模块发布到 tomcat9 中
- 运行 RestTemplateTest 中对应的用例即可
下面咱们来看 RestTemplate 常见的用法汇总。
3、发送 Get 请求
3.1、普通请求
接口代码
@GetMapping("/test/get")
@ResponseBody
public BookDto get() {
return new BookDto(1, "SpringMVC系列");
}
使用 RestTemplate 调用上面这个接口,通常有 2 种写法,如下
@Test
public void test1() {
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/chat16/test/get";
//getForObject方法,获取响应体,将其转换为第二个参数指定的类型
BookDto bookDto = restTemplate.getForObject(url, BookDto.class);
System.out.println(bookDto);
}
@Test
public void test2() {
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/chat16/test/get";
//getForEntity方法,返回值为ResponseEntity类型
// ResponseEntity中包含了响应结果中的所有信息,比如头、状态、body
ResponseEntity<BookDto> responseEntity = restTemplate.getForEntity(url, BookDto.class);
//状态码
System.out.println(responseEntity.getStatusCode());
//获取头
System.out.println("头:" + responseEntity.getHeaders());
//获取body
BookDto bookDto = responseEntity.getBody();
System.out.println(bookDto);
}
test1 输出
BookDto{id=1, name='SpringMVC系列'}
test2 输出
200 OK
头:[Content-Type:"application/json;charset=UTF-8", Transfer-Encoding:"chunked", Date:"Sat, 02 Oct 2021 07:05:15 GMT", Keep-Alive:"timeout=20", Connection:"keep-alive"]
BookDto{id=1, name='SpringMVC系列'}
3.2、url 中含有动态参数
接口代码
@GetMapping("/test/get/{id}/{name}")
@ResponseBody
public BookDto get(@PathVariable("id") Integer id, @PathVariable("name") String name) {
return new BookDto(id, name);
}
使用 RestTemplate 调用上面这个接口,通常有 2 种写法,如下
@Test
public void test3() {
RestTemplate restTemplate = new RestTemplate();
//url中有动态参数
String url = "http://localhost:8080/chat16/test/get/{id}/{name}";
Map<String, String> uriVariables = new HashMap<>();
uriVariables.put("id", "1");
uriVariables.put("name", "SpringMVC系列");
//使用getForObject或者getForEntity方法
BookDto bookDto = restTemplate.getForObject(url, BookDto.class, uriVariables);
System.out.println(bookDto);
}
@Test
public void test4() {
RestTemplate restTemplate = new RestTemplate();
//url中有动态参数
String url = "http://localhost:8080/chat16/test/get/{id}/{name}";
Map<String, String> uriVariables = new HashMap<>();
uriVariables.put("id", "1");
uriVariables.put("name", "SpringMVC系列");
//getForEntity方法
ResponseEntity<BookDto> responseEntity = restTemplate.getForEntity(url, BookDto.class, uriVariables);
BookDto bookDto = responseEntity.getBody();
System.out.println(bookDto);
}
test3 输出
BookDto{id=1, name='SpringMVC系列'}
test4 输出
BookDto{id=1, name='SpringMVC系列'}
3.3、接口返回值为泛型
接口代码
@GetMapping("/test/getList")
@ResponseBody
public List<BookDto> getList() {
return Arrays.asList(
new BookDto(1, "Spring高手系列"),
new BookDto(2, "SpringMVC系列")
);
}
当接口的返回值为泛型的时候,这种情况比较特殊,使用 RestTemplate 调用上面这个接口,代码如下,需要用到restTemplate.exchange
的方法,这个方法中有个参数是ParameterizedTypeReference
类型,通过这个参数类指定泛型类型
@Test
public void test5() {
RestTemplate restTemplate = new RestTemplate();
//返回值为泛型
String url = "http://localhost:8080/chat16/test/getList";
//若返回结果是泛型类型的,需要使用到exchange方法,
//这个方法中有个参数是ParameterizedTypeReference类型,通过这个参数类指定泛型类型
ResponseEntity<List<BookDto>> responseEntity =
restTemplate.exchange(url,
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<BookDto>>() {
});
List<BookDto> bookDtoList = responseEntity.getBody();
System.out.println(bookDtoList);
}
输出
[BookDto{id=1, name='Spring高手系列'}, BookDto{id=2, name='SpringMVC系列'}]
3.4、下载小文件
接口代码如下,这个接口会下载服务器端的 1.txt 文件。
@GetMapping("/test/downFile")
@ResponseBody
public HttpEntity<InputStreamResource> downFile() {
//将文件流封装为InputStreamResource对象
InputStream inputStream = this.getClass().getResourceAsStream("/1.txt");
InputStreamResource inputStreamResource = new InputStreamResource(inputStream);
//设置header
MultiValueMap<String, String> headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=1.txt");
HttpEntity<InputStreamResource> httpEntity = new HttpEntity<>(inputStreamResource);
return httpEntity;
}
使用 RestTemplate 调用这个接口,代码如下,目前这个文件的内容比较少,可以直接得到一个数组。
@Test
public void test6() {
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/chat16/test/downFile";
//文件比较小的情况,直接返回字节数组
ResponseEntity<byte[]> responseEntity = restTemplate.getForEntity(url, byte[].class);
//获取文件的内容
byte[] body = responseEntity.getBody();
String content = new String(body);
System.out.println(content);
}
注意:如果文件大的时候,这种方式就有问题了,会导致 oom,要用下面的方式了。
3.5、下载大文件
接口代码,继续使用上面下载 1.txt 的代码
@GetMapping("/test/downFile")
@ResponseBody
public HttpEntity<InputStreamResource> downFile() {
//将文件流封装为InputStreamResource对象
InputStream inputStream = this.getClass().getResourceAsStream("/1.txt");
InputStreamResource inputStreamResource = new InputStreamResource(inputStream);
//设置header
MultiValueMap<String, String> headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=1.txt");
HttpEntity<InputStreamResource> httpEntity = new HttpEntity<>(inputStreamResource);
return httpEntity;
}
此时使用 RestTemplate 调用这个接口,代码如下
文件比较大的时候,比如好几个 G,就不能返回字节数组了,会把内存撑爆,导致 OOM,需要使用 execute 方法了,这个方法中有个 ResponseExtractor 类型的参数,restTemplate 拿到结果之后,会回调{@link ResponseExtractor#extractData}这个方法,在这个方法中可以拿到响应流,然后进行处理,这个过程就是变读边处理,不会导致内存溢出
@Test
public void test7() {
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/chat16/test/downFile";
String result = restTemplate.execute(url,
HttpMethod.GET,
null,
new ResponseExtractor<String>() {
@Override
public String extractData(ClientHttpResponse response) throws IOException {
System.out.println("状态:"+response.getStatusCode());
System.out.println("头:"+response.getHeaders());
//获取响应体流
InputStream body = response.getBody();
//处理响应体流
String content = IOUtils.toString(body, "UTF-8");
return content;
}
}, new HashMap<>());
System.out.println(result);
}
3.6、传递头
接口代码
@GetMapping("/test/header")
@ResponseBody
public Map<String, List<String>> header(HttpServletRequest request) {
Map<String, List<String>> header = new LinkedHashMap<>();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
Enumeration<String> values = request.getHeaders(name);
List<String> list = new ArrayList<>();
while (values.hasMoreElements()) {
list.add(values.nextElement());
}
header.put(name, list);
}
return header;
}
使用 RestTemplate 调用接口,请求头中传递数据,代码如下,注意代码①和②
,这两处是关键,用到了HttpHeaders
和RequestEntity
- 请求头放在 HttpHeaders 对象中
- RequestEntity:请求实体,请求的所有信息都可以放在 RequestEntity 中,比如 body 部分、头、请求方式、url 等信息
@Test
public void test8() {
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/chat16/test/header";
//①:请求头放在HttpHeaders对象中
MultiValueMap<String, String> headers = new HttpHeaders();
headers.add("header-1", "V1");
headers.add("header-2", "Spring");
headers.add("header-2", "SpringBoot");
//②:RequestEntity:请求实体,请求的所有信息都可以放在RequestEntity中,比如body部分、头、请求方式、url等信息
RequestEntity requestEntity = new RequestEntity(
null, //body部分数据
headers, //头
HttpMethod.GET,//请求方法
URI.create(url) //地址
);
ResponseEntity<Map<String, List<String>>> responseEntity = restTemplate.exchange(requestEntity,
new ParameterizedTypeReference<Map<String, List<String>>>() {
});
Map<String, List<String>> result = responseEntity.getBody();
System.out.println(result);
}
输出
{accept=[application/json, application InputStream inputStream = RestTemplateTest.class.getResourceAsStream("/1.txt"); InputStreamResource inputStreamResource = new InputStreamResource(inputStream) { @Override public String getFilename() { return "1.txt"; } @Override public long contentLength() throws IOException { return inputStream.available(); } }; body.add("file1", inputStreamResource); //③:头 HttpHeaders headers = new HttpHeaders(); headers.add("header1", "v1"); headers.add("header2", "v2"); //④:请求实体 RequestEntity<MultiValueMap<String, Object>> requestEntity = new RequestEntity<>(body, headers, HttpMethod.POST, URI.create(url)); //⑤:发送请求(请求实体,返回值需要转换的类型) ResponseEntity<Map<String, String>> responseEntity = restTemplate.exchange( requestEntity, new ParameterizedTypeReference<Map<String, String>>() { }); Map<String, String> result = responseEntity.getBody(); System.out.println(result); }