Spring的@Async异步处理

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

30140445_rezB.png

转载于:https://my.oschina.net/jwdstef/blog/649830

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值