利用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/> 下面的可以直接粘 ...
 
随机推荐
- EMC Documentum DQL整理(二)
			
1.Get file extension SELECT dos_extension FROM dm_format WHERE name IN (SELECT a_content_type FROM d ...
 - JavaScript Patterns 4.9 Configuration Objects
			
Configuration Objects Passing a large number of parameters is not convenient. A better approach is t ...
 - [20140117]疑似checkpoint堵塞数据库连接
			
注:这个说法是不成立的,问题已经解决,但是无法正确的定位到具体什么原因:[20140702]奇怪的应用程序超时 背景: 开发通过应用程序的日志发现间歇性的出现,数据库连接超时 原因: 只能大概猜测,没 ...
 - SQL查询数据库中所有指定类型的字段名称和所在的表名
			
--查询数据库中所有指定类型的字段名称和所在的表名 --eg: 下面查的是当前数据库中 所有字段类型为 nvarchar(max) 的字段名和表名 SELECT cols.object_id , co ...
 - 朝花夕拾之--大数据平台CDH集群离线搭建
			
body { border: 1px solid #ddd; outline: 1300px solid #fff; margin: 16px auto; } body .markdown-body ...
 - 烂泥:高负载均衡学习haproxy之关键词介绍
			
本文由ilanniweb提供友情赞助,首发于烂泥行天下 上一篇文章我们简单讲解了有关haproxy的安装与搭建,在这篇文章我们把haproxy配置文件中使用到的关键词一一介绍下. 关注我微信ilann ...
 - 国内Hadoop应用现状
			
Hadoop在国内主要以互联网公司为主,下面主要介绍大规模使用Hadoop或研究Hadoop的公司. 1. 百度 百度在2006年就关注了Hadoop并开始调研和使用,截止2012年,总的集群规模超过 ...
 - linux基础-第九单元 利用vi编辑器创建和编辑正文文件
			
vi编辑器简介 什么是vi vi编辑器的操作模式 vi编辑器的3种基本模式 在vi编辑器中光标的移动 移动光标位置的键与光标移动间的关系 进入插入模式 从命令行模式进入插入模式的命令 在命令行模式下删 ...
 - html特殊字符的html,js,css写法汇总
			
⇠ 箭头类 符号 UNICODE 符号 UNICODE HTML JS CSS HTML JS CSS ⇠ ⇠ \u21E0 \21E0 ⇢ ⇢ \u21E2 \ ...
 - nodejs模块——http模块
			
http模块主要用于创建http server服务. 本文还用到url模块和path模块,还有fs模块.url模块用于解析url,path模块用于处理和转换文件路径. 一.简单应用 代码如下: // ...