Web应用中,有时会遇到一些耗时很长的操作(比如:在后台生成100张报表再呈现,或 从ftp下载若干文件,综合处理后再返回给页面下载),用户在网页上点完按钮后,通常会遇到二个问题:页面超时、看不到处理进度。
对于超时,采用异步操作,可以很好的解决这个问题,后台服务收到请求后,执行异步方法不会阻塞线程,因此就不存在超时问题。但是异步处理的进度用户也需要知道,否则不知道后台的异步处理何时完成,用户无法决定接下来应该继续等候? or 关掉页面?
思路:
1、browser -> Spring-MVC Controller -> call 后台服务中的异步方法 -> 将执行进度更新到redis缓存 -> 返回view
2、返回的view页面上,ajax -> 轮询 call 后台服务 -> 查询redis中的进度缓存数据,并实时更新UI进度显示 -> 如果完成 call 后台服务清理缓存
注:这里采用了redis保存异步处理的执行进度,也可以换成session或cookie或缓存来保存。
步骤:
一、spring配置文件中,增加Task支持
<!-- 计划任务配置,用 @Service @Lazy(false)标注类,用@Scheduled(cron = "0 0 2 * * ?")标注方法 -->
<task:executor id="executor" pool-size="10"/> <task:scheduler id="scheduler" pool-size="10"/>
<task:annotation-driven scheduler="scheduler" executor="executor" proxy-target-class="true"/>
注解的应用范围:
类:表示这个类中的所有方法都是异步的
方法:表示这个方法是异步的,如果类也注解了,则以这个方法的注解为准
相关的配置:
<task:annotation-driven />配置:
executor:指定一个缺省的executor给@Async使用。
例子:
<task:annotation-driven executor="asyncExecutor" />
<task:executor />配置参数:
id:当配置多个executor时,被@Async(“id”)指定使用;也被作为线程名的前缀。
pool-size:
core size:最小的线程数,缺省:1
max size:最大的线程数,缺省:Integer.MAX_VALUE
queue-capacity:当最小的线程数已经被占用满后,新的任务会被放进queue里面,当这个queue的capacity也被占满之后,pool里面会创建新线程处理这个任务,直到总线程数达到了max size,这时系统会拒绝这个任务并抛出TaskRejectedException异常(缺省配置的情况下,可以通过rejection-policy来决定如何处理这种情况)。缺省值为:Integer.MAX_VALUE
keep-alive:超过core size的那些线程,任务完成后,再经过这个时长(秒)会被结束掉
rejection-policy:当pool已经达到max size的时候,如何处理新任务
ABORT(缺省):抛出TaskRejectedException异常,然后不执行
DISCARD:不执行,也不抛出异常
DISCARD_OLDEST:丢弃queue中最旧的那个任务
CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
View Code
二、后台Service中,在方法前加上@Async
@Service
public class Async_Service {
@Async("executor")
public void select_GP_choose1(String sql, String itinerary, String itinerary_date, String itinerary_end, HttpServletRequest request){
...
// 查询数据库耗时任务
}
@Async("executor")
public void select_GP_choose2(String sql, String itinerary, String itinerary_date, String itinerary_end, HttpServletRequest request){
...
// 查询数据库耗时任务
}
@Async("executor")
public void select_GP_choose3(String sql, String itinerary, String itinerary_date, String itinerary_end, HttpServletRequest request){
...
// 查询数据库耗时任务
}
@Async("executor")
public void select_GP_choose4(String sql, String itinerary, String itinerary_date, String itinerary_end, HttpServletRequest request){
...
// 查询数据库耗时任务
}
}
Controller的处理
@Controller
@RequestMapping(value = "${adminPath}")
public class DepartureInformationQueryController {
@Autowired
RuntimeService runtimeService;
@Autowired
QuerytimesService querytimeService;
@Autowired
Async_Service async_service;
@RequestMapping("select_ligang")
public ModelAndView select_ligang(String itinerary,String itinerary_date,String choose,String itinerary_end,HttpServletRequest request){
//异步调用
async_service.select_GP_choose1(sql,itinerary,itinerary_date,itinerary_end,request);
}
@RequestMapping("async_status")
@ResponseBody
public Map<String, String> async_status(){
String async_result=(String)CacheUtils.get("async_result");
String error=(String)CacheUtils.get("error");
Map<String, String> map = new HashMap<>();
map.put("async_result",async_result);
map.put("error",error);
return map;
}
@RequestMapping("async_clean")
@ResponseBody
public Map<String, String> async_clean(){
CacheUtils.remove("async_result");
CacheUtils.remove("error");
Map<String, String> map = new HashMap<>();
map.put("messge","清理成功");
return map;
}
}
view上的ajax处理
<%-- ajax轮询异步请求进度--%>
<script type="text/javascript">
var timerId = null;//定时器ID
$(document).ready(function () {
// 定时轮询执行进度
timerId = setInterval(function () {
getStatus();
}, 1000);
//getStatus();
});
// 获取执行进度
function getStatus() {
var statusUrl = "${ctx}/async_status";
$.get(statusUrl, function (data) {
if (data.async_result==null || data.async_result==="" ) {
return;
}
var download_url=data.async_result;
if(download_url==="未查询到数据"){
$(".download_a").attr('href', '#');
$(".download_a").text("未查询到数据");
$("#change_td").text("已发送");
}else {
$(".download_a").attr('href', '${pageContext.request.contextPath}'+download_url);
$(".download_a").text("下载");
$("#change_td").text("已发送");
}
clearInterval(timerId);//停止定时器
//清理缓存中数据
$.get("${ctx}/async_clean", function (data) {
})
})
}
</script>
网友评论