利用Spring的@Async异步处理改善web应用中耗时操作的用户体验
时间:2022-04-23
本文章向大家介绍利用Spring的@Async异步处理改善web应用中耗时操作的用户体验,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
Web应用中,有时会遇到一些耗时很长的操作(比如:在后台生成100张报表再呈现,或 从ftp下载若干文件,综合处理后再返回给页面下载),用户在网页上点完按钮后,通常会遇到二个问题:页面超时、看不到处理进度。
对于超时,采用异步操作,可以很好的解决这个问题,后台服务收到请求后,执行异步方法不会阻塞线程,因此就不存在超时问题。但是异步处理的进度用户也需要知道,否则不知道后台的异步处理何时完成,用户无法决定接下来应该继续等候? or 关掉页面?
思路:
1、browser -> Spring-MVC Controller -> call 后台服务中的异步方法 -> 将执行进度更新到redis缓存 -> 返回view
2、返回的view页面上,ajax -> 轮询 call 后台服务 -> 查询redis中的进度缓存数据,并实时更新UI进度显示 -> 如果完成 call 后台服务清理缓存
注:这里采用了redis保存异步处理的执行进度,也可以换成session或cookie来保存。
步骤:
一、spring配置文件中,增加Task支持
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:task="http://www.springframework.org/schema/task"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans
6 http://www.springframework.org/schema/beans/spring-beans.xsd
7 http://www.springframework.org/schema/task
8 http://www.springframework.org/schema/task/spring-task.xsd">
9
10
11
12 <!-- 支持异步方法执行 -->
13 <task:annotation-driven/>
14
15 </beans>
二、后台Service中,在方法前加上@Async
先定义服务接口:
1 package ctas.web.service;
2
3 public interface AsyncService {
4
5 /**
6 * 异步执行耗时较长的操作
7 *
8 * @param cacheKey
9 * @throws Exception
10 */
11 void asyncMethod(String cacheKey) throws Exception;
12
13 /**
14 * 获取执行进度
15 *
16 * @param cacheKey
17 * @return
18 * @throws Exception
19 */
20 String getProcess(String cacheKey) throws Exception;
21
22 /**
23 * 执行完成后,清除缓存
24 *
25 * @param cacheKey
26 * @throws Exception
27 */
28 void clearCache(String cacheKey) throws Exception;
29 }
服务实现:
1 package ctas.web.service.impl;
2 import ctas.web.service.AsyncService;
3 import org.springframework.beans.factory.annotation.Autowired;
4 import org.springframework.data.redis.core.StringRedisTemplate;
5 import org.springframework.scheduling.annotation.Async;
6 import org.springframework.stereotype.Service;
7
8 @Service("asyncService")
9 public class AsyncServiceImpl extends BaseServiceImpl implements AsyncService {
10
11 @Autowired
12 StringRedisTemplate stringRedisTemplate;
13
14
15 @Override
16 @Async
17 public void asyncMethod(String cacheKey) throws Exception {
18 //模拟总有20个步骤,每个步骤耗时2秒
19 int maxStep = 20;
20 for (int i = 0; i < maxStep; i++) {
21 Thread.sleep(2000);
22 //将执行进度放入缓存
23 stringRedisTemplate.opsForValue().set(cacheKey, (i + 1) + "/" + maxStep);
24 }
25 }
26
27 @Override
28 public String getProcess(String cacheKey) throws Exception {
29 return stringRedisTemplate.opsForValue().get(cacheKey);
30 }
31
32 @Override
33 public void clearCache(String cacheKey) throws Exception {
34 //完成后,清空缓存
35 stringRedisTemplate.delete(cacheKey);
36 }
37
38
39 }
注意:asyncMethod方法前面的@Async注解,这里模拟了一个耗时的操作,并假设要完成该操作,共需要20个小步骤,每执行完一个步骤,将进度更新到redis缓存中。
三、Controller的处理
1 @RequestMapping(value = "async/{key}")
2 public String asyncTest(HttpServletRequest req,
3 HttpServletResponse resp, @PathVariable String key) throws Exception {
4 asyncService.asyncMethod(key);
5 return "common/async";
6 }
7
8 @RequestMapping(value = "async/{key}/status")
9 public String showAsyncStatus(HttpServletRequest req,
10 HttpServletResponse resp, @PathVariable String key) throws Exception {
11 String status = asyncService.getProcess(key);
12 ResponseUtil.OutputJson(resp, "{"status":"" + status + ""}");
13 return null;
14 }
15
16 @RequestMapping(value = "async/{key}/clear")
17 public String clearAsyncStatus(HttpServletRequest req,
18 HttpServletResponse resp, @PathVariable String key) throws Exception {
19 asyncService.clearCache(key);
20 ResponseUtil.OutputJson(resp, "{"status":"ok"}");
21 return null;
22 }
四、view上的ajax处理
1 <script type="text/javascript" language="JavaScript">
2
3 var timerId = null;//定时器ID
4
5 $(document).ready(function () {
6
7 /*
8 定时轮询执行进度
9 */
10 timerId = setInterval(function () {
11 getStatus();
12 }, 1000);
13 getStatus();
14 });
15
16 /**
17 获取执行进度
18 */
19 function getStatus() {
20 var statusUrl = window.location.href + "/status";
21 $.get(statusUrl, function (data) {
22 if (data == null || data.status == null || data.status == "null") {
23 updateStatus("准备中");
24 return;
25 }
26 var status = data.status;
27 updateStatus(status);
28 var temp = status.split("/");
29 if (temp[0] == temp[1]) {
30 updateStatus("完成");
31 clearInterval(timerId);//停止定时器
32 clearStatus();//清理redis缓存
33 }
34 })
35 }
36
37 /**
38 * 执行完成后,清理缓存
39 */
40 function clearStatus() {
41 var clearStatusUrl = window.location.href + "/clear";
42 $.get(clearStatusUrl, function (data) {
43 //alert(data.status);
44 })
45 }
46
47 /**
48 更新进度显示
49 */
50 function updateStatus(msg) {
51 $("#status").html(msg);
52 }
53 </script>
54 <div id="msgBox">
55 <span>请稍候,服务器正在处理中...</span>
56
57 <h1>当前处理进度:<span style="color:red" id="status">准备中</span></h1>
58 </div>
浏览 http://localhost:8080/xxx/async/123123后的效果
- python接口自动化2-发送post请求
- TypeScript 动态创建类
- Java学习笔记【持续更新】
- 互联网协议入门(二)
- 设计模式六大原则(4):接口隔离原则
- 设计模式六大原则(3):依赖倒置原则
- 闲的无聊时候就手动写第一个漏洞扫描工具吧!
- 模拟退火算法从原理到实战【基础篇】
- python接口自动化3-自动发帖(session)
- 平面上给定n条线段,找出一个点,使这个点到这n条线段的距离和最小。
- python接口自动化4-绕过验证码登录(cookie)
- 洛谷P1313 计算系数【快速幂+dp】
- python接口自动化5-Json数据处理
- Numpy教程第1部分 - 阵列简介(常用基础操作总结)
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- XXE漏洞学习
- 红帽杯-恶臭的数据包
- CTF-摩斯电码解密
- Lampiao靶机渗透
- 如何绕过堡垒机远程登录
- Android | okhttp细枝篇
- valgrind测试报告分析
- 深度学习Pytorch检测实战 - Notes - 第5章 单阶多层检测器:SSD
- Java+selnium 智能等待,try catch方法智能定位需添加等待的元素
- Python中的命名空间和作用域(2)
- C/C++可以用正则表达式吗?
- typescript实战总结之实现一个互联网黑白墙
- 文件上传漏洞演示(一句话木马文件 + 蚁剑)
- [Bazel]构建Golang项目
- 2020--IDEA破解失败后无法打开(mac/win)【已解决】