利用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支持
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd"> <!-- 支持异步方法执行 -->
<task:annotation-driven/> </beans>
二、后台Service中,在方法前加上@Async
先定义服务接口:
package ctas.web.service;
public interface AsyncService {
/**
* 异步执行耗时较长的操作
*
* @param cacheKey
* @throws Exception
*/
void asyncMethod(String cacheKey) throws Exception;
/**
* 获取执行进度
*
* @param cacheKey
* @return
* @throws Exception
*/
String getProcess(String cacheKey) throws Exception;
/**
* 执行完成后,清除缓存
*
* @param cacheKey
* @throws Exception
*/
void clearCache(String cacheKey) throws Exception;
}
服务实现:
package ctas.web.service.impl;
import ctas.web.service.AsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; @Service("asyncService")
public class AsyncServiceImpl extends BaseServiceImpl implements AsyncService { @Autowired
StringRedisTemplate stringRedisTemplate; @Override
@Async
public void asyncMethod(String cacheKey) throws Exception {
//模拟总有20个步骤,每个步骤耗时2秒
int maxStep = 20;
for (int i = 0; i < maxStep; i++) {
Thread.sleep(2000);
//将执行进度放入缓存
stringRedisTemplate.opsForValue().set(cacheKey, (i + 1) + "/" + maxStep);
}
} @Override
public String getProcess(String cacheKey) throws Exception {
return stringRedisTemplate.opsForValue().get(cacheKey);
} @Override
public void clearCache(String cacheKey) throws Exception {
//完成后,清空缓存
stringRedisTemplate.delete(cacheKey);
} }
注意:asyncMethod方法前面的@Async注解,这里模拟了一个耗时的操作,并假设要完成该操作,共需要20个小步骤,每执行完一个步骤,将进度更新到redis缓存中。
三、Controller的处理
@RequestMapping(value = "async/{key}")
public String asyncTest(HttpServletRequest req,
HttpServletResponse resp, @PathVariable String key) throws Exception {
asyncService.asyncMethod(key);
return "common/async";
}
@RequestMapping(value = "async/{key}/status")
public String showAsyncStatus(HttpServletRequest req,
HttpServletResponse resp, @PathVariable String key) throws Exception {
String status = asyncService.getProcess(key);
ResponseUtil.OutputJson(resp, "{\"status\":\"" + status + "\"}");
return null;
}
@RequestMapping(value = "async/{key}/clear")
public String clearAsyncStatus(HttpServletRequest req,
HttpServletResponse resp, @PathVariable String key) throws Exception {
asyncService.clearCache(key);
ResponseUtil.OutputJson(resp, "{\"status\":\"ok\"}");
return null;
}
四、view上的ajax处理
<script type="text/javascript" language="JavaScript">
var timerId = null;//定时器ID
$(document).ready(function () {
/*
定时轮询执行进度
*/
timerId = setInterval(function () {
getStatus();
}, 1000);
getStatus();
});
/**
获取执行进度
*/
function getStatus() {
var statusUrl = window.location.href + "/status";
$.get(statusUrl, function (data) {
if (data == null || data.status == null || data.status == "null") {
updateStatus("准备中");
return;
}
var status = data.status;
updateStatus(status);
var temp = status.split("/");
if (temp[0] == temp[1]) {
updateStatus("完成");
clearInterval(timerId);//停止定时器
clearStatus();//清理redis缓存
}
})
}
/**
* 执行完成后,清理缓存
*/
function clearStatus() {
var clearStatusUrl = window.location.href + "/clear";
$.get(clearStatusUrl, function (data) {
//alert(data.status);
})
}
/**
更新进度显示
*/
function updateStatus(msg) {
$("#status").html(msg);
}
</script>
<div id="msgBox">
<span>请稍候,服务器正在处理中...</span>
<h1>当前处理进度:<span style="color:red" id="status">准备中</span></h1>
</div>
浏览 http://localhost:8080/xxx/async/123123后的效果

利用Spring的@Async异步处理改善web应用中耗时操作的用户体验的更多相关文章
- Spring Boot @Async 异步任务执行
1.任务执行和调度 Spring用TaskExecutor和TaskScheduler接口提供了异步执行和调度任务的抽象. Spring的TaskExecutor和java.util.concurre ...
- Spring学习(四)在Web项目中实例化IOC容器
1.前言 前面我们讲到Spring在普通JAVA项目中的一些使用.本文将介绍在普通的Web项目中如何实例化Spring IOC容器.按照一般的思路.如果在Web中实例化Ioc容器.这不得获取Conte ...
- 利用简洁的图片预加载组件提升h5移动页面的用户体验
在做h5移动页面,相信大家一定碰到过页面已经打开,但是里面的图片还未加载出来的情况,这种问题虽然不影响页面的功能,但是不利于用户体验.抛开网速的原因,解决这个问题有多方面的思路:最基本的,要从http ...
- Web服务中延时对QoE(体验质量)的影响
S. Egger等人在论文<WAITING TIMES IN QUALITY OF EXPERIENCE FOR WEB BASED SERVICES>中,研究了Web服务中延时对主观感受 ...
- 使用spring的@Async异步执行方法
应用场景: 1.某些耗时较长的而用户不需要等待该方法的处理结果 2.某些耗时较长的方法,后面的程序不需要用到这个方法的处理结果时 在spring的配置文件中加入对异步执行的支持 <beans x ...
- Spring Boot Async异步执行
异步调用就是不用等待结果的返回就执行后面的逻辑,同步调用则需要等带结果再执行后面的逻辑. 通常我们使用异步操作都会去创建一个线程执行一段逻辑,然后把这个线程丢到线程池中去执行,代码如下: Execut ...
- spring boot @Async异步注解上下文透传
上一篇文章说到,之前使用了@Async注解,子线程无法获取到上下文信息,导致流量无法打到灰度,然后改成 线程池的方式,每次调用异步调用的时候都手动透传 上下文(硬编码)解决了问题. 后面查阅了资料,找 ...
- 利用spring boot构建一个简单的web工程
1.选择Spring InitiaLizr, jdk选择好路径 2.设置项目信息 3.这一步是设置选择使用哪些组件,这里我们只需要选择web 4.设置工程名和路径
- spring的@Async异步使用
pring的@Async功能,用的时候一定要注意: 1.异步方法和调用类不要在同一个类中. 2.xml里需要加入这一行 <task:annotation-driven/> 下面的可以直接粘 ...
随机推荐
- symfony2 安装并创建第一个页面
1.安装和配置 参考 http://symfony.cn/docs/book/installation.html 使用安装工具: windows系统 Open your command console ...
- Amoeba for MySQL---分布式数据库Proxy解决方案
Amoeba是什么? Amoeba(变形虫)项目,致力于MySQL的分布式数据库前端代理层,它主要在应用层访问MySQL的时候充当SQL路由功能,专注于分布式数据库代理层(Database Proxy ...
- Java Gradle入门指南之依赖管理(添加依赖、仓库、版本冲突)
开发任何软件,如何管理依赖是一道绕不过去的坎,软件开发过程中,我们往往会使用这样那样的第三方库,这个时候,一个好的依赖管理就显得尤为重要了.作为一个自动构建工作,Gradle对依赖管理有着很好 ...
- 安卓普通类通过classloader访问资源文件
Android studio不知道怎么设置,才可以在生成APK时把一些文件打包进去. 但是不管怎么样,放在res文件夹下的东西是一定得打包的.所以把一些资源文件放在res/raw这个文件夹里是科学的. ...
- asp.net之treeview无法显示树结点图标(IP与域名的表现竟不一样)
背景 今天接到客户的电话,说部署上去的项目树型的treeview无法正常显示,显示成了好几个大红叉.如: 排查 于是我通过远程登录到服务器,在本地测试了一会发现没有这个问题存在,无论是通过IP ...
- Composer管理PHP包
安装 Composer包含两大逻辑部分:一个是用来存储包,另一个是命令行应用程序,帮助你发现.下载.更新和分享代码. $ cd/path/to/my/project $ curl -s http:// ...
- coursera机器学习-logistic回归,正则化
#对coursera上Andrew Ng老师开的机器学习课程的笔记和心得: #注:此笔记是我自己认为本节课里比较重要.难理解或容易忘记的内容并做了些补充,并非是课堂详细笔记和要点: #标记为<补 ...
- 烂泥:openvpn双网卡客户端与内网机器通信
本文由ilanniweb提供友情赞助,首发于烂泥行天下 想要获得更多的文章,可以关注我的微信ilanniweb. 前段时间写了一篇有关openvpn搭建与内网机器通信的文章,那篇文章是基于服务器单网卡 ...
- 企业邮箱在Android(安卓)系统手机上POP3/IMAP协议的设置方法
此处以三星(系统版本4.4.2)为例,介绍下使用安卓系统自带的客户端如何设置pop/imap协议方式方法 以下我们将使用test@zhuyuming.so 为测试案例,请您操作时更换成您自己的邮箱账号 ...
- C中的数组与指针问题
反复在数组名与指针上犯错误,特记录下. ,,,,}; int *p, *q; p = (); q = (); *(p+1)? *(q-1) ? 答案是 3, 5.这里主要涉及的问题就是指针参与运算 ...