利用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/> 下面的可以直接粘 ...
随机推荐
- 全面理解JavaScript中的闭包的含义及用法
1.什么是闭包 闭包:闭包就是能够读取其他函数内部变量的函数;闭包简单理解成“定义在一个函数内部的函数”. 闭包的形式:即内部函数能够使用它所在级别的外部函数的参数,属性或者内部函数等,并且能在包含它 ...
- JavaScript(五)——插入地图
代码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3. ...
- maven 安装alipay-sdk包到本地及远程仓库
安装到本地:mvn install:install-file -DgroupId=com.alipay -DartifactId=sdk-Java -Dversion=*** -Dpackaging= ...
- PostgreSQL模仿Oracle的instr函数
-- -- instr functions that mimic Oracle's counterpart -- Syntax: instr(string1, string2, [n], [m]) w ...
- activiti入门
一.Activiti简介 Activiti 是一个针对商务人士. 开发人员和系统管理员的轻量级的工作流和业务流程管理 (BPM) 平台.它的核心是Java的高速和可靠的 BPMN 2 流程引擎.它是开 ...
- Seafile内部云盘
软件列表 软件 版本 备注 centos 6.4 x86_64 64位系统 mysql mysql5.5.49 本机使用 python 2.7 seafile 依赖python pip 8.1.2 安 ...
- Linux和开源已经在2013年开始悄悄主宰世界?
提到Linux,很多人觉得它依旧不温不火,实际上在这个即将过去的一年,Linux和开源软件已经悄悄主宰了计算领域的各方面,这具体表现在开源项目遍地、Chromebook崛起、SteamOS备受瞩目、A ...
- MyEclipse 启动 tomcate 失败 解决方法
MyEclipse中启动tomcate失败 十月 20, 2015 9:53:04 下午 org.apache.catalina.startup.VersionLoggerListener log信息 ...
- 更改项目名或者多个项目时,发现多个"Home"匹配的Controller时,解决方法
[备份]异常信息:找到多个与名为“Home”的控制器匹配的类型.如果为此请求(“{controller}/{action}/{id}”)提供服务的 路由在搜索匹配此请求的控制器时没有指定命名空间,则会 ...
- [No000012]编程中浮点数之什么是科学计数法
科学记数法 把一个绝对值小于1(或者大于等于10)的实数记为a×10n的形式(其中1≤/a/<10),这种记数法叫做科学记数法. (或者大于等于10)的实数记为a×10^n的形式(其中1≤|a| ...