文章出處

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后的效果

 


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()