利用Spring MVC中@Async异步特性改善耗时操作的用户体验

Web应用中,有时会遇到一些耗时很长的操作(比如:在后台生成100张报表再呈现,或 从ftp下载若干文件,综合处理后再返回给页面下载),用户在网页上点完按钮后,通常会遇到二个问题:页面超时、看不到处理进度。

对于超时,采用异步操作,可以很好的解决这个问题,后台服务收到请求后,执行异步方法不会阻塞线程,因此就不存在超时问题。但是异步处理的进度用户也需要知道,否则不知道后台服务何时完成,不知道是否继续等候,还是关掉页面。

思路:

1、browser -> Spring-MVC Controller -> call 后台服务中的异步方法 -> 将执行进度更新到redis缓存 -> 返回view

2、返回的view页面上,ajax -> 轮询 call 后台服务 -> 查询redis中的进度缓存数据,并实时更新UI进度显示 -> 如果完成 call 后台服务清理缓存

注:这里采用了redis保存异步处理的执行进度,也可以换成session或cookie来保存。

步骤:

一、spring配置文件中,增加Task支持

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4        xmlns:task="http://www.springframework.org/schema/task"
 5        xsi:schemaLocation="http://www.springframework.org/schema/beans
 6        http://www.springframework.org/schema/beans/spring-beans.xsd
 7        http://www.springframework.org/schema/task
 8        http://www.springframework.org/schema/task/spring-task.xsd">
 9
10
11
12     <!-- 支持异步方法执行 -->
13     <task:annotation-driven/>
14
15 </beans>

二、后台Service中,在方法前加上@Async

先定义服务接口:

 1 package ctas.web.service;
 2
 3 public interface AsyncService {
 4
 5     /**
 6      * 异步执行耗时较长的操作
 7      *
 8      * @param cacheKey
 9      * @throws Exception
10      */
11     void asyncMethod(String cacheKey) throws Exception;
12
13     /**
14      * 获取执行进度
15      *
16      * @param cacheKey
17      * @return
18      * @throws Exception
19      */
20     String getProcess(String cacheKey) throws Exception;
21
22     /**
23      * 执行完成后,清除缓存
24      *
25      * @param cacheKey
26      * @throws Exception
27      */
28     void clearCache(String cacheKey) throws Exception;
29 }

服务实现:

 1 package ctas.web.service.impl;
 2 import ctas.web.service.AsyncService;
 3 import org.springframework.beans.factory.annotation.Autowired;
 4 import org.springframework.data.redis.core.StringRedisTemplate;
 5 import org.springframework.scheduling.annotation.Async;
 6 import org.springframework.stereotype.Service;
 7
 8 @Service("asyncService")
 9 public class AsyncServiceImpl extends BaseServiceImpl implements AsyncService {
10
11     @Autowired
12     StringRedisTemplate stringRedisTemplate;
13
14
15     @Override
16     @Async
17     public void asyncMethod(String cacheKey) throws Exception {
18         //模拟总有20个步骤,每个步骤耗时2秒
19         int maxStep = 20;
20         for (int i = 0; i < maxStep; i++) {
21             Thread.sleep(2000);
22             //将执行进度放入缓存
23             stringRedisTemplate.opsForValue().set(cacheKey, (i + 1) + "/" + maxStep);
24         }
25     }
26
27     @Override
28     public String getProcess(String cacheKey) throws Exception {
29         return stringRedisTemplate.opsForValue().get(cacheKey);
30     }
31
32     @Override
33     public void clearCache(String cacheKey) throws Exception {
34         //完成后,清空缓存
35         stringRedisTemplate.delete(cacheKey);
36     }
37
38
39 }

注意:asyncMethod方法前面的@Async注解,这里模拟了一个耗时的操作,并假设要完成该操作,共需要20个小步骤,每执行完一个步骤,将进度更新到redis缓存中。

三、Controller的处理

 1     @RequestMapping(value = "async/{key}")
 2     public String asyncTest(HttpServletRequest req,
 3                             HttpServletResponse resp, @PathVariable String key) throws Exception {
 4         asyncService.asyncMethod(key);
 5         return "common/async";
 6     }
 7
 8     @RequestMapping(value = "async/{key}/status")
 9     public String showAsyncStatus(HttpServletRequest req,
10                                   HttpServletResponse resp, @PathVariable String key) throws Exception {
11         String status = asyncService.getProcess(key);
12         ResponseUtil.OutputJson(resp, "{\"status\":\"" + status + "\"}");
13         return null;
14     }
15
16     @RequestMapping(value = "async/{key}/clear")
17     public String clearAsyncStatus(HttpServletRequest req,
18                                    HttpServletResponse resp, @PathVariable String key) throws Exception {
19         asyncService.clearCache(key);
20         ResponseUtil.OutputJson(resp, "{\"status\":\"ok\"}");
21         return null;
22     }

四、view上的ajax处理

 1 <script type="text/javascript" language="JavaScript">
 2
 3     var timerId = null;//定时器ID
 4
 5     $(document).ready(function () {
 6
 7         /*
 8          定时轮询执行进度
 9          */
10         timerId = setInterval(function () {
11             getStatus();
12         }, 1000);
13         getStatus();
14     });
15
16     /**
17      获取执行进度
18      */
19     function getStatus() {
20         var statusUrl = window.location.href + "/status";
21         $.get(statusUrl, function (data) {
22             if (data == null || data.status == null || data.status == "null") {
23                 updateStatus("准备中");
24                 return;
25             }
26             var status = data.status;
27             updateStatus(status);
28             var temp = status.split("/");
29             if (temp[0] == temp[1]) {
30                 updateStatus("完成");
31                 clearInterval(timerId);//停止定时器
32                 clearStatus();//清理redis缓存
33             }
34         })
35     }
36
37     /**
38      * 执行完成后,清理缓存
39      */
40     function clearStatus() {
41         var clearStatusUrl = window.location.href + "/clear";
42         $.get(clearStatusUrl, function (data) {
43             //alert(data.status);
44         })
45     }
46
47     /**
48      更新进度显示
49      */
50     function updateStatus(msg) {
51         $("#status").html(msg);
52     }
53 </script>
54 <div id="msgBox">
55     <span>请稍候,服务器正在处理中...</span>
56
57     <h1>当前处理进度:<span style="color:red" id="status">准备中</span></h1>
58 </div>

浏览 http://localhost:8080/xxx/async/123123后的效果

时间: 2025-01-05 18:26:27

利用Spring MVC中@Async异步特性改善耗时操作的用户体验的相关文章

【Spring学习笔记-MVC-5】利用spring MVC框架,实现ajax异步请求以及json数据的返回

作者:ssslinppp      时间:2015年5月26日 15:32:51 1. 摘要 本文讲解如何利用spring MVC框架,实现ajax异步请求以及json数据的返回. Spring MVC与json参考文章:[spring学习笔记-mvc-3]返回json数据-方式1  和 [spring学习笔记-mvc-4]返回json数据-方式2. 使用到的技术主要如下: Ajax:使用JQuery 提供的ajax:==>需要引入jquery.min.js文件: Spring MVC: Jso

Spring MVC中处理静态资源的多种方法

处理静态资源,我想这可能是框架搭建完成之后Web开发的”头等大事“了. 因为一个网站的显示肯定会依赖各种资源:脚本.图片等,那么问题来了,如何在页面中请求这些静态资源呢? 还记得Spring MVC中的DispatcherServlet吗?它是Spring MVC中的前置控制器,若配置的拦截路径为“/”,那么所有的请求都将被它拦截.对静态资源的访问也属于一个请求,那么也会被它拦截,然后进入它的匹配流 程,我们知道它是根据HandlerMapping的配置来匹配的.而对于静态资源来说,默认的Spr

ASP.NET MVC中使用异步控制器

线程池 一直想把项目改写成异步,但是ASP.NETMVC3下写的过于繁琐,.NET 4.5与ASP.NET MVC下代码写起来就比较简单了, MS好像也一直喜欢这样搞,每一个成熟的东西,都要演变好几个版本,才能趋于规范. ASP.NET MVC 中为什么需要使用异步呢,IIS有一个线程池来处理用户的请求,当一个新的请求过来时,将调度池中的线程以处理该请求,然而,但并发量很高的情况下,池中的线程已经不能够满足这么多的请求时候,池中的每一个线程都处于忙的状态则在处理请求时将阻塞处理请求的线程,并且该

spring mvc 和ajax异步交互完整实例(转自CSDN) 附下载地址

spring mvc 和ajax异步交互完整实例 spring MVC 异步交互demo: demo下载地址:http://download.csdn.net/download/quincylk/9521375 1.jsp页面: [java] view plain copy print? <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-

在 ASP.NET MVC 中使用异步控制器

线程池 一直想把项目改写成异步,但是ASP.NETMVC3下写的过于繁琐,.NET 4.5与ASP.NET MVC下代码写起来就比较简单了, MS好像也一直喜欢这样搞,每一个成熟的东西,都要演变好几个版本,才能趋于规范. ASP.NET MVC 中为什么需要使用异步呢,IIS有一个线程池来处理用户的请求,当一个新的请求过来时,将调度池中的线程以处理该请求,然而,但并发量很高的情况下,池中的线程已经不能够满足这么多的请求时候,池中的每一个线程都处于忙的状态则在处理请求时将阻塞处理请求的线程,并且该

(转)Spring MVC中处理静态资源的多种方法

处理静态资源,我想这可能是框架搭建完成之后Web开发的”头等大事“了. 因为一个网站的显示肯定会依赖各种资源:脚本.图片等,那么问题来了,如何在页面中请求这些静态资源呢? 还记得Spring MVC中的DispatcherServlet吗?它是Spring MVC中的前置控制器,若配置的拦截路径为“/”,那么所有的请求都将被它拦截.对静态资源的访问也属于一个请求,那么也会被它拦截,然后进入它的匹配流 程,我们知道它是根据HandlerMapping的配置来匹配的.而对于静态资源来说,默认的Spr

利用Spring MVC 上传图片文件

本文转自:http://amcucn.iteye.com/blog/264457.感谢作者 近日在工作当中,需要用到上传图片的功能,然而自己平时学习的时候只会使用struts的上传功能,但因为项目并没有使用struts,而是spring mvc ,最后不得不另寻它路.通过google和百度,看到了一些相关的介绍.经过自己的偿试,最终搞定利用spring mvc 上传文件的功能,完成图片的上传.如果只是为了上传图片,可以通过限制扩展名的形式达到目的.下面给出关键的代码部分. 关于spring的配置

Spring MVC中防止csrf攻击

Spring MVC中防止csrf攻击的拦截器示例 https://blog.csdn.net/qq_40754259/article/details/80510088 Spring MVC中的CSRF攻击防御 https://blog.csdn.net/minebk/article/details/81430177 利用spring-security解决CSRF问题 https://blog.csdn.net/u013185616/article/details/70446392 Securi

spring MVC 中获取request

spring MVC中如何获取request 呢? 有如下方式: 方式一:在action中注入request 直接在action的参数中增加HttpServletRequest request 例如 /*** * 返回json * @param id * @param roleLevel * @param model * @param request * @param targetView * @return * @throws SecurityException * @throws NoSuc