Web 應用常需定期執行維護任務(資料備份、報表產生、快取刷新)。除引入 Quartz 等重量級框架,JDK 內建 java.util.Timer 與 TimerTask 配合 ServletContextListener 即可實現輕量級排程,無額外依賴。
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.util.Timer;
public class TaskManager implements ServletContextListener {
private static final long PERIOD_DAY = 24 * 60 * 60 * 1000L;
private static final long PERIOD_WEEK = PERIOD_DAY * 7;
private static final long NO_DELAY = 0;
private Timer timer;
@Override
public void contextInitialized(ServletContextEvent sce) {
timer = new Timer("資料庫備份排程", true); // daemon 執行緒
// 每 4 週(約一個月)執行一次,立即首次執行
timer.schedule(new BackUpTableTask(), NO_DELAY, PERIOD_WEEK * 4);
// 測試用:每 30 秒執行一次
// timer.schedule(new BackUpTableTask(), NO_DELAY, 30_000);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
timer.cancel(); // 關閉排程器,釋放執行緒
}
}
import java.util.TimerTask;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class BackUpTableTask extends TimerTask {
private static final Log log = LogFactory.getLog(BackUpTableTask.class);
private static boolean isRunning = false;
@Override
public void run() {
if (!isRunning) {
isRunning = true;
try {
log.debug("開始執行備份任務...");
// TODO: 實作實際備份邏輯
// backupDatabase();
log.debug("備份任務完成...");
} catch (Exception e) {
log.error("備份任務異常", e);
} finally {
isRunning = false;
}
} else {
log.warn("上一輪備份尚未結束,跳過本次執行");
}
}
}
關鍵點:isRunning 旗標防止任務重疊執行;實際專案建議改用 AtomicBoolean 或 ReentrantLock 確保執行緒安全。
<listener>
<listener-class>com.example.task.TaskManager</listener-class>
<description>資料庫定期備份排程任務</description>
</listener>
run() 內未捕獲例外會導致 Timer 執行緒終結,後續排程失效。務必在 run() 完整捕獲並記錄例外。@Scheduled、@EnableScheduling 提供聲明式排程,支援 Cron 運算式、固定延遲/頻率,整合 Spring 容器生命週期,推薦 Spring 專案優先採用。Timer + ServletContextListener 適合簡單、單節點、無持久化需求的輕量排程。隨著系統複雜度提升,建議遷移至 Quartz 或 Spring Scheduling 獲得企業級功能。