定时任务框架Quartz的使用

一、什么是Quartz?

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:

  • 持久性作业 - 就是保持调度定时的状态;
  • 作业管理 - 对调度作业进行有效的管理;

二、java定时任务调度的实现方式

(1)Timer

特点是:简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务;能实现简单的定时任务,稍微复杂点(或要求高一些)的定时任务却不好实现。

(2)ScheduledExecutor

鉴于Timer的缺陷,Java 5推出了基于线程池设计的ScheduledExecutor;
特点:每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发执行的,相互之间不会受到干扰。需要注意的是,只有当任务的执行时间到来时,ScheduedExecutor 才会真正启动一个线程,其余时间 ScheduledExecutor 都是在轮询任务的状态。
虽然用ScheduledExecutor和Calendar能够实现复杂任务调度,但实现起来还是比较麻烦,对开发还是不够友善。

(3)Spring Scheduler

spring对任务调度的实现支持,可以指定任务的执行时间,但对任务队列和线程池的管控较弱;一般集成于项目中,小任务很方便。

(4)开源工具包 JCronTab

JCronTab则是一款完全按照crontab语法编写的java任务调度工具。
特点:

  • 可指定任务的执行时间;
  • 提供完全按照Unix的UNIX-POSIX crontab的格式来规定时间;
  • 支持多种任务调度的持久化方法,包括普通文件、数据库以及 XML 文件进行持久化;
  • JCronTab内置了发邮件功能,可以将任务执行结果方便地发送给需要被通知的人;
  • 设计和部署是高性能并可扩展。

(5)开源工具包 Quartz

  • 具有强大的调度功能,很容易与spring集成,形成灵活可配置的调度功能;
  • 调度环境的持久化机制:可以保存并恢复调度现场,即使系统因为故障关闭,任务调度现场的数据并不会丢失;timer没有这些特点;
  • 灵活的应用方式:可以灵活的定义触发器调度的时间表,并可以对触发器与任务进行关联映射;
  • 分布式与集群能力;

三、quartz的相关概念

Scheduler:调度器,进行任务调度;quartz的大脑
Job:业务job,亦可称业务组件;定时任务的具体执行业务需要实现此接口,调度器会调用此接口的execute方法完成我们的定时业务
JobDetail:用来定义业务Job的实例,我们可以称之为quartz job,很多时候我们谈到的job指的是JobDetail
Trigger:触发器,用来定义一个指定的Job何时被执行
JobBuilder:Job构建器,用来定义或创建JobDetail的实例;JobDetail限定了只能是Job的实例
TriggerBuilder:触发器构建器,用来定义或创建触发器的实例

四、如何在springboot中集成Quartz

(1)在pom.xml中添加依赖

 <!--quartz依赖-->
 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

(2)appliaction.yml中添加配置(不可用,不知为何……问了同事,只说quartz官方推荐quartz.properties的配置方式)

spring:
  quartz:
      # 将任务等保存化到数据库
      job-store-type: jdbc
      # 程序结束时会等待quartz相关的内容结束
      wait-for-jobs-to-complete-on-shutdown: true
      # QuartzScheduler启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录
      overwrite-existing-jobs: true
      # 这里居然是个map,搞得智能提示都没有,佛了
      properties:
        org:
          quartz:
          	# scheduler相关
            scheduler:
              # scheduler的实例名
              instanceName: scheduler
              instanceId: AUTO
            # 持久化相关
            jobStore:
              class: org.quartz.impl.jdbcjobstore.JobStoreTX
              driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
              # 表示数据库中相关表是QRTZ_开头的
              tablePrefix: QRTZ_
              useProperties: false
            # 线程池相关
            threadPool:
              class: org.quartz.simpl.SimpleThreadPool
              # 线程数
              threadCount: 10
              # 线程优先级
              threadPriority: 5
              threadsInheritContextClassLoaderOfInitializingThread: true

(2')添加配置quartz.properties

#quartz集群配置
# ===========================================================================
# Configure Main Scheduler Properties 调度器属性
# ===========================================================================
#调度标识名 集群中每一个实例都必须使用相同的名称
org.quartz.scheduler.instanceName=DefaultQuartzScheduler
#ID设置为自动获取 每一个必须不同
org.quartz.scheduler.instanceid=AUTO
#============================================================================
# Configure ThreadPool
#============================================================================
#线程池的实现类(一般使用SimpleThreadPool即可满足几乎所有用户的需求)
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
#指定线程数,至少为1(无默认值)(一般设置为1-100直接的整数合适)
org.quartz.threadPool.threadCount = 25
#设置线程的优先级(最大为java.lang.Thread.MAX_PRIORITY 10,最小为Thread.MIN_PRIORITY 1,默认为5)
org.quartz.threadPool.threadPriority = 5
#============================================================================
# Configure JobStore
#============================================================================
# 信息保存时间 默认值60秒
org.quartz.jobStore.misfireThreshold = 60000
#数据保存方式为数据库持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#数据库代理类,一般org.quartz.impl.jdbcjobstore.StdJDBCDelegate可以满足大部分数据库
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
#JobDataMaps是否都为String类型
org.quartz.jobStore.useProperties = true
#数据库别名 随便取
org.quartz.jobStore.dataSource = myDS
#表的前缀,默认QRTZ_
org.quartz.jobStore.tablePrefix = QRTZ_
#是否加入集群
org.quartz.jobStore.isClustered = true
#调度实例失效的检查时间间隔
org.quartz.jobStore.clusterCheckinInterval = 50000
#============================================================================
# Configure Datasources
#============================================================================
#数据库引擎
#org.quartz.dataSource.myDS.driver = org.postgresql.Driver
##数据库连接
#org.quartz.dataSource.myDS.URL = jdbc:postgresql://20.20.1.205:5432/quartz
##数据库用户
#org.quartz.dataSource.myDS.user = postgres
##数据库密码
#org.quartz.dataSource.myDS.password = passwd
##允许最大连接
#org.quartz.dataSource.myDS.maxConnections = 5
##验证查询sql,可以不设置
#org.quartz.dataSource.myDS.validationQuery=select 0 from dual

(3) 直接上代码

这次我们写的高级些,将调度任务持久化到数据库,对前端暴露接口,通过接口来操作定时任务,更加灵活且便于管理。
https://blog.csdn.net/qq_37339399/article/details/108141744
(i)新建Quartz相关的数据库表,SQL在哪里找?
依赖包中找到Maven:org.quartz-scheduler:quartz:2.3.0 ==> org ==> quartz ==> impl ==> jdbcjobstore 文件夹下对应各个方言的sql文件,找到合适的就可以了

各个表的含义:
qrtz_fired_triggers; ------ 存储以触发的Trigger相关的状态信息,以及关联的JOb信息
qrtz_paused_trigger_grps; ------ 存储已暂停的Trigger组的信息
qrtz_scheduler_state; ------ 存储少量的有关Scheduler的状态信息
qrtz_locks; ------ 存储程序的悲观锁信息
qrtz_simple_triggers; ------ 存储simpleTrigger,包括重复次数,间隔,以及已执行次数
qrtz_simprop_triggers; ------ 存储CalendarIntervalTrigger等其他类的trigger
qrtz_cron_triggers; ------ 存储cronTrigger的基本信息,包括表达式,时区
qrtz_blob_triggers; ------ 将Trigger作为Blob类型存储,用于用户自定义Trigger类型
qrtz_triggers; ------ 存储已配置trigger的信息
qrtz_job_details; ------ 存储每一个已配置的Job的详细信息
qrtz_calendars; ------ 以Blob形式存储Quartz的calendar信息
注:Blob数据类型:是一个变长的二进制字符串(二进制大对象)

(ii)项目启动时加载

/**
 * Quartz的相关配置,注册JobDetail和Trigger
 * 注意JobDetail和Trigger是org.quartz包下的,不是spring包下的,不要导入错误
 */
@Component
public class QuartzConfig {
   /**
     * 将job的实例化交给IOC去进行
     */
    public static class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
        private transient AutowireCapableBeanFactory beanFactory;
        @Override
        public void setApplicationContext(final ApplicationContext context) {
           beanFactory = context.getAutowireCapableBeanFactory();
        }
        protected Object creatJobInstance(final TriggerFiredBundle bundle) throws Exception {
           final Object job = super.createJobInstance(bundle);
           //进行注入
           beanFactory.autowireBean(job);
           return job;
         }
     }
        //从quartz.properties文件中读取Quartz配置属性
        @Bean
        public Properties quartzProperties() throws IOException {
            PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
            propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
            propertiesFactoryBean.afterPropertiesSet();
            return propertiesFactoryBean.getObject();
        }
     /**
       * 配置JobFactory
       * @param applicationContext
       * @return
       */
      @Bean
      public JobFactory jobFactory(ApplicationContext applicationContext) {
          AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
          jobFactory.setApplicationContext(applicationContext);
          return jobFactory;
      }
     /**
       * 自定义配置任务工厂
       * @param jobFactory
       * @param dataSource
       * @return
       * @throws Exception
       */
      @Bean
      public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, DataSource dataSource) throws  Exception {
          SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
          schedulerFactoryBean.setJobFactory(jobFactory);
          //设置覆盖已存在的任务
          schedulerFactoryBean.setOverwriteExistingJobs(true);
          //schedulerFactoryBean.setStartupDelay(2);
          //设置数据源,使用与项目统一数据源
          schedulerFactoryBean.setDataSource(dataSource);
          //设置上下文spring bean name
          schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext");
          //设置项目启动时开始任务
          schedulerFactoryBean.setAutoStartup(true);
          schedulerFactoryBean.setQuartzProperties(quartzProperties());
          return schedulerFactoryBean;
       }
}

(iii)controller层

@RestController
public class QuartzController {
    @Autowired
    private QuartzService quartzService;
   /**
     * 新增定时任务
     * @param jobDTO
     */ 
     @RequestMapping("/qz/addJob")
     public void addJob(@RequestBody JobDTO jobDTO) {
         quartzService.addJob(jobDTO);
     }
}

入参实体:

@Data
public class JobDTO {
    private String jobId;
    private String jobName;
    private String groupName;
    private String cron;
    private String description;
    private String status;
    private String parameter;
    private String type;
    private Class T;
    private String jobClass;
}

(iv)service层

public interface QuartzService {
    void addJob(@RequestBody JobDTO jobDTO);
}

(v)serviceImpl层

@Slf4j
@Service
public class QuartzServiceImpl implements QuartzService {
    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;
    @Override
    public void addJob(JobDTO jobDTO) {
    try {
        // 获取调度器
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        JobDetail jobDetail = JobBuilder.newJob(DynamicJob.class).withIdentity(jobDTO.getJobName(), jobDTO.getGroupName())
                                                             //  .setJobData(castMap(jobDTO))
                                                                 .build();
        // 基于表达式构建触发器
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobDTO.getCron());
        // TriggerBuilder 用于构建触发器实例
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(jobDTO.getJobName(), jobDTO.getGroupName())
       .withSchedule(cronScheduleBuilder).build();

       //调度任务
       scheduler.scheduleJob(jobDetail, cronTrigger);
       scheduler.start();
       } catch (SchedulerException e) {
            e.printStackTrace();
       }
    }
}

(vi)创建个DynamicJob类实现Job接口,来写需要的定时业务逻辑

@Slf4j
@DisallowConcurrentExecution
public class DynamicJob implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    // 根据不同的type 执行不同任务
    //JobDetail中的JobDataMap是共用的,从getMergedJobDataMap获取的JobDataMap是全新的对象
    JobDataMap map = jobExecutionContext.getMergedJobDataMap();
    String type = SpringUtil.objectToString(map.get("type"));
       switch (type){
          case "one":
                this.doOne(map);
                break;
          case "two":
                this.doTwo(map);
                break;
          case "three":
                this.doThree(map);
                break;
          default:
                log.info("nothing todo");
                break;
        }
     }
     private void doOne(JobDataMap map){
        log.info("do one,description:{},jobId:{}",map.get("jobDescription"),map.get("jobId"));
     }
     private void doTwo(JobDataMap map){
        log.info("do two,description:{},jobId:{}",map.get("jobDescription"),map.get("jobId"));
     }
     private void doThree(JobDataMap map){
         log.info("do three,description:{},jobId:{}",map.get("jobDescription"),map.get("jobId"));
     }
}

(vii)使用postman调用添加job接口

localhost:8070/qz/addJob

入参格式:

 {  
    "jobId":"26",
    "jobName":"job名称26",
    "cron": "*/5 * * * * ?",
    "type":"one"
  }

可以在控制台看到每五秒钟打印一条定时日志,且入参的job信息已自动添加到数据库quartz相关表中。

原文地址:https://www.cnblogs.com/zyzyBlog/p/13754321.html