利用Spring的@Async异步处理改善web应用中耗时操作的用户体验

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: Web应用中,有时会遇到一些耗时很长的操作(比如:在后台生成100张报表再呈现,或 从ftp下载若干文件,综合处理后再返回给页面下载),用户在网页上点完按钮后,通常会遇到二个问题:页面超时、看不到处理进度。

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>
View Code

二、后台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 }
View Code

服务实现:

 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 }
View Code

注意: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 Code

四、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>
View Code

浏览 http://localhost:8080/xxx/async/123123后的效果

 

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
22天前
|
XML Java 数据库
探索 Spring Boot 中的 @Configuration 注解:核心概念与应用
【4月更文挑战第20天】在 Spring Boot 项目中,@Configuration 注解扮演了一个关键角色,它标识一个类作为配置源,这些配置用于定义和管理 Spring 应用程序中的 Bean
42 7
|
2天前
|
安全 Java 开发者
深入理解Spring Boot配置绑定及其实战应用
【4月更文挑战第10天】本文详细探讨了Spring Boot中配置绑定的核心概念,并结合实战示例,展示了如何在项目中有效地使用这些技术来管理和绑定配置属性。
10 1
|
1天前
|
Java 应用服务中间件 测试技术
深入探索Spring Boot Web应用源码及实战应用
【5月更文挑战第11天】本文将详细解析Spring Boot Web应用的源码架构,并通过一个实际案例,展示如何构建一个基于Spring Boot的Web应用。本文旨在帮助读者更好地理解Spring Boot的内部工作机制,以及如何利用这些机制优化自己的Web应用开发。
8 2
|
3天前
|
Java Spring 容器
深入理解Spring Boot启动流程及其实战应用
【5月更文挑战第9天】本文详细解析了Spring Boot启动流程的概念和关键步骤,并结合实战示例,展示了如何在实际开发中运用这些知识。
13 2
|
5天前
|
JavaScript Java 开发者
Spring Boot中的@Lazy注解:概念及实战应用
【4月更文挑战第7天】在Spring Framework中,@Lazy注解是一个非常有用的特性,它允许开发者控制Spring容器的bean初始化时机。本文将详细介绍@Lazy注解的概念,并通过一个实际的例子展示如何在Spring Boot应用中使用它。
17 2
|
10天前
|
XML 前端开发 Oracle
16:JSP简介、注释与Scriptlet、Page指令元素、Include操作、内置对象、四种属性-Java Web
16:JSP简介、注释与Scriptlet、Page指令元素、Include操作、内置对象、四种属性-Java Web
13 2
|
12天前
|
SQL Java 数据库连接
Springboot框架整合Spring JDBC操作数据
JDBC是Java数据库连接API,用于执行SQL并访问多种关系数据库。它包括一系列Java类和接口,用于建立数据库连接、创建数据库操作对象、定义SQL语句、执行操作并处理结果集。直接使用JDBC涉及七个步骤,包括加载驱动、建立连接、创建对象、定义SQL、执行操作、处理结果和关闭资源。Spring Boot的`spring-boot-starter-jdbc`简化了这些步骤,提供了一个在Spring生态中更便捷使用JDBC的封装。集成Spring JDBC需要添加相关依赖,配置数据库连接信息,并通过JdbcTemplate进行数据库操作,如插入、更新、删除和查询。
|
12天前
|
SQL Java 数据库连接
Springboot框架整合Spring Data JPA操作数据
Spring Data JPA是Spring基于ORM和JPA规范封装的框架,简化了数据库操作,提供增删改查等接口,并可通过方法名自动生成查询。集成到Spring Boot需添加相关依赖并配置数据库连接和JPA设置。基础用法包括定义实体类和Repository接口,通过Repository接口可直接进行数据操作。此外,JPA支持关键字查询,如通过`findByAuthor`自动转换为SQL的`WHERE author=?`查询。
|
12天前
|
XML Java 数据格式
如何在Spring AOP中定义和应用通知?
【4月更文挑战第30天】如何在Spring AOP中定义和应用通知?
17 0
|
13天前
|
JavaScript 前端开发 开发者
【Web 前端】如何操作DOM元素?
【4月更文挑战第22天】【Web 前端】如何操作DOM元素?