首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Spring 整合 Quartz 實現定時任務:從 XML 配置到註解驅動的完整演進

     編輯:Java 綜合教學

前言:為什麼選 Quartz?

Quartz 是 Java 生態最成熟的調度框架,支持Cron 表達式、集群、持久化、彈性誤火策略、監聽器鏈。Spring 提供無縫整合,本文展示從傳統 XML 到現代註解驅動的完整實踐。

方案一:傳統 XML 配置 (Spring 3/4 風格)

1. 依賴坐標

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>5.3.x</version>
</dependency>
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.2</version>
</dependency>

2. 任務類 (POJO,無 Quartz 依賴)

package com.example.task;

public class ApiMonitorTask {
    private static final Logger log = LoggerFactory.getLogger(ApiMonitorTask.class);

    public void verifyChannel() {
        log.info("Quartz 任務執行:上游接口巡檢 - {}", LocalDateTime.now());
        // 業務邏輯:調用 API、檢查響應、記錄異常、發送告警
    }
}

3. quartz-config.xml 核心配置

<?xml version="1.0" encoding="UTF-8"?>
<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 -->
    <bean id="apiMonitorTask" class="com.example.task.ApiMonitorTask" />

    <!-- 2. JobDetail: 定義調用對象與方法 -->
    <bean id="monitorJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject" ref="apiMonitorTask" />
        <property name="targetMethod" value="verifyChannel" />
        <property name="concurrent" value="false" /> <!-- 禁止並發執行 -->
    </bean>

    <!-- 3. Trigger: Cron 表達式觸發 -->
    <bean id="monitorTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="monitorJobDetail" />
        <property name="cronExpression" value="0 0/30 * * * ?" /> <!-- 每 30 分鐘 -->
        <property name="misfireInstructionName" value="MISFIRE_INSTRUCTION_FIRE_NOW" /> <!-- 誤火策略 -->
    </bean>

    <!-- 4. SchedulerFactoryBean: 總調度器 -->
    <bean id="schedulerFactory" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref bean="monitorTrigger" />
            </list>
        </property>
        <property name="startupDelay" value="30" /> <!-- 啟動延遲 30s,待應用完全啟動 -->
        <property name="autoStartup" value="true" />
        <property name="overwriteExistingJobs" value="true" /> <!-- 覆蓋同名任務 -->
    </bean>

</beans>

4. web.xml 載入 (非 Spring Boot 環境)

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring/quartz-config.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

方案二:Spring Boot 2.x+ 註解驅動 (推薦)

1. 配置類

@Configuration
@EnableScheduling // 啟用 Spring 任務調度 (可選,Quartz 不依賴此註解)
public class QuartzConfig {

    @Bean
    public JobDetail apiMonitorJobDetail() {
        return JobBuilder.newJob(ApiMonitorJob.class) // 實現 Job 接口的類
            .withIdentity("apiMonitorJob")
            .storeDurably() // 無 Trigger 時也保留
            .build();
    }

    @Bean
    public Trigger apiMonitorTrigger(JobDetail apiMonitorJobDetail) {
        return TriggerBuilder.newTrigger()
            .forJob(apiMonitorJobDetail)
            .withIdentity("apiMonitorTrigger")
            .withSchedule(CronScheduleBuilder.cronSchedule("0 0/30 * * * ?"))
            .withMisfireHandlingInstructionFireAndProceed() // 誤火立即執行
            .build();
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(Trigger apiMonitorTrigger) {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setTriggers(apiMonitorTrigger);
        factory.setStartupDelay(30);
        factory.setOverwriteExistingJobs(true);
        // 集群配置 (需數據庫表)
        // factory.setDataSource(dataSource);
        // factory.setQuartzProperties(quartzProperties());
        return factory;
    }
}

2. 實現 Job 接口的任務類 (標準 Quartz 風格)

@Component
@DisallowConcurrentExecution // 禁止並發 (等同 concurrent=false)
public class ApiMonitorJob implements Job {
    private static final Logger log = LoggerFactory.getLogger(ApiMonitorJob.class);

    @Autowired
    private ApiMonitorService apiMonitorService; // 注入 Service 層

    @Override
    public void execute(JobExecutionContext context) {
        log.info("Quartz Job 執行:上游接口巡檢 - {}", LocalDateTime.now());
        try {
            apiMonitorService.verifyChannel();
        } catch (Exception e) {
            log.error("巡檢任務異常", e);
            // 可拋出 JobExecutionException 觸發重試/誤火策略
            throw new JobExecutionException(e);
        }
    }
}

3. 動態管理任務 (運行時增刪改查)

@Service
public class DynamicTaskService {
    @Autowired
    private Scheduler scheduler;

    /** 添加/更新任務 */
    public void addOrUpdateJob(String jobName, String jobGroup, String cron, Class jobClass) {
        try {
            JobDetail jobDetail = JobBuilder.newJob(jobClass)
                .withIdentity(jobName, jobGroup)
                .build();
            Trigger trigger = TriggerBuilder.newTrigger()
                .forJob(jobDetail)
                .withIdentity(jobName + "Trigger", jobGroup)
                .withSchedule(CronScheduleBuilder.cronSchedule(cron))
                .build();
            scheduler.scheduleJob(jobDetail, trigger); // 若存在則更新
        } catch (SchedulerException e) {
            throw new RuntimeException("任務調度失敗", e);
        }
    }

    /** 暫停/恢復/刪除 */
    public void pauseJob(String jobName, String jobGroup) throws SchedulerException {
        scheduler.pauseJob(JobKey.jobKey(jobName, jobGroup));
    }
    public void resumeJob(String jobName, String jobGroup) throws SchedulerException {
        scheduler.resumeJob(JobKey.jobKey(jobName, jobGroup));
    }
    public void deleteJob(String jobName, String jobGroup) throws SchedulerException {
        scheduler.deleteJob(JobKey.jobKey(jobName, jobGroup));
    }
}

方案三:持久化與集群 (生產必備)

1. Quartz 表結構 (官方提供 SQL)

-- 必須創建 11 張表:QRTZ_JOB_DETAILS, QRTZ_TRIGGERS, QRTZ_CRON_TRIGGERS, 
-- QRTZ_SIMPLE_TRIGGERS, QRTZ_BLOB_TRIGGERS, QRTZ_CALENDARS, QRTZ_PAUSED_TRIGGER_GRPS,
-- QRTZ_FIRED_TRIGGERS, QRTZ_SCHEDULER_STATE, QRTZ_LOCKS, QRTZ_SIMPROP_TRIGGERS

2. quartz.properties 關鍵配置

org.quartz.scheduler.instanceName = MyClusteredScheduler
org.quartz.scheduler.instanceId = AUTO
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 20
org.quartz.threadPool.threadPriority = 5

3. Spring Boot 整合持久化

@Configuration
public class QuartzClusterConfig {
    @Bean
    public Properties quartzProperties() {
        Properties props = new Properties();
        props.setProperty("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
        props.setProperty("org.quartz.jobStore.isClustered", "true");
        // ... 其他屬性
        return props;
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource, Properties quartzProperties) {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setDataSource(dataSource);
        factory.setQuartzProperties(quartzProperties);
        factory.setOverwriteExistingJobs(true);
        return factory;
    }
}

常見坑與避坑指南

坑點現象解決
任務方法有返回值MethodInvokingJobDetailFactoryBean 不支持返回值改用 Job 接口實現類,或返回 void
Cron 表達式錯誤啟動報錯 / 不按預期執行在線驗證工具測試、注意 6/7 位制 (Quartz 用 6 位:秒 分 時 日 月 周)
集群下重複執行多節點同時執行同一任務開啟 `isClustered=true`、配置共享數據庫、同步時間 (NTP)
誤火策略不生效錯過執行時間後不補償顯式設置 `misfireInstruction`、檢查 `misfireThreshold` (默認 60s)
Spring 事務不生效Job 類中 @Transactional 無效Job 非 Spring Bean 時需手動獲取 TransactionTemplate、或確保 @Component 掃描

Cron 表達式速查

格式:秒 分 時 日 月 周 (年可選)
*    任意值
,    列表值 (1,3,5)
-    範圍 (1-5)
/    步長 (0/30 = 每 30 秒)
?    不指定值 (日/周 二擇一)
L    最後一天/最後一個周X
W    工作日 (最近工作日)
#    第 N 個周X (6#3 = 第 3 個周五)

常用示例:
0 0 2 * * ?       每天 02:00
0 0/5 * * * ?     每 5 分鐘
0 0 10,14,18 * * ? 每天 10:00, 14:00, 18:00
0 0/30 9-17 * * MON-FRI 工作日 9-17 點每 30 分鐘
0 0 0 1 * ?       每月 1 號 00:00

總結

Spring + Quartz 定時任務演進路徑:

  1. XML 配置:理解核心組件關係 (JobDetail、Trigger、SchedulerFactoryBean)
  2. 註解驅動:Spring Boot 環境下簡化配置、支持依賴注入、動態管理
  3. 持久化集群:生產環境高可用、數據不丟失、防重複執行

核心原則:任務邏輯無狀態、冪等設計、異常捕獲不拋出、監控告警全覆蓋。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved