使用场景:
在平常web开发过程中,有时操作员要做一个后台会运行很长时间的任务(如上传一个大文件到后台处理),而此时前台页面仍需要给用户及时的进度信息反馈,同时还要避免前台页面超时。
框架介绍:
本架构采用Struts+Spring+AJAX(jquery)方式实现,前台提交任务到后台,然后通过AJAX方式周期性获取任务进度,展示给用户看。
本框架支持提交任务、查看任务进展、停止任务、删除任务。
使用java线程池的技术来执行任务,避免不停的生成新的线程;
代码下载(内含使用样例):
https://github.com/jerrymousecn/daemo_tasks
下载包说明:
1)demo/daemonTasks.war为tomcat发布包(zip格式),可以直接放到tomcat的webapps目录;
2)demo/demo_source目录是eclipse样例项目代码
3)source目录是本框架源代码
主要代码:
1.DaemonTask.java
后台任务基础类
package cn.jerry.tools.tasks; import java.util.Date; public abstract class DaemonTask implements Runnable { private String taskID; private String taskName; private String taskDesc; private boolean isStartedFlag = false;; private boolean isTerminated = false;; private int progress; private Date startTime; private Date terminatedTime; protected boolean toStopFlag = false; protected abstract void execute(); protected void setProgress(int progress) { this.progress = progress; } public void run() { this.isStartedFlag = true; this.execute(); this.isTerminated = true; this.terminatedTime = new Date(); } public String getTaskID() { return taskID; } public void setTaskID(String taskID) { this.taskID = taskID; } public boolean isStarted() { return isStartedFlag; } public boolean getIsTerminated() { return isTerminated; } public void setToStopFlag(boolean toStopFlag) { this.toStopFlag = toStopFlag; } public Date getStartTime() { return startTime; } public Date getTerminatedTime() { return terminatedTime; } public int getProgress() { return progress; } public String getTaskName() { return taskName; } public void setTaskName(String taskName) { this.taskName = taskName; } public String getTaskDesc() { return taskDesc; } public void setTaskDesc(String taskDesc) { this.taskDesc = taskDesc; } }
2.TaskManager.java
后台任务管理类,支持添加任务,停止任务,删除任务,查看任务,对已完成任务进行定期清理,获取任务进度
package cn.jerry.tools.tasks; import java.util.Collections; import java.util.Date; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.UUID; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TaskManager { private static TaskManager taskManager = new TaskManager(); private ExecutorService executorService = Executors.newCachedThreadPool(); private ConcurrentMap<String, DaemonTask> taskMap = new ConcurrentHashMap<String, DaemonTask>(); private final int TASK_NOT_FOUND = -1; private TaskCleaner taskCleaner = new TaskCleaner(taskMap); private Timer timer = new Timer(); private final int CLEAN_PERIOD = 200000; //in milliseconds private String getUniqueID() { return UUID.randomUUID().toString(); } private TaskManager() { startTerminatedTaskCleanTimer(); } private void startTerminatedTaskCleanTimer() { timer.schedule(taskCleaner, 0, CLEAN_PERIOD); } private void stopTerminatedTaskCleanTimer() { timer.cancel(); } public static TaskManager getInstance() { return taskManager; } public synchronized String addTaskAndStart(DaemonTask task) { String taskID = getUniqueID(); task.setTaskID(taskID); taskMap.put(taskID, task); executorService.execute(task); return taskID; } public synchronized void stopTask(String taskID) { DaemonTask task = getTask(taskID); if (task != null) { task.setToStopFlag(true); } } public synchronized void delFinishedTask(String taskID) { DaemonTask task = getTask(taskID); if (task != null) { if (task.getIsTerminated()) { taskMap.remove(taskID); } } } public DaemonTask getTask(String taskID) { return taskMap.get(taskID); } public Map<String, DaemonTask> getTasksForView() { Map<String, DaemonTask> map = Collections.unmodifiableMap(taskMap); return map; } public int getProgress(String taskID) { int progress = TASK_NOT_FOUND; DaemonTask task = getTask(taskID); if (task != null) { progress = task.getProgress(); } return progress; } } class TaskCleaner extends TimerTask { private ConcurrentMap<String, DaemonTask> taskMap; public TaskCleaner(ConcurrentMap<String, DaemonTask> taskMap) { this.taskMap = taskMap; } @Override public void run() { for (Entry<String, DaemonTask> entry : taskMap.entrySet()) { String taskID = entry.getKey(); DaemonTask task = entry.getValue(); if (task.getIsTerminated()) { Date terminatedTime = task.getTerminatedTime(); Date now = new Date(); long timeOffset = getTimeOffset(terminatedTime, now); if (timeOffset > 10000) { taskMap.remove(taskID); System.out.println("task: " + taskID + " is removed"); } } } } private long getTimeOffset(Date date1, Date date2) { long time1 = date1.getTime(); long time2 = date2.getTime(); long offset = time2 - time1; if (offset < 0) { offset = -offset; } return offset; } }
3.任务样例TestTask.java
关键点需要注意: 方法中需要自行计算进度(完成时,进度应设置为100),并且要对toStopFlag进行判断,用于终止任务。
package cn.jerry.tools.tasks; public class TestTask extends DaemonTask { @Override protected void execute() { for (int i = 0; i < 10; i++) { if(this.toStopFlag==false) { int progress = (i + 1) * 100 / 10; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.setProgress(progress); } } } }
4.前台查询状态页面getTaskResult.jsp:
<%@ taglib prefix="s" uri="/struts-tags" %> <html> <head> <script type="text/javascript" src="js/jquery-1.11.1.min.js"></script> <link rel="stylesheet" href="css/jquery-ui.css"> <script src="js/jquery-ui.js"></script> <link rel="stylesheet" href="css/style.css"> <script type="text/javascript"> var intervalId; function trim(str){ return str.replace(/(^s*)|(s*$)/g, ""); } function getprogress() { htmlobj=$.ajax({url:"getTaskProgress.jsp?taskid="+'<s:property value="taskid"/>',cache:false,async:false}); str = trim(htmlobj.responseText); $("#myDiv").html(str); $( "#progressbar" ).progressbar({ value: parseInt(str) }); if(parseInt(str)>=100) { clearInterval(intervalId); } } $(document).ready(function(){ intervalId = setInterval(getprogress, 1000); }); </script> </head> <body> Task [<s:property value="taskName"/>]progress: <div id="progressbar"></div> <div id="myDiv"></div> </body> </html>
5.相关截图
1)进度页面截图:
2)查看任务列表
声明:
本框架中使用的进度条代码实现来自以下网站,版权不属于本人,本框架仅用于学习交流。
http://jqueryui.com/progressbar/#animated