利用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/> 下面的可以直接粘 ...
随机推荐
- linux 学习随笔-磁盘管理
1:df 用于查看已挂载磁盘的容量信息 -i 查看inodes使用情况 -h 以合适的单位显示 -k -m 分别以k M单位显示 2:du 查看某个文件或者目录占用的空间 du [-abckmsh] ...
- EMC Documentum DQL整理(一)
1.Get user SELECT * FROM dm_user WHERE r_is_group = 0 2.Get Group SELECT * FROM dm_group WHERE gro ...
- 表格table嵌套,边框合并问题
[问题] 外层table与内层table嵌套,内外表格都需边框时,设置“border=1”,但边框会重复,造成某些地方边框粗,有些地方边框细的问题. [解决办法]: 外表格样式: <tabl ...
- C#编写抽奖问题
输入每个人的中奖号码,进行滚动显示 //清屏 //随即一个索引 // 根据索引打印 //等待0.1秒 Console.Write("请输入参与者人数:&q ...
- Spring 整体架构
1. Core Container:核心容器(core.Beans.Context.Expression Language Core.Beans框架基础构成,提供IOC.依赖注入特性.BeanFa ...
- coursera机器学习笔记-神经网络,学习篇
#对coursera上Andrew Ng老师开的机器学习课程的笔记和心得: #注:此笔记是我自己认为本节课里比较重要.难理解或容易忘记的内容并做了些补充,并非是课堂详细笔记和要点: #标记为<补 ...
- java 自动装箱自动拆箱
1.Java数据类型 在介绍Java的自动装箱和拆箱之前,我们先来了解一下Java的基本数据类型. 在Java中,数据类型可以分为两大种,Primitive Type(基本类型)和Reference ...
- js判断手机访问PC端跳转到手机站
<script type="text/javascript">(function() { //得到域名后缀 var path = location.pathname.s ...
- LaTeX字体相关
以下内容均来自网络. 字体命令: 相应的字体声明都有相应的字体命令 family: \textrm{文本} \texttt{ } \textsf{ } shape : \textup{文本} \ ...
- 笔记:html 拾遗之一
html 拾遗之一 今天翻了下w3schools.com 把忘掉的语法记一下(仅常用但是不熟的部分) img alt 属性,当图片无法显示时可显示alt属性的文字 br 换行 < html la ...