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

Java 定時任務實戰:Quartz 整合 Spring 與動態管理器設計

     編輯:Java 相關知識

Quartz 核心概念速覽

組件職責
Job任務邏輯執行單元,實現 Job 介面
JobDetailJob 定義元數據(名稱、組、數據映射)
Trigger觸發條件:SimpleTrigger(間隔)、CronTrigger(Cron 表達式)
Scheduler調度器核心,管理 Job/Trigger 生命週期
SchedulerFactory創建 Scheduler,StdSchedulerFactory 為標準實現

Spring XML 整合配置(傳統方式)

<!-- 1. JobDetail 定義 -->
<bean id="testJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
    <property name="jobClass" value="com.example.TestQuartzJob"/>
    <property name="jobDataAsMap">
        <map>
            <entry key="testService" value-ref="testServiceImpl"/>
        </map>
    </property>
</bean>

<!-- 2. CronTrigger 定義 -->
<bean id="testTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail" ref="testJobDetail"/>
    <property name="cronExpression" value="0 0/1 * * * ?"/>  <!-- 每分鐘 -->
</bean>

<!-- 3. SchedulerFactoryBean 組裝 -->
<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list><ref bean="testTrigger"/></list>
    </property>
    <property name="overwriteExistingJobs" value="true"/>
    <property name="autoStartup" value="true"/>
</bean>

Job 實現類

public class TestQuartzJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        // 從 JobDataMap 獲取 Spring Bean
        TestService testService = (TestService) context.getJobDetail()
            .getJobDataMap().get("testService");
        testService.executeBusinessLogic();
    }
}

優化:直接使用 @Autowired 注入 Spring Bean,需 Job 類交由 Spring 管理(@Component + @Configurable 或 Spring 3.1+ SpringBeanJobFactory)。

進階場景:Cron 表達式從數據庫動態讀取

方案:繼承 CronTriggerFactoryBean

public class DynamicCronTriggerFactoryBean extends CronTriggerFactoryBean {
    @Autowired private SysParamService sysParamService;
    private String paramKey;

    public void setParamKey(String paramKey) { this.paramKey = paramKey; }

    @PostConstruct
    public void initCronExpression() {
        setCronExpression(fetchCronFromDB());
    }

    private String fetchCronFromDB() {
        if (StringUtils.isEmpty(paramKey)) return "0 0/5 * * * ?";  // 預設 5 分鐘
        try {
            SysParam param = sysParamService.getByKey(paramKey);
            return (param != null && StringUtils.isNotBlank(param.getValue())) 
                ? param.getValue() : "0 0/5 * * * ?";
        } catch (Exception e) {
            return "0 0/5 * * * ?";
        }
    }
}

XML 配置動態 Trigger

<bean id="dynamicTrigger" class="com.example.DynamicCronTriggerFactoryBean">
    <property name="jobDetail" ref="testJobDetail"/>
    <property name="paramKey" value="quartz.cron.testJob"/>
    <property name="sysParamService" ref="sysParamService"/>
</bean>

動態任務管理器:運行時增刪改查

基於 Scheduler API 封裝通用管理器:

@Component
public class QuartzManager {
    private static final Logger log = LoggerFactory.getLogger(QuartzManager.class);
    private final Scheduler scheduler;

    public QuartzManager(Scheduler scheduler) { this.scheduler = scheduler; }

    /** 新增任務 */
    public void addJob(String jobName, String jobGroup, Class<? extends Job> jobClass, 
                       String cronExpression, Map<String, Object> jobData) {
        try {
            JobDetail jobDetail = JobBuilder.newJob(jobClass)
                .withIdentity(jobName, jobGroup)
                .usingJobData(new JobDataMap(jobData))
                .build();

            Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(jobName + "Trigger", jobGroup)
                .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
                .build();

            scheduler.scheduleJob(jobDetail, trigger);
            if (!scheduler.isStarted()) scheduler.start();
            log.info("任務新增成功: {}.{}", jobGroup, jobName);
        } catch (SchedulerException e) {
            throw new RuntimeException("新增任務失敗", e);
        }
    }

    /** 移除任務 */
    public void removeJob(String jobName, String jobGroup) {
        try {
            JobKey jobKey = new JobKey(jobName, jobGroup);
            scheduler.deleteJob(jobKey);
            log.info("任務移除成功: {}.{}", jobGroup, jobName);
        } catch (SchedulerException e) {
            throw new RuntimeException("移除任務失敗", e);
        }
    }

    /** 修改 Cron 表達式 */
    public void rescheduleJob(String jobName, String jobGroup, String newCron) {
        try {
            TriggerKey triggerKey = new TriggerKey(jobName + "Trigger", jobGroup);
            Trigger newTrigger = TriggerBuilder.newTrigger()
                .withIdentity(triggerKey)
                .withSchedule(CronScheduleBuilder.cronSchedule(newCron))
                .build();
            scheduler.rescheduleJob(triggerKey, newTrigger);
            log.info("任務重新調度: {}.{} -> {}", jobGroup, jobName, newCron);
        } catch (SchedulerException e) {
            throw new RuntimeException("重新調度失敗", e);
        }
    }

    /** 暫停/恢復任務 */
    public void pauseJob(String jobName, String jobGroup) throws SchedulerException {
        scheduler.pauseJob(new JobKey(jobName, jobGroup));
    }
    public void resumeJob(String jobName, String jobGroup) throws SchedulerException {
        scheduler.resumeJob(new JobKey(jobName, jobGroup));
    }

    /** 查詢所有任務狀態 */
    public List<JobInfo> listJobs() throws SchedulerException {
        List<JobInfo> result = new ArrayList<>();
        for (String groupName : scheduler.getJobGroupNames()) {
            for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
                List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
                for (Trigger trigger : triggers) {
                    Trigger.TriggerState state = scheduler.getTriggerState(trigger.getKey());
                    result.add(new JobInfo(
                        jobKey.getName(), jobKey.getGroup(),
                        trigger.getKey().getName(),
                        trigger instanceof CronTrigger ? ((CronTrigger) trigger).getCronExpression() : "N/A",
                        state.name(),
                        trigger.getNextFireTime(),
                        trigger.getPreviousFireTime()
                    ));
                }
            }
        }
        return result;
    }

    @Data @AllArgsConstructor
    public static class JobInfo {
        private String jobName, jobGroup, triggerName, cronExpression, state;
        private Date nextFireTime, prevFireTime;
    }
}

REST API 暴露管理功能

@RestController
@RequestMapping("/api/quartz")
@RequiredArgsConstructor
public class QuartzController {
    private final QuartzManager quartzManager;

    @PostMapping("/jobs")
    public Result addJob(@RequestBody JobRequest req) {
        quartzManager.addJob(req.getJobName(), req.getJobGroup(), req.getJobClass(), 
                           req.getCron(), req.getJobData());
        return Result.success();
    }

    @DeleteMapping("/jobs/{group}/{name}")
    public Result removeJob(@PathVariable String group, @PathVariable String name) {
        quartzManager.removeJob(name, group);
        return Result.success();
    }

    @PutMapping("/jobs/{group}/{name}/cron")
    public Result reschedule(@PathVariable String group, @PathVariable String name, 
                             @RequestParam String cron) {
        quartzManager.rescheduleJob(name, group, cron);
        return Result.success();
    }

    @GetMapping("/jobs")
    public Result listJobs() throws SchedulerException {
        return Result.success(quartzManager.listJobs());
    }

    @PostMapping("/jobs/{group}/{name}/pause")
    public Result pause(@PathVariable String group, @PathVariable String name) throws SchedulerException {
        quartzManager.pauseJob(name, group);
        return Result.success();
    }

    @PostMapping("/jobs/{group}/{name}/resume")
    public Result resume(@PathVariable String group, @PathVariable String name) throws SchedulerException {
        quartzManager.resumeJob(name, group);
        return Result.success();
    }
}

持久化與集群化配置(生產必備)

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.dataSource = myDS
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.dataSource.myDS.driver = com.mysql.cj.jdbc.Driver
org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/quartz?useSSL=false
org.quartz.dataSource.myDS.user = quartz
org.quartz.dataSource.myDS.password = quartz
org.quartz.dataSource.myDS.maxConnections = 20

Spring 整合數據源

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

@Bean
public Properties quartzProperties() {
    PropertiesFactoryBean pf = new PropertiesFactoryBean();
    pf.setLocation(new ClassPathResource("quartz.properties"));
    return pf.getObject();
}

常見坑點與最佳實踐

問題解決方案
任務重複執行集群模式下確保 isClustered=true、同一數據源、實例 ID 自動生成
Spring Bean 注入失效使用 SpringBeanJobFactory 或 @Autowired 配合 @Configurable
Cron 表達式錯誤使用 CronExpression.isValidExpression() 啟動前校驗
任務執行時間過長阻塞線程池調大 threadCount、異步執行業務邏輯、設置 misfireInstruction
數據庫表未建立執行 Quartz 官方 SQL 腳本(tables_mysql.sql 等)
Misfire 處理配置 MISFIRE_INSTRUCTION_FIRE_ONCE_NOW 或 DO_NOTHING

Spring Boot 2.x+ 簡化配置

@Configuration
@EnableScheduling
public class QuartzConfig {
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setDataSource(dataSource);
        factory.setWaitForJobsToCompleteOnShutdown(true);
        return factory;
    }
}

配合 spring-boot-starter-quartz 依賴,自動建表、自動配置數據源。

總結

Quartz 企業級定時任務解決方案完整鏈路:

  1. 靜態定義:Spring XML/Java Config 宣告 Job/Trigger/Scheduler
  2. 動態管理:QuartzManager 封裝 Scheduler API,支援運行時增刪改查
  3. 持久化集群:JDBC JobStore + 數據庫鎖,保證高可用、不重複執行
  4. 監控運維:REST API + 管理界面,可視化任務狀態、手動觸發、日誌追蹤

核心原則:業務邏輯與調度解耦、狀態持久化、集群安全、變更可追溯。

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