<dependencies>
<!-- Quartz 核心 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version> <!-- 建議升級至 2.x -->
</dependency>
<!-- Spring Context Support(含 Quartz 整合類) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.3.25</version>
</dependency>
</dependencies>
註:原文使用 1.8.5 版本屬於舊世代,生產環境建議升級至 2.3.x,API 有變動但核心概念一致。
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 1. 任務執行類 -->
<bean id="rewardTask" class="com.example.quartz.GenerateRewardListTask"/>
<!-- 2. JobDetail:封裝目標對象與方法 -->
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="rewardTask"/>
<property name="targetMethod" value="generate"/>
<property name="concurrent" value="false"/> <!-- 禁止並發執行 -->
</bean>
<!-- 3. CronTrigger:定義執行時間表達式 -->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="jobDetail"/>
<!-- 每天 23:00 執行 -->
<property name="cronExpression" value="0 0 23 * * ?"/>
<!-- 若需指定年份:0 0 23 10 11 ? 2016(僅 2016-11-10 23:00) -->
</bean>
<!-- 4. SchedulerFactoryBean:統一管理觸發器 -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list><ref bean="cronTrigger"/></list>
</property>
<property name="overwriteExistingJobs" value="true"/>
<property name="startupDelay" value="30"/> <!-- 啟動 30s 後啟動調度,避免搶資源 -->
</bean>
</beans>
@Slf4j
public class GenerateRewardListTask {
@Autowired private GiftReceiveRecordService giftReceiveRecordService;
public void generate() {
try {
log.info("開始生成獎勵名單...");
giftReceiveRecordService.generateRewardMember();
log.info("獎勵名單生成完成");
} catch (Exception e) {
log.error("定時任務異常", e);
// 建議拋出 JobExecutionException 讓 Quartz 記錄失敗狀態
throw new JobExecutionException(e);
}
}
}
格式:秒 分 時 日 月 周 [年]
* 通配符
? 不指定值(日/周二擇一)
/ 遞增(0/15 = 每 15 秒)
- 範圍
, 列舉
常用範例:
0 0 2 * * ? 每天 02:00
0 0/5 * * * ? 每 5 分鐘
0 10,44 14 ? 3 WED 三月每週三 14:10 和 14:44
0 0 23 10 11 ? 2016 2016-11-10 23:00 單次
| 坑點 | 現象 | 解決方案 |
|---|---|---|
| Cron 帶年份 | 專案啟動時間已過該時間點,Quartz 反覆嘗試補償執行,導致啟動卡死、Memory Leak 警告 | 生產環境嚴禁在 Cron 中寫死年份,改用「每天固定時間」或啟動時動態計算下一次執行時間 |
| 修改系統時間測試 | Quartz 基於 JVM 啟動時間計算間隔,而非讀取系統實時鐘 | 單元測試請用 TimeProvider 抽象時間源,或直接調用 scheduler.triggerJob() 手動觸發 |
| 事務邊界缺失 | 任務中資料庫操作失敗不回滾 | 在 Service 方法加 @Transactional,或在 Task 中程式化控制 TransactionTemplate |
| 集群模式重複執行 | 多節點部署時同一任務並發跑多份 | 啟用 Quartz 集群:org.quartz.jobStore.isClustered=true,共享資料庫表,配置 instanceId=AUTO |
@EnableScheduling + @Scheduled(cron = "0 0 23 * * ?") 即可,無需 XML。Spring + Quartz XML 配置核心四件套:Task Bean → JobDetail → CronTrigger → SchedulerFactoryBean。牢記「不寫死年份、不改系統時間測試、集群必開 isClustered」,即可避開 90% 生產事故。