【SpringBoot WEB 系列】AsyncRestTemplate 之异步非阻塞网络请求介绍篇
AsyncRestTemplate 发起异步网络请求,由 Spring4.0 引入,但是在 5.0 就被表上了删除注解,官方推荐使用基于 React 的 WebClient 来代替。
虽然官方已经不推荐使用AsyncRestTemplate
,但是如果你的 web 项目,并不想引入 react 相关的包,使用AsyncRestTemplate
来实现异步网络请求也不失为一个选择,本文将主要介绍它的基本使用姿势
I. 项目环境
本文创建的实例工程采用SpringBoot 2.2.1.RELEASE
+ maven 3.5.3
+ idea
进行开发
1. pom 依赖
具体的 SpringBoot 项目工程创建就不赘述了,对于 pom 文件中,需要重点关注下面两个依赖类
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
请注意我们并没有引入 react 相关的包,所以是没法直接使用 webclient 的
简单的配置一下 logback 的日志输出(非必要条件), application.yml
文件内容如下
logging:
pattern:
console: (%msg%n%n){blue}
logging:
pattern:
console: (%msg%n%n){blue}
2. 测试接口
编写几个简单的用于测试的 REST 接口
@GetMapping(path = "atimeout")
public String aTimeOut(HttpServletRequest request) throws InterruptedException {
Thread.sleep(3_000L);
return "time out!" + JSON.toJSONString(request.getParameterMap());
}
@GetMapping(path = "4xx")
public String _4xx(HttpServletRequest request, HttpServletResponse response) {
response.setStatus(401);
return "return 401 : " + JSON.toJSONString(request.getParameterMap());
}
@GetMapping(path = "atimeout")
public String aTimeOut(HttpServletRequest request) throws InterruptedException {
Thread.sleep(3_000L);
return "time out!" + JSON.toJSONString(request.getParameterMap());
}
@GetMapping(path = "4xx")
public String _4xx(HttpServletRequest request, HttpServletResponse response) {
response.setStatus(401);
return "return 401 : " + JSON.toJSONString(request.getParameterMap());
}
II. 使用说明
从接口声明上来看,AsyncRestTemplate 与 RestTemplate 的使用姿势没有什么区别,如典型的 GET/POST 接口声明如下
// GET
@Override
public ListenableFuture> getForEntity(String url, Class responseType, Object... uriVariables) throws RestClientException@Overridepublic ListenableFuture> getForEntity(String url, Class responseType,
Map uriVariables) throws RestClientException@Overridepublic ListenableFuture> getForEntity(URI url, Class responseType)throws RestClientException//POST@Overridepublic ListenableFuture> postForEntity(String url, @Nullable HttpEntity> request,
Class responseType, Object... uriVariables) throws RestClientException@Overridepublic ListenableFuture> postForEntity(String url, @Nullable HttpEntity> request,
Class responseType, Map uriVariables) throws RestClientException@Overridepublic ListenableFuture> postForEntity(URI url,@Nullable HttpEntity> request, Class responseType) throws RestClientException
// GET
@Override
public ListenableFuture> getForEntity(String url, Class responseType, Object... uriVariables) throws RestClientException@Overridepublic ListenableFuture> getForEntity(String url, Class responseType,
Map uriVariables) throws RestClientException@Overridepublic ListenableFuture> getForEntity(URI url, Class responseType)throws RestClientException//POST@Overridepublic ListenableFuture> postForEntity(String url, @Nullable HttpEntity> request,
Class responseType, Object... uriVariables) throws RestClientException@Overridepublic ListenableFuture> postForEntity(String url, @Nullable HttpEntity> request,
Class responseType, Map uriVariables) throws RestClientException@Overridepublic ListenableFuture> postForEntity(URI url,@Nullable HttpEntity> request, Class responseType) throws RestClientException
1. 使用姿势
GET/POST 的访问姿势就不再赘述,有兴趣的小伙伴可以查看 RestTemplate 的使用博文:【WEB 系列】RestTemplate 基础用法小结
注意到不同的点在于返回的对象,RestTemplate
是直接返回实体;而AsyncRestTemplate
返回的则是ListenerableFuture
包装的结果,这个类属于 Spring 自定义对象,继承自 Future 体系,而 Future 是我们并发编程中用于获取异步结果的一个接口
ListenerableFuture
的最大特点在于它可以绑定执行完成的监听器,就不需要通过 get 来阻塞获取结果了,一个简单的使用姿势如下, 分别演示正常返回,异常返回的回调 case(两者都不会阻塞主线程的执行哦)
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate();
long start = System.currentTimeMillis();
ListenableFuture> response =
asyncRestTemplate.getForEntity("http://127.0.0.1:8080/atimeout?name=一灰灰&age=20", String.class);
response.addCallback(new ListenableFutureCallback>() {@Overridepublic void onFailure(Throwable throwable) {
log.info("1. Async get error! cost: {}, e: {}", System.currentTimeMillis() - start, throwable);
}@Overridepublic void onSuccess(ResponseEntity stringResponseEntity) {
String ans = stringResponseEntity.getBody();
log.info("1. success get: {}, cost: {}", ans, System.currentTimeMillis() - start);
}
});
response = asyncRestTemplate.getForEntity("http://127.0.0.1:8080/4xx?name=一灰灰&age=20", String.class);
response.addCallback(new ListenableFutureCallback>() {@Overridepublic void onFailure(Throwable ex) {
log.info("2. Async get error! cost: {}, e: {}", System.currentTimeMillis() - start, ex);
}@Overridepublic void onSuccess(ResponseEntity result) {
log.info("2. success get: {}, cost: {}", result, System.currentTimeMillis() - start);
}
});
log.info("do something else!!!");
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate();
long start = System.currentTimeMillis();
ListenableFuture> response =
asyncRestTemplate.getForEntity("http://127.0.0.1:8080/atimeout?name=一灰灰&age=20", String.class);
response.addCallback(new ListenableFutureCallback>() {@Overridepublic void onFailure(Throwable throwable) {
log.info("1. Async get error! cost: {}, e: {}", System.currentTimeMillis() - start, throwable);
}@Overridepublic void onSuccess(ResponseEntity stringResponseEntity) {
String ans = stringResponseEntity.getBody();
log.info("1. success get: {}, cost: {}", ans, System.currentTimeMillis() - start);
}
});
response = asyncRestTemplate.getForEntity("http://127.0.0.1:8080/4xx?name=一灰灰&age=20", String.class);
response.addCallback(new ListenableFutureCallback>() {@Overridepublic void onFailure(Throwable ex) {
log.info("2. Async get error! cost: {}, e: {}", System.currentTimeMillis() - start, ex);
}@Overridepublic void onSuccess(ResponseEntity result) {
log.info("2. success get: {}, cost: {}", result, System.currentTimeMillis() - start);
}
});
log.info("do something else!!!");
请注意下面的动图,主线程的do something else!!!
文案会优先输出,并不会被阻塞;然后就是返回结果之后的回调,因为第一个 case 访问的 rest 服务有个 sleep,所以输出也会有一个明显的滞后
2. Guava 方式的异步请求
除了上面说到的 AsyncRestTemplate 来实现异步访问,我们也可以借助 Gauva 配合RestTemplate
来实现类似的效果,下面作为扩展知识点,给出一个等效的使用说明
public void guava() {
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 基于jdk线程池,创建支持异步回调的线程池
ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(executorService);
long start = System.currentTimeMillis();
// 具体的异步访问任务
com.google.common.util.concurrent.ListenableFuture> ans =
listeningExecutorService.submit(new Callable>() {@Overridepublic HttpEntity call() throws Exception {
RestTemplate restTemplate = new RestTemplate();return restTemplate
.getForEntity("http://127.0.0.1:8080/atimeout?name=一灰灰&age=19", String.class);
}
});// 完成之后,在指定的线程池(第三个参数)中回调
Futures.addCallback(ans, new com.google.common.util.concurrent.FutureCallback>() {@Overridepublic void onSuccess(@Nullable HttpEntity stringHttpEntity) {
log.info("guava call back res: {}, cost: {}", stringHttpEntity.getBody(),
System.currentTimeMillis() - start);
}@Overridepublic void onFailure(Throwable throwable) {
log.info("guava call back failed cost:{}, e: {}", System.currentTimeMillis() - start, throwable);
}
}, Executors.newFixedThreadPool(1));
log.info("do something other in guava!");
listeningExecutorService.shutdown();
}
public void guava() {
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 基于jdk线程池,创建支持异步回调的线程池
ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(executorService);
long start = System.currentTimeMillis();
// 具体的异步访问任务
com.google.common.util.concurrent.ListenableFuture> ans =
listeningExecutorService.submit(new Callable>() {@Overridepublic HttpEntity call() throws Exception {
RestTemplate restTemplate = new RestTemplate();return restTemplate
.getForEntity("http://127.0.0.1:8080/atimeout?name=一灰灰&age=19", String.class);
}
});// 完成之后,在指定的线程池(第三个参数)中回调
Futures.addCallback(ans, new com.google.common.util.concurrent.FutureCallback>() {@Overridepublic void onSuccess(@Nullable HttpEntity stringHttpEntity) {
log.info("guava call back res: {}, cost: {}", stringHttpEntity.getBody(),
System.currentTimeMillis() - start);
}@Overridepublic void onFailure(Throwable throwable) {
log.info("guava call back failed cost:{}, e: {}", System.currentTimeMillis() - start, throwable);
}
}, Executors.newFixedThreadPool(1));
log.info("do something other in guava!");
listeningExecutorService.shutdown();
}
看到这里自然而然会有一个疑问,异步任务完成的回调,是怎么实现呢?
欢迎各位小伙伴评论给出看法
II. 其他
0. 项目&系列博文
博文
- 【WEB 系列】RestTemplate 之非 200 状态码信息捕获
- 【WEB 系列】RestTemplate 之 Basic Auth 授权
- 【WEB 系列】RestTemplate 之代理访问
- 【WEB 系列】RestTemplate 之超时设置
- 【WEB 系列】RestTemplate 之中文乱码问题 fix
- 【WEB 系列】RestTemplate 之自定义请求头
- 【WEB 系列】RestTemplate 基础用法小结
源码
- 工程:https://github.com/liuyueyi/spring-boot-demo
- 源码: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/221-web-resttemplate