• 利用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支持

     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>
    View Code

    二、后台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 }
    View Code

    服务实现:

     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 }
    View Code

    注意: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 Code

    四、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>
    View Code

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

  • 相关阅读:
    MAC之基本命令(持续更新)
    Mac下android_sdk配置环境变量
    Eclipse最常用10大快捷键
    Android之使用Jsoup抓取网络数据
    MAC之curl命令
    MAC之cat命令
    Android之FileOutputStream与openFileOutput()的区别
    C# 数字语音wav 提示。。。。。。。。。。。
    HttpWebRequest 获取验证码的图片 并针对有验证码的网页进行Winform登陆。
    经常开车,坐车的朋友请进(看后对你绝对有好处)
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/how-to-use-Async-annotation-in-spring-mvc.html
Copyright © 2020-2023  润新知