RestTemplate 概述
Spring 框架提供的 RestTemplate 类可用于在应用中调用 rest 服务,它简化了与 http 服务的通信方式,统一了 RESTful 的标准,封装了 http 链接, 我们只需要传入 url 及返回值类型即可。相较于之前常用的 HttpClient,RestTemplate 是一种更优雅的调用 RESTful 服务的方式。
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()等方法。
发送 Get 请求
接口代码
1 2 3 4 5
| @GetMapping("/test/get") @ResponseBody public BookDto get() { return new BookDto(1, "SpringMVC系列"); }
|
使用 RestTemplate 调用上面这个接口,通常有 2 种写法,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Test public void test1() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/get"; 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"; ResponseEntity<BookDto> responseEntity = restTemplate.getForEntity(url, BookDto.class); System.out.println(responseEntity.getStatusCode()); System.out.println("头:" + responseEntity.getHeaders()); BookDto bookDto = responseEntity.getBody(); System.out.println(bookDto); }
|
url 中含有动态参数
1 2 3 4 5
| @GetMapping("/test/get/{id}/{name}") @ResponseBody public BookDto get(@PathVariable("id") Integer id, @PathVariable("name") String name) { return new BookDto(id, name); }
|
使用 RestTemplate 调用上面这个接口,通常有 2 种写法,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Test public void test3() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/get/{id}/{name}"; Map<String, String> uriVariables = new HashMap<>(); uriVariables.put("id", "1"); uriVariables.put("name", "SpringMVC系列"); BookDto bookDto = restTemplate.getForObject(url, BookDto.class, uriVariables); System.out.println(bookDto); }
@Test public void test4() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/get/{id}/{name}"; Map<String, String> uriVariables = new HashMap<>(); uriVariables.put("id", "1"); uriVariables.put("name", "SpringMVC系列"); ResponseEntity<BookDto> responseEntity = restTemplate.getForEntity(url, BookDto.class, uriVariables); BookDto bookDto = responseEntity.getBody(); System.out.println(bookDto); }
|
接口返回值为泛型
1 2 3 4 5 6 7 8
| @GetMapping("/test/getList") @ResponseBody public List<BookDto> getList() { return Arrays.asList( new BookDto(1, "Spring高手系列"), new BookDto(2, "SpringMVC系列") ); }
|
当接口的返回值为泛型的时候,这种情况比较特殊,使用 RestTemplate 调用上面这个接口,代码如下,需要用到restTemplate.exchange的方法,这个方法中有个参数是ParameterizedTypeReference类型,通过这个参数类指定泛型类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Test public void test5() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/getList"; ResponseEntity<List<BookDto>> responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference<List<BookDto>>() { }); List<BookDto> bookDtoList = responseEntity.getBody(); System.out.println(bookDtoList); }
|
下载小文件
1 2 3 4 5 6 7 8 9 10 11 12
| @GetMapping("/test/downFile") @ResponseBody public HttpEntity<InputStreamResource> downFile() { InputStream inputStream = this.getClass().getResourceAsStream("/1.txt"); InputStreamResource inputStreamResource = new InputStreamResource(inputStream); MultiValueMap<String, String> headers = new HttpHeaders(); headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=1.txt"); HttpEntity<InputStreamResource> httpEntity = new HttpEntity<>(inputStreamResource); return httpEntity; }
|
使用 RestTemplate 调用这个接口,代码如下,目前这个文件的内容比较少,可以直接得到一个数组。
1 2 3 4 5 6 7 8 9 10 11
| @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); }
|
下载大文件
1 2 3 4 5 6 7 8 9 10 11 12
| @GetMapping("/test/downFile") @ResponseBody public HttpEntity<InputStreamResource> downFile() { InputStream inputStream = this.getClass().getResourceAsStream("/1.txt"); InputStreamResource inputStreamResource = new InputStreamResource(inputStream); MultiValueMap<String, String> headers = new HttpHeaders(); headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=1.txt"); HttpEntity<InputStreamResource> httpEntity = new HttpEntity<>(inputStreamResource); return httpEntity; }
|
此时使用 RestTemplate 调用这个接口,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @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); }
|
传递头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @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 等信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Test public void test8() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/header"; MultiValueMap<String, String> headers = new HttpHeaders(); headers.add("header-1", "V1"); headers.add("header-2", "Spring"); headers.add("header-2", "SpringBoot"); RequestEntity requestEntity = new RequestEntity( null, 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); }
|
综合案例:含头、url 动态参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @GetMapping("/test/getAll/{path1}/{path2}") @ResponseBody public Map<String, Object> getAll(@PathVariable("path1") String path1, @PathVariable("path2") String path2, HttpServletRequest request) { Map<String, Object> result = new LinkedHashMap<>(); result.put("path1", path1); result.put("path2", path2); 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); } result.put("header", header); return result; }
|
如下,使用 RestTemplate 调用接口,GET 方式、传递 header、path 中动态参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @Test public void test9() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/getAll/{path1}/{path2}"; MultiValueMap<String, String> headers = new HttpHeaders(); headers.add("header-1", "V1"); headers.add("header-2", "Spring"); headers.add("header-2", "SpringBoot"); Map<String, String> uriVariables = new HashMap<>(); uriVariables.put("path1", "v1"); uriVariables.put("path2", "v2"); HttpEntity requestEntity = new HttpEntity( null, headers ); ResponseEntity<Map<String, Object>> responseEntity = restTemplate.exchange( url, HttpMethod.GET, requestEntity, new ParameterizedTypeReference<Map<String, Object>>() { }, uriVariables ); Map<String, Object> result = responseEntity.getBody(); System.out.println(result); }
|
POST 请求
http 请求头中的 Content-Type 用来指定请求的类型,常见的有 3 种
Content-Type |
说明 |
application/x-www-form-urlencoded |
页面中普通的 form 表单提交时就是这种类型,表单中的元素会按照名称和值拼接好,然后之间用&连接,格式如:p1=v1&p2=v2&p3=v3然后通过 urlencoded 编码之后丢在 body 中发送 |
multipart/form-data |
页面中表单上传文件的时候,用到的就是这种格式 |
application/json |
将发送的数据转换为 json 格式,丢在 http 请求的 body 中发送,后端接口通常用@RequestBody 配合对象来接收。 |
普通表单请求
普通表单默认为 application/x-www-form-urlencoded 类型的请求。
1 2 3 4 5
| @PostMapping("/test/form1") @ResponseBody public BookDto form1(BookDto bookDto) { return bookDto; }
|
使用 RestTemplate 调用接口
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Test public void test10() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/form1"; MultiValueMap<String, String> body = new LinkedMultiValueMap<>(); body.add("id","1"); body.add("name","SpringMVC系列"); BookDto result = restTemplate.postForObject(url, body, BookDto.class); System.out.println(result); }
|
如果想携带头信息,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Test public void test11() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/form1"; MultiValueMap<String, String> body = new LinkedMultiValueMap<>(); body.add("id","1"); body.add("name","SpringMVC系列"); HttpHeaders headers = new HttpHeaders(); headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE); HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(body, headers); BookDto result = restTemplate.postForObject(url, httpEntity, BookDto.class); System.out.println(result); }
|
上传本地文件
上传文件 Content-Type 为 multipart/form-data 类型。
接口如下,上传上传单个文件,返回值为一个 Map 类型,是泛型类型
1 2 3 4 5 6 7 8 9
| @PostMapping(value = "/test/form2") @ResponseBody public Map<String, String> form2(@RequestParam("file1") MultipartFile file1) { Map<String, String> fileMetadata = new LinkedHashMap<>(); fileMetadata.put("文件名", file1.getOriginalFilename()); fileMetadata.put("文件类型", file1.getContentType()); fileMetadata.put("文件大小(byte)", String.valueOf(file1.getSize())); return fileMetadata; }
|
使用 RestTemplate 调用接口,主要下面代码②上传的文件需要包装为org.springframework.core.io.Resource,常用的有 3 中[FileSystemResource、InputStreamResource、ByteArrayResource],这里案例中我们用到的是 FileSystemResource 来上传本地文件,另外 2 种(InputStreamResource、ByteArrayResource)用法就比较特殊了,见下个案例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Test public void test12() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/form2"; MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); body.add("file1", new FileSystemResource(".\\src\\main\\java\\com\\javacode2018\\springmvc\\chat16\\dto\\UserDto.java")); 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); }
|
通过流或字节数组的方式上传文件
有时候,上传的文件是通过流的方式或者字节数组的方式,那么就需要用到 InputStreamResource、ByteArrayResource 这俩了。
注意:使用这俩的时候,需要重写 2 个方法,否则会上传失败
- getFilename:文件名称
- contentLength:长度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| @Test public void test13() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/form2"; MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
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); }
|
复杂表单:多个普通元素+多文件上传
1 2 3 4 5 6 7 8 9 10
| @PostMapping("/test/form3") @ResponseBody public Map<String, String> form3(UserDto userDto) { Map<String, String> result = new LinkedHashMap<>(); result.put("name", userDto.getName()); result.put("headImg", userDto.getHeadImg().getOriginalFilename()); result.put("idImgList", Arrays.toString(userDto.getIdImgList().stream(). map(MultipartFile::getOriginalFilename).toArray())); return result; }
|
UserDto:包含了多个元素(姓名、头像、多张证件照),这种可以模拟复杂的表单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public class UserDto { private String name; private MultipartFile headImg; private List<MultipartFile> idImgList;
}
@Test public void test14() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/form3"; MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); body.add("name", "路人"); body.add("headImg", new FileSystemResource(".\\src\\main\\resources\\1.jpg")); body.add("idImgList", new FileSystemResource(".\\src\\main\\resources\\2.jpg")); body.add("idImgList", new FileSystemResource(".\\src\\main\\resources\\3.jpg")); 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); }
|
发送 json 格式数据:传递 java 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @PostMapping("/test/form4") @ResponseBody public BookDto form4(@RequestBody BookDto bookDto) { return bookDto; }
@Test public void test15() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/form4"; BookDto body = new BookDto(1, "SpringMVC系列"); BookDto result = restTemplate.postForObject(url, body, BookDto.class); System.out.println(result); }
|
发送 json 格式数据:传递 java 对象,返回值为泛型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @PostMapping("/test/form5") @ResponseBody public List<BookDto> form5(@RequestBody List<BookDto> bookDtoList) { return bookDtoList; }
@Test public void test16() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/form5"; List<BookDto> body = Arrays.asList( new BookDto(1, "SpringMVC系列"), new BookDto(2, "MySQL系列")); HttpHeaders headers = new HttpHeaders(); headers.add("header1", "v1"); headers.add("header2", "v2"); RequestEntity requestEntity = new RequestEntity(body, headers, HttpMethod.POST, URI.create(url)); ResponseEntity<List<BookDto>> responseEntity = restTemplate.exchange( requestEntity, new ParameterizedTypeReference<List<BookDto>>() { }); List<BookDto> result = responseEntity.getBody(); System.out.println(result); }
|
发送 json 字符串格式数据
上面 2 个 json 案例 body 都是 java 对象,RestTemplate 默认自动配上 Content-Type=application/json
但是如果 body 的值是 json 格式字符串的时候,调用的时候需要在头中明确指定 Content-Type=application/json,写法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Test public void test17() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/form5"; String body = "[{\"id\":1,\"name\":\"SpringMVC系列\"},{\"id\":2,\"name\":\"MySQL系列\"}]";
HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); RequestEntity requestEntity = new RequestEntity(body, headers, HttpMethod.POST, URI.create(url)); ResponseEntity<List<BookDto>> responseEntity = restTemplate.exchange( requestEntity, new ParameterizedTypeReference<List<BookDto>>() { }); List<BookDto> result = responseEntity.getBody(); System.out.println(result); }
|
DELETE、PUT、OPTION 请求
DELETE 请求
1 2 3
| public void delete(String url, Object... uriVariables); public void delete(String url, Map<String, ?> uriVariables); public void delete(URI url);
|
PUT 请求
PUT 请求和 POST 请求类似,将类型改为 PUT 就可以了。
OPTIONS 请求
OPTIONS 请求用来探测接口支持哪些 http 方法
1 2 3
| public Set<HttpMethod> optionsForAllow(String url, Object... uriVariables); public Set<HttpMethod> optionsForAllow(String url, Map<String, ?> uriVariables); public Set<HttpMethod> optionsForAllow(URI url);
|
集成 HttpClient
RestTemplate 内部默认用的是 jdk 自带的 HttpURLConnection 发送请求的,性能上面并不是太突出。
可以将其替换为 httpclient 或者 okhttp。
先来看下如何替换为 HttpClient。
1 2 3 4 5
| <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.7</version> </dependency>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| public HttpClient httpClient() { HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); try { SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (arg0, arg1) -> true).build(); httpClientBuilder.setSSLContext(sslContext); HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE; SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier); Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", sslConnectionSocketFactory).build();
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); poolingHttpClientConnectionManager.setMaxTotal(1000); poolingHttpClientConnectionManager.setDefaultMaxPerRoute(100); httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager); httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(0, true)); List<Header> headers = new ArrayList<>(); httpClientBuilder.setDefaultHeaders(headers); return httpClientBuilder.build(); } catch (Exception e) { throw new RuntimeException(e); } }
public ClientHttpRequestFactory clientHttpRequestFactory() { HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient()); clientHttpRequestFactory.setConnectTimeout(10 * 1000); clientHttpRequestFactory.setReadTimeout(60 * 1000); clientHttpRequestFactory.setConnectionRequestTimeout(10 * 1000); return clientHttpRequestFactory; }
public RestTemplate restTemplate(){ return new RestTemplate(this.clientHttpRequestFactory()); }
@Test public void test18() { RestTemplate restTemplate = this.restTemplate(); String url = "http://localhost:8080/chat16/test/get"; BookDto bookDto = restTemplate.getForObject(url, BookDto.class); System.out.println(bookDto); }
|
集成 okhttp
1 2 3 4 5
| <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.3.1</version> </dependency>
|
创建 RestTemplate
1
| new RestTemplate(new OkHttp3ClientHttpRequestFactory());
|