1,引言
现如今的 IT 项目,由服务端向外发起网络请求的场景,基本上处处可见!
传统情况下,在服务端代码里访问 http 服务时,一般会使用 JDK 的 HttpURLConnection 或者 Apache 的 HttpClient,不过这种方法使用起来太过繁琐,而且 api 使用起来非常的复杂,还得操心资源回收。
RestTemplate是一个执行HTTP请求的同步阻塞式工具类,它仅仅只是在 HTTP 客户端库(例如 JDK HttpURLConnection,Apache HttpComponents,okHttp 等)基础上,封装了更加简单易用的模板方法 API,方便程序员利用已提供的模板方法发起网络请求和处理,能很大程度上提升我们的开发效率
2,环境配置
1,Spring 环境下
RestTemplate 在 spring-web 包中,需要引入
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
2,SpringBoot 环境下
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
从 spring 某个版本开始,默认不生成 RestTemplate bean,需要我们自己注入
@Configuration
public class RestTemplateConfig {
/**
* 没有实例化RestTemplate时,初始化RestTemplate
* @return
*/
@ConditionalOnMissingBean(RestTemplate.class)
@Bean
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate();
return restTemplate;
}
}
3,RestTemplate 底层实现
1. 源码分析
RestTemplate 有一个非常重要的基类叫做 HttpAccessor,可以理解为用于HTTP接触访问的基础类。
总结:
-
RestTemplate 支持至少三种HTTP客户端库,也就是 ClientHttpRequestFactory 的子类。
- SimpleClientHttpRequestFactory。对应的HTTP库是java JDK自带的HttpURLConnection。
- HttpComponentsAsyncClientHttpRequestFactory。对应的HTTP库是Apache HttpComponents。
- OkHttp3ClientHttpRequestFactory。对应的HTTP库是OkHttp
-
java JDK自带的HttpURLConnection是默认的底层HTTP实现客户端
-
SimpleClientHttpRequestFactory,即java JDK自带的HttpURLConnection不支持HTTP协议的Patch方法,如果希望使用Patch方法,需要将底层HTTP客户端实现切换为Apache HttpComponents 或 OkHttp
-
可以通过设置 setRequestFactory 方法,来切换 RestTemplate 的底层HTTP客户端实现类库。
2. 切换底层实现方法
1. 切换为 OKHttp
<!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.12.0</version>
</dependency>
@Bean
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
return restTemplate;
}
private ClientHttpRequestFactory getClientHttpRequestFactory(){
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.build();
return new OkHttp3ClientHttpRequestFactory(okHttpClient);
}
要注意一下版本。
2. 切换为 Apache HttpClient
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.10</version>
</dependency>
@Bean
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
return restTemplate;
}
/**
* 使用 appach 的 HttpClient 作为底层客户端
* @return
*/
private ClientHttpRequestFactory getClientHttpRequestFactory() {
int timeout = 1_000;
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.setSocketTimeout(timeout)
.build();
CloseableHttpClient client = HttpClientBuilder
.create()
.setDefaultRequestConfig(config)
.build();
return new HttpComponentsClientHttpRequestFactory(client);
}
据说 OkHttp > Apache HttpComponents > HttpURLConnection
4. 拦截器设置
1. 定义一个拦截器
public class RestTemplateInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
System.out.println("拦截器!!!");
ClientHttpResponse response = execution.execute(request, body);
return response;
}
}
2. 给 restTemplate 设置拦截器
@Bean
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
restTemplate.setInterceptors(Collections.singletonList(new RestTemplateInterceptor()));
return restTemplate;
}
5. RestTemplate Api
1. get 请求
-
getForObject():返回值是HTTP协议的响应体内容
-
getForEntity():返回的是ResponseEntity,ResponseEntity是对HTTP响应的封装,除了包含响应体,还包含HTTP状态码、contentType、contentLength、Header等信息
getForObject 方法
<T> T getForObject(URI url, Class<T> responseType) throws RestClientException;
/** url 请求路径,responseType 返回类型,uriVariables URL 上参数 */
<T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;
/** url 请求路径,responseType 返回类型,uriVariables 请求参数*/
<T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
/**
* 无参
* */
Dog dog = restTemplate.getForObject(url + "/get", Dog.class);
System.out.println(dog);
/**
* path 参数
* */
Dog dog1 = restTemplate.getForObject(url + "/get/path/{code}", Dog.class, "不太聪明");
System.out.println(dog1);
/**
* restful 参数
* */
Map<String, Object> map = new HashMap<>();
map.put("name", "张飞");
map.put("color", "green");
Dog dog2 = restTemplate.getForObject(url + "/get/restful?name={name}&color={color}", Dog.class, map);
System.out.println(dog2);
getForEntity 方法
//无参
<T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException;
//路径参数
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;
//restful 参数
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
/**
* 无参
* */
ResponseEntity<Dog> res = restTemplate.getForEntity(url + "/get", Dog.class);
System.out.println( res.getStatusCode());
System.out.println( res.getBody() );
/**
* path 参数
* */
ResponseEntity<Dog> res1 = restTemplate.getForEntity(url + "/get/path/{code}", Dog.class, "不太聪明");
System.out.println( res1.getStatusCode());
System.out.println( res1.getBody() );
/**
* restful 参数
* */
Map<String, Object> map = new HashMap<>();
map.put("name", "张飞");
map.put("color", "green");
ResponseEntity<Dog> res2 = restTemplate.getForEntity(url + "/get/restful?name={name}&color={color}", Dog.class, map);
System.out.println( res2.getStatusCode());
System.out.println( res2.getBody() );
2. post 请求
- postForObject():返回值是HTTP协议的响应体body内容
- postForEntity():返回的是ResponseEntity,ResponseEntity是对HTTP响应的封装,除了包含响应体,还包含HTTP状态码、contentType、contentLength、Header等信息
- postForLocation:使用POST创建一个新资源,返回UTI对象,可以从响应中返回Location报头。
postForObject 方法
/** url 请求路径,request 请求体,responseType 返回类型 */
<T> T postForObject(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException;
/** uriVariables 路劲里的参数 */
<T> T postForObject(String url, @Nullable Object request, Class<T> responseType,
Object... uriVariables) throws RestClientException;
/** uriVariables 请求参数 */
<T> T postForObject(String url, @Nullable Object request, Class<T> responseType,
Map<String, ?> uriVariables) throws RestClientException;
/**
* 无参
* */
Dog dog = restTemplate.postForObject(url + "/api", null, Dog.class);
System.out.println(dog);
/**
* path 参数
* */
Dog dog1 = restTemplate.postForObject(url + "/api/path/{code}", null, Dog.class, "不太聪明");
System.out.println(dog1);
/**
* 表单 参数 表单参数(专用map)
* */
MultiValueMap<String,String> map = new LinkedMultiValueMap<>();
map.add("name", "张飞");
map.add("color", "green");
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String,String>> requestEntity = new HttpEntity<>(map,httpHeaders);
Dog dog2 = restTemplate.postForObject(url + "/api/restful", requestEntity, Dog.class);
System.out.println(dog2);
/**
* json
* */
Dog d = new Dog("李白", "得瑟");
Dog dog3 = restTemplate.postForObject(url + "/api/json", d, Dog.class);
System.out.println(dog3);
postForEntity 方法
/** url 请求路径,request 请求体,responseType 返回类型 */
<T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType)
throws RestClientException;
/** uriVariables 路劲里的参数 */
<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType,
Object... uriVariables) throws RestClientException;
/** uriVariables 请求参数 */
<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType,
Map<String, ?> uriVariables) throws RestClientException;
/**
* 无参
* */
ResponseEntity<Dog> entity = restTemplate.postForEntity(url + "/api", null, Dog.class);
System.out.println(entity.getBody());
/**
* path 参数
* */
ResponseEntity<Dog> entity1 = restTemplate.postForEntity(url + "/api/path/{code}", null, Dog.class, "不太聪明");
System.out.println(entity1.getBody());
/**
* 表单 参数
* */
//表单参数(专用map)
MultiValueMap<String,String> map = new LinkedMultiValueMap<String,String>();
map.add("name", "张飞");
map.add("color", "green");
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String,String>> requestEntity = new HttpEntity<>(map,httpHeaders);
ResponseEntity<Dog> entity2 = restTemplate.postForEntity(url + "/api/restful", requestEntity, Dog.class);
System.out.println(entity2.getBody());
/**
* json
* */
Dog d = new Dog("李白", "得瑟");
ResponseEntity<Dog> entity3 = restTemplate.postForEntity(url + "/api/json", d, Dog.class);
System.out.println(entity3.getBody());
/**
* json 封装请求体
* */
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
ResponseEntity<Dog> entity4 = restTemplate.postForEntity(url + "/api/json", new HttpEntity<>(d,headers), Dog.class);
System.out.println(entity4.getBody());
3. exchange 请求
exchange 通用请求类型方法,可以自己组装各种各样的请求。
/**
* url 请求地址
* method 请求方式,get、post 等
* requestEntity 请求体
* responseType 返回类型
* uriVariables 路径中的参数
*/
<T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity,
Class<T> responseType, Object... uriVariables) throws RestClientException;
/**
* uriVariables 请求参数
*/
<T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity,
Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
<T> ResponseEntity<T> exchange(URI url, HttpMethod method, @Nullable HttpEntity<?> requestEntity,
Class<T> responseType) throws RestClientException;
<T> ResponseEntity<T> exchange(String url,HttpMethod method, @Nullable HttpEntity<?> requestEntity,
ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException;
/**
* responseType 返回 List<T> 这种类型的时候,要用这个, Class<T> 表示不了
*/
<T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity,
ParameterizedTypeReference<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
<T> ResponseEntity<T> exchange(URI url, HttpMethod method, @Nullable HttpEntity<?> requestEntity,
ParameterizedTypeReference<T> responseType) throws RestClientException;
<T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, Class<T> responseType)
throws RestClientException;
<T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, ParameterizedTypeReference<T> responseType)
throws RestClientException;
//无参
ResponseEntity<Dog> e1 = restTemplate.exchange(url + "/api", HttpMethod.GET, null, Dog.class);
System.out.println( e1.getBody() );
//path 参数
ResponseEntity<Dog> e2 = restTemplate.exchange(url + "/api/path/{code}", HttpMethod.GET, null, Dog.class, "啥子");
System.out.println( e2.getBody() );
//表单参数
MultiValueMap<String,String> map = new LinkedMultiValueMap<String,String>();
map.add("name", "张飞");
map.add("color", "green");
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String,String>> requestEntity = new HttpEntity<>(map,httpHeaders);
ResponseEntity<Dog> e3 = restTemplate.exchange(url + "/api/restful", HttpMethod.POST, requestEntity, Dog.class);
System.out.println(e3.getBody());
//json 参数
Dog d1 = new Dog("尸鬼", "李贺");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Dog> dogHttpEntity = new HttpEntity<>(d1, headers);
ResponseEntity<Dog> e4 = restTemplate.exchange(url + "/api/json", HttpMethod.POST, dogHttpEntity, Dog.class);
System.out.println(e4.getBody());
//返回结果是 list<T> 这种格式
ResponseEntity<List<Dog>> e5 = restTemplate.exchange(url + "/api/list", HttpMethod.GET, null, new ParameterizedTypeReference<List<Dog>>() {
@Override
public Type getType() {
return super.getType();
}
});
System.out.println( e5.getBody() );
4. 文件上传下载
@RequestMapping("/file/upload")
public void fileDemo(){
// 请求头设置,multipart/form-data格式的数据
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
//提交参数
String path = "C:\\Users\\admin\\Desktop\\local-file\\jsStream.js";
MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
param.add("file", new FileSystemResource(new File(path)));
param.add("name", "小三子");
// 组装请求体
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(param, headers);
ResponseEntity<Dog> exchange = restTemplate.postForEntity("http://localhost:8080/file/upload", request, null);
System.out.println(exchange.getStatusCode());
}
/**
* 下载
* */
@RequestMapping("/file/download")
public void fileDemo1() throws IOException {
//发起请求(响应内容为字节文件)
ResponseEntity<byte[]> response = restTemplate.getForEntity("http://localhost:8080/file/download", byte[].class);
System.out.println( response.getStatusCode() );
Files.write(Paths.get( "C:\\Users\\admin\\Desktop\\local-file\\dd"), response.getBody() );
}
上述文件下载的方式,是一次性将响应内容加载到客户端内存中,然后再从内存中将文件写入到磁盘中,这种方式适用于一些小文件的下载。如果遇到大的文件,可能会爆内存。之后我们使用 execute 方法处理。
5. execute 方法
其内部调用的 doExecute 方法,是几乎所有方法最终的走向
/** requestCallback 对请求的处理,responseExtractor 对响应的处理 */
<T> T execute(URI url, HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException;
<T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor, Map<String, ?> uriVariables)
throws RestClientException;
<T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables)
throws RestClientException;
doExecute 方法源码:
@Nullable
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
Assert.notNull(url, "URI is required");
Assert.notNull(method, "HttpMethod is required");
ClientHttpResponse response = null;
Object var14;
try {
//1.生成请求
ClientHttpRequest request = this.createRequest(url, method);
if (requestCallback != null) {
//2.设置header
requestCallback.doWithRequest(request);
}
//3.执行请求
response = request.execute();
//4.处理响应
this.handleResponse(url, method, response);
//5.返回执行结果
var14 = responseExtractor != null ? responseExtractor.extractData(response) : null;
} catch (IOException var12) {
String resource = url.toString();
String query = url.getRawQuery();
resource = query != null ? resource.substring(0, resource.indexOf(63)) : resource;
throw new ResourceAccessException("I/O error on " + method.name() + " request for \"" + resource + "\": " + var12.getMessage(), var12);
} finally {
if (response != null) {
response.close();
}
}
return var14;
}
此方法并没有对外暴露,只能通过继承调用。可以看到我们每次请求的时候内部会发生这几个步骤:
- 生成请求
- 设置header
- 执行请求
- 处理响应
- 返回执行结果
RequestCallback 接口 官方介绍
RequestCallback用于操作ClientHttpRequest的代码的回调接口。允许操作请求头,并写入请求体。
在RestTemplate内部使用,但对应用程序代码也很有用。有以下两个实现类:
- AcceptHeaderRequestCallback:只处理请求头回调,用于restTemplate.getXXX()方法;
- HttpEntityRequestCallback:继承AcceptHeaderRequestCallback,可以处理请求头和body,用于restTemplate.putXXX()、restTemplate.postXXX()和restTemplate.exchange()等方法。
ResponseExtractor 接口
响应提取器,主要就是用来从Response中提取数据。RestTemplate请求完成后,都是通过它来从ClientHttpResponse提取出指定内容,比如:请求头、请求body等。
ResponseExtractor有三个实现类:
- HeadersExtractor:用于提取请求头
- HttpMessageConverterExtractor:用于提取响应body体内容
- ResponseEntityResponseExtractor:内部借助HttpMessageConverterExtractor提取body体(委托模式),然后将body和响应头、状态封装成ResponseEntity对象返回调用者。
下载大文件
/**
* 下载
* */
@RequestMapping("/file/download")
public void fileDemo1() throws Exception {
//请求地址
String url = "http://localhost:8080/file/download";
//定义请求头的接收类型
RequestCallback requestCallback = clientHttpRequest
-> clientHttpRequest.getHeaders()
.setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM,MediaType.ALL));
//下载文件保存路径
File savePath = new File("C:\\Users\\admin\\Desktop\\local-file\\");
if (!savePath.isDirectory()){
savePath.mkdirs();
}
Path path = Paths.get(savePath.getPath() + File.separator + "ssss.zip");
try {
//对响应结果进行流式处理,而不是一次性将响应结果全部加载到内存
restTemplate.execute(url, HttpMethod.GET, requestCallback,clientHttpResponse
-> {
//每响应一次就写入磁盘一次,不在内存中逗留
Files.copy(Objects.requireNonNull(clientHttpResponse.getBody(),"下载文件失败!"), path);
return null;
});
}catch (Exception e){
throw new Exception("文件下载异常或已经存在!");
}
}
参考文献
https://zhuanlan.zhihu.com/p/166214440
https://blog.csdn.net/qq_52596258/article/details/127604909