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应用中耗时操作的用户体验的更多相关文章

  1. Spring Boot @Async 异步任务执行

    1.任务执行和调度 Spring用TaskExecutor和TaskScheduler接口提供了异步执行和调度任务的抽象. Spring的TaskExecutor和java.util.concurre ...

  2. Spring学习(四)在Web项目中实例化IOC容器

    1.前言 前面我们讲到Spring在普通JAVA项目中的一些使用.本文将介绍在普通的Web项目中如何实例化Spring IOC容器.按照一般的思路.如果在Web中实例化Ioc容器.这不得获取Conte ...

  3. 利用简洁的图片预加载组件提升h5移动页面的用户体验

    在做h5移动页面,相信大家一定碰到过页面已经打开,但是里面的图片还未加载出来的情况,这种问题虽然不影响页面的功能,但是不利于用户体验.抛开网速的原因,解决这个问题有多方面的思路:最基本的,要从http ...

  4. Web服务中延时对QoE(体验质量)的影响

    S. Egger等人在论文<WAITING TIMES IN QUALITY OF EXPERIENCE FOR WEB BASED SERVICES>中,研究了Web服务中延时对主观感受 ...

  5. 使用spring的@Async异步执行方法

    应用场景: 1.某些耗时较长的而用户不需要等待该方法的处理结果 2.某些耗时较长的方法,后面的程序不需要用到这个方法的处理结果时 在spring的配置文件中加入对异步执行的支持 <beans x ...

  6. Spring Boot Async异步执行

    异步调用就是不用等待结果的返回就执行后面的逻辑,同步调用则需要等带结果再执行后面的逻辑. 通常我们使用异步操作都会去创建一个线程执行一段逻辑,然后把这个线程丢到线程池中去执行,代码如下: Execut ...

  7. spring boot @Async异步注解上下文透传

    上一篇文章说到,之前使用了@Async注解,子线程无法获取到上下文信息,导致流量无法打到灰度,然后改成 线程池的方式,每次调用异步调用的时候都手动透传 上下文(硬编码)解决了问题. 后面查阅了资料,找 ...

  8. 利用spring boot构建一个简单的web工程

    1.选择Spring InitiaLizr,    jdk选择好路径 2.设置项目信息 3.这一步是设置选择使用哪些组件,这里我们只需要选择web 4.设置工程名和路径

  9. spring的@Async异步使用

    pring的@Async功能,用的时候一定要注意: 1.异步方法和调用类不要在同一个类中. 2.xml里需要加入这一行 <task:annotation-driven/> 下面的可以直接粘 ...

随机推荐

  1. Aptana Studio 3 汉化简体中文版

    最近开始学习ruby on rails了,同事推荐我用aptana这个编辑器,它对ror的支持比较好,所以安装了这个软件,但是发现都是英文的,所以在网上看汉化教程,幸亏有高手写过这个文章了,这里我只是 ...

  2. 【译】Java中的可变参数

    译文链接 http://www.programcreek.com/2014/01/java-varargs-examples/ 什么是Java可变参数 可变参数是Java 1.5引入的一个特性,它允许 ...

  3. 使用 xtrabackup 进行MySQL数据库物理备份

    0. xtrabackup的功能 能实现的功能: 非阻塞备份innodb等事务引擎数据库. 备份myisam表会阻塞(需要锁). 支持全备.增量备份.压缩备份. 快速增量备份(xtradb,原理类似于 ...

  4. 内部类访问的局部变量必须加final

    (1)内部类是外部类的一个成员,就像外部类的成员方法一样,所以内部类有权限访问外部类的所有成员,包括private的.  (2)内部类不能访问外部类方法中的局部变量,除非变量是final的(一般发生在 ...

  5. Containers Reserved yarn resourcemanager

    yarn rm的管理页面中显示了集群的概况,其中有一个指标叫Containers Reserved . 预留的容器,为什么会预留,集群的资源使用饱合,新的app请求的资源一般会进入pending状态, ...

  6. AI(Adobe Illustrator)简单入门——骷髅

    成果: 步骤如下: 一.椭圆工具画正圆 按住shift+alt画一个正圆. 二.圆角矩形工具画矩形 用圆角矩形工具画一个矩形,然后全选水平垂直对齐. 三.画眼睛 利用椭圆工具画一只眼睛,然后按住alt ...

  7. 以后上午就只能这样了么-jQuery

    hi 昨天睡得不错 为什么早上还是看不进论文,宁愿做这个,也不愿认真看论文.感觉上还是下午看论文感觉要好的多.不过最近有三十多篇要看哇...管球... 1.jQuery -----jQuery常用插件 ...

  8. java设计模式之命令模式

    学校中.生活中.社会中总是会存在一定的阶层,虽然我们很多人都不可认可阶层的存在.命令这一词也就在阶层中诞生.家长命令孩子,老师命令学生,领导命令小娄娄.这些都在我们的生活存在的东西,相信这一个模式学习 ...

  9. 【温故而知新-Javascript】使用canvas元素(第二部分)

    本文将继续介绍canvas的功能,展示如何绘制更复杂的图形(包括圆弧和曲线),如何使用剪裁区域来限制操作以及如何绘制文本.还是介绍可以应用在画布上的特效和变换,包括阴影.透明度.旋转和坐标重映射. 1 ...

  10. jmeter 中的 HTTP URL Re-writing Modifier

    URL rewriting modifier,因为tomcat的session实现不是通过cookie的,而是通过session id的,就是说,用户登录有了session之后,tomcat就会维护一 ...