定時任務框架Quartz基本使用


前言:

之前開發定時任務時,有兩種方式:

a、如果是SpringBoot項目,在方法上加上 @Scheduled 注解,然后開配置下cron就可以了。  缺點:不支持通過某種條件來開啟任務

b、使用 Executors.newScheduledThreadPool() 啟動一個定時線程。缺點:服務重啟或者任務失敗,線程就結束了

項目中使用了Quartz框架,很完美的解決了以上兩個問題。本文主要記錄Quartz框架的基本使用

 

上代碼:

以下配置是基於SpringBoot 2.1.0 + Quartz 2.3.0版本

1、pom.xml文件():

     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!--定時任務需要依賴context模塊 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
        </dependency>

2、application.yml配置文件

app:
  db:
    host: 127.0.01
    port: 3306
    dbname: xwj

server:
  port: 18090
  
spring:
  application:
    name: quarts-one
  datasource: 
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: net.sf.log4jdbc.DriverSpy
    url: jdbc:log4jdbc:mysql://${app.db.host}:${app.db.port}/${app.db.dbname}?autoReconnect=true&failOverReadOnly=false&createDatabaseIfNotExist=true&useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
    username: root
    password: 123456

logging:
  level:
    jdbc: off
    jdbc.sqltiming: error #記錄sql執行的時間
    #root: INFO
    com.xwj: debug

3、Quartz的配置文件 quartz.properties (發現只能用properties文件,如果用yml文件不生效)

org.quartz.scheduler.instanceName = instance_one
org.quartz.scheduler.instanceId = instance_id_one
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.useProperties = false
org.quartz.jobStore.clusterCheckinInterval = 20000
org.quartz.scheduler.idleWaitTime = 5000
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 20
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

4、Quartz的配置類

/**
 * Quartz配置類
 */
@Configuration
public class QuartzConfig {

    /**
     * 繼承org.springframework.scheduling.quartz.SpringBeanJobFactory 實現任務實例化方式
     */
    public static class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {

        private transient AutowireCapableBeanFactory beanFactory;

        @Override
        public void setApplicationContext(final ApplicationContext context) {
            beanFactory = context.getAutowireCapableBeanFactory();
        }

        /**
         * 將job實例交給spring ioc托管 我們在job實例實現類內可以直接使用spring注入的調用被spring ioc管理的實例
         */
        @Override
        protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
            final Object job = super.createJobInstance(bundle);
            // 將job實例交付給spring ioc
            beanFactory.autowireBean(job);
            return job;
        }
    }

    /**
     * 配置任務工廠實例
     */
    @Bean
    public JobFactory jobFactory(ApplicationContext applicationContext) {
        // 采用自定義任務工廠 整合spring實例來完成構建任務
        AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
        jobFactory.setApplicationContext(applicationContext);
        return jobFactory;
    }

    /**
     * 配置任務調度器 使用項目數據源作為quartz數據源
     *
     * @param jobFactory 自定義配置任務工廠
     * @param dataSource 數據源實例
     */
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, DataSource dataSource) throws Exception {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        // 將spring管理job自定義工廠交由調度器維護
        schedulerFactoryBean.setJobFactory(jobFactory);
        // 設置覆蓋已存在的任務
        schedulerFactoryBean.setOverwriteExistingJobs(true);
        // 項目啟動完成后,等待2秒后開始執行調度器初始化
        schedulerFactoryBean.setStartupDelay(2);
        // 設置調度器自動運行
        schedulerFactoryBean.setAutoStartup(true);
        // 設置數據源,使用與項目統一數據源
        schedulerFactoryBean.setDataSource(dataSource);
        // 設置上下文spring bean name
        schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext");
        // 設置配置文件位置
        schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties"));
        return schedulerFactoryBean;
    }

}

5、Quartz工具類

@Slf4j
@Component
public class MyQuartzScheduler {

    @Autowired
    private Scheduler scheduler; // 任務

    private final String JOB_NAME_PREFIX = "JOB_"; // 任務名稱前綴

    /**
     * 指定時間后執行任務(只會執行一次)
     * 
     * @param triggerStartTime 指定時間
     */
    @SneakyThrows
    public void addJob(Class<? extends Job> jobClass, String jobName, Date triggerStartTime, Map<String, Object> params) {
        // 使用job類名作為組名
        String groupName = jobClass.getSimpleName();

        // 創建任務觸發器
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, groupName).startAt(triggerStartTime).build();

        // 將觸發器與任務綁定到調度器內
        this.scheduleJob(jobClass, groupName, jobName, params, trigger);
    }

    /**
     * 帶觸發器的任務(執行多次)
     * 
     * @param cronExpression 定時任務表達式
     */
    @SneakyThrows
    public void addJobWithCron(Class<? extends Job> jobClass, String jobName, String cronExpression, Map<String, Object> params) {
        // 使用job類名作為組名
        String groupName = jobClass.getSimpleName();

        // 基於表達式構建觸發器
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(jobName, groupName).withSchedule(cronScheduleBuilder).build();

        // 將觸發器與任務綁定到調度器內
        this.scheduleJob(jobClass, groupName, jobName, params, cronTrigger);
    }

    /**
     * 帶觸發器的任務,同時指定時間段(立馬執行)
     * 
     * @param timeoutSeconds 超時時間(秒)
     * @param cronExpression 定時任務表達式
     */
    @SneakyThrows
    public void addJobWithCron(Class<? extends Job> jobClass, String jobName, String cronExpression, long timeoutSeconds, 
     Map<String, Object> params) { // 使用job類名作為組名 String groupName = jobClass.getSimpleName(); // 計算結束時間 Date endDate = TimeUtil.localDateTime2Date(LocalDateTime.now().plusSeconds(timeoutSeconds)); // 基於表達式構建觸發器,同時指定時間段 CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression); CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(jobName, groupName)
                    .startNow().endAt(endDate)
                    .withSchedule(cronScheduleBuilder).build();
// 將觸發器與任務綁定到調度器內 this.scheduleJob(jobClass, groupName, jobName, params, cronTrigger); } @SneakyThrows private void scheduleJob(Class<? extends Job> jobClass, String groupName, String jobName, Map<String, Object> params, Trigger trigger) { jobName = StringUtils.join(JOB_NAME_PREFIX, jobName); log.info("創建任務,任務名稱:{}", jobName); // 創建任務 JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, groupName).build(); // 添加參數 jobDetail.getJobDataMap().putAll(params); // 將觸發器與任務綁定到調度器內 scheduler.scheduleJob(jobDetail, trigger); } /** * 刪除某個任務 */ @SneakyThrows public boolean deleteJob(String name, String group) { JobKey jobKey = new JobKey(name, group); JobDetail jobDetail = scheduler.getJobDetail(jobKey); if (jobDetail == null) { throw new RuntimeException("任務不存在"); } return scheduler.deleteJob(jobKey); } /** * 修改某個任務的執行時間 */ @SneakyThrows public boolean modifyJob(String name, String group, String time) { Date date = null; TriggerKey triggerKey = new TriggerKey(name, group); CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey); String oldTime = cronTrigger.getCronExpression(); if (!oldTime.equalsIgnoreCase(time)) { CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(time); CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(name, group).withSchedule(cronScheduleBuilder).build(); date = scheduler.rescheduleJob(triggerKey, trigger); } return date != null; } /** * 獲取任務狀態 */ @SneakyThrows public TriggerState getJobState(String name, String group) { TriggerKey triggerKey = TriggerKey.triggerKey(name, group); return scheduler.getTriggerState(triggerKey); } /** * 獲取任務狀態 */ @SneakyThrows public TriggerState getJobState(TriggerKey triggerKey) { return scheduler.getTriggerState(triggerKey); } /** * 暫停所有任務 */ @SneakyThrows public void pauseAllJob() { scheduler.pauseAll(); } /** * 暫停某個任務 */ @SneakyThrows public void pauseJob(String name, String group) { JobKey jobKey = new JobKey(name, group); JobDetail jobDetail = scheduler.getJobDetail(jobKey); if (jobDetail == null) { throw new RuntimeException("任務不存在"); } scheduler.pauseJob(jobKey); } /** * 恢復所有任務 */ @SneakyThrows public void resumeAllJob() { scheduler.resumeAll(); } /** * 恢復某個任務 */ @SneakyThrows public void resumeJob(String name, String group) { JobKey jobKey = new JobKey(name, group); JobDetail jobDetail = scheduler.getJobDetail(jobKey); if (jobDetail == null) { throw new RuntimeException("任務不存在"); } scheduler.resumeJob(jobKey); }/** * 通過group查詢有多少個運行的任務 */ @SneakyThrows public long getRunningJobCountByGroup(Class<? extends Job> jobClass) { String groupName = jobClass.getSimpleName(); GroupMatcher<JobKey> matcher = GroupMatcher.jobGroupEquals(groupName); Set<JobKey> jobKeySet = scheduler.getJobKeys(matcher); if (CollectionUtils.isNotEmpty(jobKeySet)) { return jobKeySet.stream().filter(d -> StringUtils.equals(d.getGroup(), groupName)).count(); } return 0; } }

6、新建一個job任務

@Slf4j
public class MyJob extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        JobDetail jobDetail = context.getJobDetail();
        JobKey jobKey = jobDetail.getKey();
        JobDataMap dataMap = jobDetail.getJobDataMap(); // 接收參數
        log.info("執行MyJob任務,任務名稱:{},接收參數:{}", jobKey.getName(), dataMap.getString("id"));
    }

}

7、新建一個Controller測試類

@RestController
@RequestMapping("/quartz")
public class QuartzApiController {

    @Autowired
    private MyQuartzScheduler quartzScheduler;

    /**
     * 指定時間點觸發的任務()
     */
    @RequestMapping("/job/start/{id}")
    public void startQuartzJob(@PathVariable String id) {
        // 20s之后執行
        LocalDateTime ldt = LocalDateTime.now();
        Date date = TimeUtil.localDateTime2Date(ldt.plusSeconds(20));

        Map<String, Object> params = new HashMap<String, Object>();
        params.put("id", id);
        quartzScheduler.addJob(MyJob.class, id, date, params);
    }

    /**
     * 定時任務
     */
    @RequestMapping("/job/cron/{id}")
    public void cronQuartzJob(@PathVariable String id) {
        Map<String, Object> params = new HashMap<>();
        params.put("id", id);
        // 每10秒執行一次
        quartzScheduler.addJobWithCron(MyJob.class, id, "0/10 * * * * ?", params);
    }

    /**
     * 刪除某個任務
     */
    @RequestMapping(value = "/job/delete")
    public boolean deleteJob(String name, String group) {
        return quartzScheduler.deleteJob(name, group);
    }

    /**
     * 修改任務執行時間
     */
    @RequestMapping("/job/modify")
    public boolean modifyQuartzJob(String name, String group, String time) {
        return quartzScheduler.modifyJob(name, group, time);
    }

    /**
     * 暫停某個任務
     */
    @RequestMapping(value = "/job/pause")
    public void pauseQuartzJob(String name, String group) {
        quartzScheduler.pauseJob(name, group);
    }

    /**
     * 暫停所有任務
     */
    @RequestMapping(value = "/job/pauseAll")
    public void pauseAllQuartzJob() {
        quartzScheduler.pauseAllJob();
    }

}

8、在數據庫中執行新建quartz相關表的sql (腳本太長,可自己百度,網上一大堆)

9、啟動SpringBoot服務,控制台可以看到Quartz相關的日志信息(表示Quartz配置成功):

打開數據庫中的 qrtz_scheduler_state 表,會發現表中多了一條數據(SCHED_NAME和INSTANCE_NAME 分別是quartz.properties配置文件中的instanceName和instanceId):

 10、在瀏覽器請求 http://localhost:18090/quartz/job/start/123,在控制台會看到如下日志:

2020-12-11 21:22:03.635  INFO 13052 --- [io-18090-exec-6] c.x.q.MyQuartzScheduler                  : 創建任務,任務名稱:JOB_123

同時在表 qrtz_job_details 和 qrtz_triggers 中會分別插入一條數據,表示該任務的詳細信息

過了20秒之后(上面配置的定時任務是20秒之后執行),可以看到又打印出一條日志:

2020-12-11 21:22:23.667  INFO 13052 --- [ce_one_Worker-2] c.x.q.j.MyJob                            : 執行MyJob任務,任務名稱:JOB_123,接收參數:123

表示任務已經成功執行,並且表 qrtz_job_details 和 qrtz_triggers 的任務信息已經被刪除

至此Quartz配置和測試完成,其它復雜的測試,可請求QuartzApiController的方法自行操作

 

擴展:

使用SpringBoot封裝好的Quartz,會比上面的方式簡單一些,但是使用方法跟上面一模一樣。需要修改的點包括:

1、不使用 quartz.properties 配置文件和 QuartzConfig 配置類,直接在application.yml文件中增加Quartz配置:

spring:
  quartz:  #quartz相關屬性配置
    properties:
      org:
        quartz:
          scheduler:
            instanceName: instance_one  #調度器實例名稱
            #instanceId: AUTO     #調度器實例編號自動生成
            instanceId: instance_id_one  #調度器實例id
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX  #持久化方式配置
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate  #持久化方式配置數據驅動,MySQL數據庫
            tablePrefix: QRTZ_   #quartz相關數據表前綴名
            isClustered: true    #開啟分布式部署
            clusterCheckinInterval: 10000   #分布式節點有效性檢查時間間隔,單位:毫秒
            useProperties: false   #配置是否使用
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool   #線程池實現類
            threadCount: 10   #執行最大並發線程數量
            threadPriority: 5  #線程優先級
            threadsInheritContextClassLoaderOfInitializingThread: true   #配置是否啟動自動加載數據庫內的定時任務,默認true
    #數據庫方式
    job-store-type: jdbc

 

 

Quartz詳細說明:Quartz官方中文文檔

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM