【Springboot】Quartz定时任务框架

一、实现流程:

  1. 导入依赖:

    <!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.3.2</version>
    </dependency>
    
  2. 定时任务-实现Job接口

    public class MyJob implements Job{
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException{
            //todo 定时任务代码逻辑
        }
    }
    
  3. 定时器工具

    public class QuartzJobUtil{
        private static SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        
        public static void add(String jobName, String jobGroupName, int time, Class <? extends Job> className){
            //创建任务
            JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();
            //创建触发器 每 time 秒钟执行一次
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(jobName, jobGroupName)
                   	.withSchedule(SimpleScheduleBuilder
                                      .simpleSchedule()
                                      .withIntervalInSeconds(time)
                                      .repeatForever())
                    .build();
            try {
                //获取实例化的 Scheduler。
                Scheduler scheduler = schedulerFactory.getScheduler();
                //将任务及其触发器放入调度器
                scheduler.scheduleJob(jobDetail, trigger);
                //调度器开始调度任务
                if (!scheduler.isShutdown()) {
                    scheduler.start();
                    //todo 启动任务
                }
            } catch (SchedulerException e) {
                e.printStackTrace();
            }
        }
        
        public void deleteJob(String jobName, String jobGroupName){
                try {
                    TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
                    Scheduler scheduler = schedulerFactory.getScheduler();
                    scheduler.pauseTrigger(triggerKey);// 停止触发器
                    scheduler.unscheduleJob(triggerKey);// 移除触发器
                    scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));
                    //todo 删除任务
                } catch (SchedulerException e) {
                    e.printStackTrace();
                }
            }
    }
    
  4. 使用定时器工具添加定时任务。

二、需要注意:

  1. 由于简易,没有持久化操作,重启系统后定时任务丢失。

  2. 每个定时任务启动后会立即执行一次,需要注意。

    延时启动方法:

    //QuartzJobUtil.class
     Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(jobName, jobGroupName)
         //date为Date类型参数,指到该时间后执行第一次定时任务。
         			.startAt(date)
         			.withSchedule(SimpleScheduleBuilder
                                      .simpleSchedule()
                                      .withIntervalInSeconds(time)
                                      .withRepeatCount(0))
                    .build();
    //SimpleScheduleBuilder相关API用法
    //无限制次数重复定时
    SimpleScheduleBuilder.repeatForever()
    //限制次数重复定时,countNum为定时次数
    SimpleScheduleBuilder..withRepeatCount(countNum)
    //每过time时间执行一次定时任务
    SimpleScheduleBuilder.withIntervalInSeconds(time)
    
  3. 实际上Quartz有默认的配置文件,你可以使用quartz.properties文件去设置Quartz的参数。默认的配置文件可能并不适用于大量的定时任务,因此需要根据项目需求调整quartz的参数。

  4. 工具型的写法虽然简单,但是无法使用Spring注入的变量,即使用@Autowired修饰的变量,因为是static修饰,所以需要将Spring注入的变量提前加载好。所以该写法适用于监控任务简单的场景。

    建议:使用@Component修饰工具类,不使用static修饰变量与方法,统一使用Spring注入的方式调用工具类。

三、更进一步

  1. 定时任务持久化

    • 添加持久化池依赖:

      <dependency>
          <groupId>com.mchange</groupId>
          <artifactId>c3p0</artifactId>
          <version>0.9.5.2</version>
      </dependency>
      
    • 编写配置文件quartz.properties。Quartz通过配置文件结合StdSchedulerFactory实现载入配置,StdSchedulerFactory默认加载工作目录下的quartz.properties文件,读取失败时会尝试加载org/quartz包下的quartz.properties文件。

      #quartz.properties
      # 实例化ThreadPool时,使用的线程类为SimpleThreadPool
      org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
      # threadCount和threadPriority将以setter的形式注入ThreadPool实例
      # 并发个数
      org.quartz.threadPool.threadCount = 5
      # 优先级
      org.quartz.threadPool.threadPriority = 5
      org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
      org.quartz.jobStore.misfireThreshold = 5000
      #持久化使用的类
      org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
      #数据库中表的前缀
      org.quartz.jobStore.tablePrefix = QRTZ_
      #数据源命名
      org.quartz.jobStore.dataSource = qzDS
      #qzDS 数据源
      org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver
      org.quartz.dataSource.qzDS.URL = jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=UTF-8
      org.quartz.dataSource.qzDS.user = root
      org.quartz.dataSource.qzDS.password = root
      org.quartz.dataSource.qzDS.maxConnections = 10
      
    • 自定义配置加载,创建QuartzConfig.java

      //QuartzConfig.java
      import org.quartz.Scheduler;
      import org.quartz.ee.servlet.QuartzInitializerListener;
      import org.springframework.beans.factory.config.PropertiesFactoryBean;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.core.io.ClassPathResource;
      import org.springframework.scheduling.quartz.SchedulerFactoryBean;
      
      import java.io.IOException;
      import java.util.Properties;
      
      /**
       * Qartz配置, 搭配quartz.properties文件自定义配置, 该处目的使定时任务持久化
       */
      @Configuration
      public class QuartzConfig {
      
          /**
           * 读取quartz.properties 文件
           * 将值初始化
           * @return
           */
          @Bean
          public Properties quartzProperties() throws IOException {
              PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
              propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
              propertiesFactoryBean.afterPropertiesSet();
              return propertiesFactoryBean.getObject();
          }
      
          /**
           * 将配置文件的数据加载到SchedulerFactoryBean中
           * @return
           * @throws IOException
           */
          @Bean
          public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
              SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
              schedulerFactoryBean.setQuartzProperties(quartzProperties());
              return schedulerFactoryBean;
          }
      
          /**
           * 初始化监听器
           * @return
           */
          @Bean
          public QuartzInitializerListener executorListener(){
              return new QuartzInitializerListener();
          }
      
          /**
           * 获得Scheduler 对象
           * @return
           * @throws IOException
           */
          @Bean
          public Scheduler scheduler() throws IOException {
              return schedulerFactoryBean().getScheduler();
          }
      }
      
    • 数据库创建默认表,从官网或者org/quartz/impl/jdbcjobstore包下找到对应数据库的SQL文件进行创建

      DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
      DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
      DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
      DROP TABLE IF EXISTS QRTZ_LOCKS;
      DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
      DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
      DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
      DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
      DROP TABLE IF EXISTS QRTZ_TRIGGERS;
      DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
      DROP TABLE IF EXISTS QRTZ_CALENDARS;
      
      CREATE TABLE QRTZ_JOB_DETAILS
        (
          SCHED_NAME VARCHAR(120) NOT NULL,
          JOB_NAME  VARCHAR(200) NOT NULL,
          JOB_GROUP VARCHAR(200) NOT NULL,
          DESCRIPTION VARCHAR(250) NULL,
          JOB_CLASS_NAME   VARCHAR(250) NOT NULL,
          IS_DURABLE VARCHAR(1) NOT NULL,
          IS_NONCONCURRENT VARCHAR(1) NOT NULL,
          IS_UPDATE_DATA VARCHAR(1) NOT NULL,
          REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
          JOB_DATA BLOB NULL,
          PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
      );
      
      CREATE TABLE QRTZ_TRIGGERS
        (
          SCHED_NAME VARCHAR(120) NOT NULL,
          TRIGGER_NAME VARCHAR(200) NOT NULL,
          TRIGGER_GROUP VARCHAR(200) NOT NULL,
          JOB_NAME  VARCHAR(200) NOT NULL,
          JOB_GROUP VARCHAR(200) NOT NULL,
          DESCRIPTION VARCHAR(250) NULL,
          NEXT_FIRE_TIME BIGINT(13) NULL,
          PREV_FIRE_TIME BIGINT(13) NULL,
          PRIORITY INTEGER NULL,
          TRIGGER_STATE VARCHAR(16) NOT NULL,
          TRIGGER_TYPE VARCHAR(8) NOT NULL,
          START_TIME BIGINT(13) NOT NULL,
          END_TIME BIGINT(13) NULL,
          CALENDAR_NAME VARCHAR(200) NULL,
          MISFIRE_INSTR SMALLINT(2) NULL,
          JOB_DATA BLOB NULL,
          PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
          FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
              REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
      );
      
      CREATE TABLE QRTZ_SIMPLE_TRIGGERS
        (
          SCHED_NAME VARCHAR(120) NOT NULL,
          TRIGGER_NAME VARCHAR(200) NOT NULL,
          TRIGGER_GROUP VARCHAR(200) NOT NULL,
          REPEAT_COUNT BIGINT(7) NOT NULL,
          REPEAT_INTERVAL BIGINT(12) NOT NULL,
          TIMES_TRIGGERED BIGINT(10) NOT NULL,
          PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
          FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
              REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
      );
      
      CREATE TABLE QRTZ_CRON_TRIGGERS
        (
          SCHED_NAME VARCHAR(120) NOT NULL,
          TRIGGER_NAME VARCHAR(200) NOT NULL,
          TRIGGER_GROUP VARCHAR(200) NOT NULL,
          CRON_EXPRESSION VARCHAR(200) NOT NULL,
          TIME_ZONE_ID VARCHAR(80),
          PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
          FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
              REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
      );
      
      CREATE TABLE QRTZ_SIMPROP_TRIGGERS
        (          
          SCHED_NAME VARCHAR(120) NOT NULL,
          TRIGGER_NAME VARCHAR(200) NOT NULL,
          TRIGGER_GROUP VARCHAR(200) NOT NULL,
          STR_PROP_1 VARCHAR(512) NULL,
          STR_PROP_2 VARCHAR(512) NULL,
          STR_PROP_3 VARCHAR(512) NULL,
          INT_PROP_1 INT NULL,
          INT_PROP_2 INT NULL,
          LONG_PROP_1 BIGINT NULL,
          LONG_PROP_2 BIGINT NULL,
          DEC_PROP_1 NUMERIC(13,4) NULL,
          DEC_PROP_2 NUMERIC(13,4) NULL,
          BOOL_PROP_1 VARCHAR(1) NULL,
          BOOL_PROP_2 VARCHAR(1) NULL,
          PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
          FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) 
          REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
      );
      
      CREATE TABLE QRTZ_BLOB_TRIGGERS
        (
          SCHED_NAME VARCHAR(120) NOT NULL,
          TRIGGER_NAME VARCHAR(200) NOT NULL,
          TRIGGER_GROUP VARCHAR(200) NOT NULL,
          BLOB_DATA BLOB NULL,
          PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
          FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
              REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
      );
      
      CREATE TABLE QRTZ_CALENDARS
        (
          SCHED_NAME VARCHAR(120) NOT NULL,
          CALENDAR_NAME  VARCHAR(200) NOT NULL,
          CALENDAR BLOB NOT NULL,
          PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
      );
      
      CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
        (
          SCHED_NAME VARCHAR(120) NOT NULL,
          TRIGGER_GROUP  VARCHAR(200) NOT NULL, 
          PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
      );
      
      CREATE TABLE QRTZ_FIRED_TRIGGERS
        (
          SCHED_NAME VARCHAR(120) NOT NULL,
          ENTRY_ID VARCHAR(95) NOT NULL,
          TRIGGER_NAME VARCHAR(200) NOT NULL,
          TRIGGER_GROUP VARCHAR(200) NOT NULL,
          INSTANCE_NAME VARCHAR(200) NOT NULL,
          FIRED_TIME BIGINT(13) NOT NULL,
          SCHED_TIME BIGINT(13) NOT NULL,
          PRIORITY INTEGER NOT NULL,
          STATE VARCHAR(16) NOT NULL,
          JOB_NAME VARCHAR(200) NULL,
          JOB_GROUP VARCHAR(200) NULL,
          IS_NONCONCURRENT VARCHAR(1) NULL,
          REQUESTS_RECOVERY VARCHAR(1) NULL,
          PRIMARY KEY (SCHED_NAME,ENTRY_ID)
      );
      
      CREATE TABLE QRTZ_SCHEDULER_STATE
        (
          SCHED_NAME VARCHAR(120) NOT NULL,
          INSTANCE_NAME VARCHAR(200) NOT NULL,
          LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
          CHECKIN_INTERVAL BIGINT(13) NOT NULL,
          PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
      );
      
      CREATE TABLE QRTZ_LOCKS
        (
          SCHED_NAME VARCHAR(120) NOT NULL,
          LOCK_NAME  VARCHAR(40) NOT NULL, 
          PRIMARY KEY (SCHED_NAME,LOCK_NAME)
      );
      commit;
      
  2. 解决直接实现Job接口导致@Autowired修饰的变量为空,并报空指针异常的情况。

    原因:Quartz的Job时由自己管理的,因此如果想要在Job中调用Spring管理的Bean,必须让Job也加入Spring容器当中。

    • 自定义JobFactory,使用Spring容器管理Quartz的Bean

      //参考https://www.cnblogs.com/huahua035/p/7839834.html
      //MyJobFactory.class
      import org.quartz.spi.TriggerFiredBundle;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
      import org.springframework.scheduling.quartz.AdaptableJobFactory;
      import org.springframework.stereotype.Component;
      
      /**
       * Description: 自定义JobFactory,使用Spring容器管理的Quartz的Bean(Job)
       * <p/>
       * AdaptableJobFactory是Spring提供的SchedulerFactoryBean的默认实例化工厂,将由直接实例化Job,没有被Spring管理
       */
      @Component
      public class MyJobFactory extends AdaptableJobFactory {
      
          /**
           * AutowireCapableBeanFactory接口是BeanFactory的子类
           * 可以连接和填充那些生命周期不被Spring管理的已存在的bean实例
           * 具体请参考:http://blog.csdn.net/iycynna_123/article/details/52993542
           */
          @Autowired
          private AutowireCapableBeanFactory capableBeanFactory;
      
          /**
           * 创建Job实例
           *
           * @param bundle
           * @return
           * @throws Exception
           */
          @Override
          protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
              // 实例化对象
              Object jobInstance = super.createJobInstance(bundle);
              // 进行注入(Spring管理该Bean),这是Spring进行注入的API,可以查看Spring官方API文档
              capableBeanFactory.autowireBean(jobInstance);
              //返回对象
              return jobInstance;
          }
      }
      
    • QuartzConfig.java配置文件中配置SchedulerFactoryBean

      //QuartzConfig.java
      @Autowired
      private MyJobFactory myJobFactory;
      
      @Bean //将一个方法产生为Bean并交给Spring容器管理(@Bean只能用在方法上)
      public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) throws IOException {
          //Spring提供SchedulerFactoryBean为Scheduler提供配置信息,并被Spring容器管理其生命周期
          SchedulerFactoryBean factory = new SchedulerFactoryBean();
          //启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
          factory.setOverwriteExistingJobs(true);
          // 延时启动(秒)
          factory.setStartupDelay(20);
          //设置quartz的配置文件
          factory.setQuartzProperties(quartzProperties());
          //设置自定义Job Factory,用于Spring管理Job bean----此处重点.
          factory.setJobFactory(myJobFactory);
          return factory;
      }
      
    • 编写Job任务

      //MyJob.java
      @Component
      public class MyJob extends QuartzJobBean {
      
          //使用Spring容器中的变量
          @Autowired
          private XxxService xxxService;
      
      
          /**
           * 执行Job
           *
           * @param jobExecutionContext
           * @throws JobExecutionException
           */
          @Override
          protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
             //todo Job任务逻辑
          }
      

其他知识:

  1. 以Job,Trigger,Scheduler为主要组成部分

  2. Trigger:

    • SimpleTrigger:在特定的时间点,或每隔T个时间单位调度一次

    • CronTrigger:基于日历的调度十分有用,比如每个月,每个星期等调度方式

      * * * * * * *cron表达式,从左至右代表秒,分,时,日,月,周,年,一般情况下忽略维度,因此6个参数

  3. 如何在Job中增加属性?

    • 因为每当scheduler执行Job时,会创建一个Job实例,调用execute()方法后会回收引用,因此无法在Job类中直接定义数据属性。
    • 使用JobDataMap,原理跟多线程中使用ThreadLocal类似。
    • 在执行Job时,从JobDataMap中取得数据。
    • 假如采取了持久化的方式,那么需要注意存储的数据对象的序列化方式。
  4. JobFactory:

    JobFactory

    当trigger触发时,通过Scheduler上配置的JobFactory实例化与之关联的jobs。默认的JobFactory只是在jobs类上调用newInstance()。您可能需要创建自己的JobFactory实现,以完成诸如让应用程序的IoC或DI容器生成/初始化jobs实例之类的操作。

    请参阅org.quartz.spi.JobFactory接口以及相关的Scheduler.setJobFactory(fact) 方法。

  5. 参考:
    Quartz文档:w3c
    第一部分内容参考:文章一

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值