Quartz (3) 常见api解释

Trigger

Trigger是一个接口,字面意思触发器 - 定义执行给定作业的计划的组件。有2个常见的实现类 SimpleTriggerCronTrigger

//v2.23
public abstract class AbstractTrigger ...extends Trigger {  //间接继承Trigger 
  private transient TriggerKey key = null;  //Trigger 标识
}
  
public class CronTriggerImpl extends AbstractTrigger  {
  private Date startTime = null;   //属性
  private Date endTime = null;     //属性

Trigger的公共属性

所有类型的trigger都有TriggerKey这个属性,表示trigger的身份;除此之外,trigger还有很多其它的公共属性。这些属性,在构建trigger的时候可以通过TriggerBuilder设置。

trigger的公共属性有:

  • jobKey属性:
    当trigger触发时被执行的job的身份;
  • startTime属性:
    设置trigger第一次触发的时间;该属性的值是java.util.Date类型,表示某个指定的时间点;有些类型的trigger,会在设置的startTime时立即触发,有些类型的trigger,表示其触发是在startTime之后开始生效。比如,现在是1月份,你设置了一个trigger–“在每个月的第5天执行”,然后你将startTime属性设置为4月1号,则该trigger第一次触发会是在几个月以后了(即4月5号)。
  • endTime属性:
    表示trigger失效的时间点。比如,”每月第5天执行”的trigger,如果其endTime是7月1号,则其最后一次执行时间是6月5号。

优先级(priority)

如果你的trigger很多(或者Quartz线程池的工作线程太少),Quartz可能没有足够的资源同时触发所有的trigger;这种情况下,你可能希望控制哪些trigger优先使用Quartz的工作线程,要达到该目的,可以在trigger上设置priority属性。比如,你有N个trigger需要同时触发,但只有Z个工作线程,优先级最高的Z个trigger会被首先触发。如果没有为trigger设置优先级,trigger使用默认优先级,值为5;priority属性的值可以是任意整数,正数、负数都可以。

注意:只有同时触发的trigger之间才会比较优先级。10:59触发的trigger总是在11:00触发的trigger之前执行。

注意:如果trigger是可恢复的,在恢复后再调度时,优先级与原trigger是一样的。

错过触发(misfire Instructions)

trigger还有一个重要的属性misfire;如果scheduler关闭了,或者Quartz线程池中没有可用的线程来执行job,此时持久性的trigger就会错过(miss)其触发时间,即错过触发(misfire)。不同类型的trigger,有不同的misfire机制。它们默认都使用“智能机制(smart policy)”,即根据trigger的类型和配置动态调整行为。当scheduler启动的时候,查询所有错过触发(misfire)的持久性trigger。然后根据它们各自的misfire机制更新trigger的信息。当你在项目中使用Quartz时,你应该对各种类型的trigger的misfire机制都比较熟悉,这些misfire机制在JavaDoc中有说明。关于misfire机制的细节,会在讲到具体的trigger时作介绍。

TriggerBuilder

用来创建Trigger实例

withIdentity()

设置TriggerKey这个成员属性。

语法:

  • withIdentity(String name, String group)
    指定TriggerKey的name和group的名称
  • withIdentity(String name)
    指定TriggerKey的name,没指定group
    当没指定group时,构建TriggerKey时,会设置为默认值“DEFAULT”

withIdentity()方法非必调用,系统内会自动设置name和group属性,name为UUID格式字符串,group会设置为默认值“DEFAULT”

源码:

//v2.2.3
public class TriggerBuilder{
   public TriggerBuilder<T> withIdentity(String name, String group) {
      key = new TriggerKey(name, group);

startAt()

设置Trigger触发时间,在指定的时间开始触发。触发时间对于Trigger来说是个必须存在的参数,入参不能为null

但是用户可以不设置触发时间,因为系统已经默认设置为当前时间,也就是会立即触发。

endAt()

设置Trigger停止时间。省略该参数时,表示永远执行下去。

startNow()

设置Trigger触发时间,立即触发。
等价于 startAt(new Date())

usingJobData()

给Trigger添加一些附加值(通过context.Trigger.JobDataMap获取) ,详情可以参考quartz 传参

withPriority()

设置Trigger的优先级,默认为5,数字越大,优先级越高.(该优先级用于一个job对应多个Trigger,且Trigger的触发时间相同,优先级越大的越先执行)。

ForJob()

jobtrigger提前进行关联,trigger必须指向唯一的一个job,否则指定了触发器,却没有相应的job去执行,那么毫无意义。

该方法有多个重载,无论哪个方法,都是为了构建一个JobKey对象而已。

我们先简单介绍下一般使用的场景,更详细的解释可以参考下面章节《Job和Trigger关联问题》,调度有2种api:

  • scheduleJob(jobDetail, trigger);
    创建一个调度任务,会先检查绑定job和trigger映射关系,如果没有绑定,则利用入参的2个参数进行绑定,并把job存入JobStore;如果已经绑定,则会校验入参的jobDetail和已经绑定的那个jobDetail是否一致,避免用户前后设置不一致。

    如果 使用 ForJob()预设置jobDetail,并且使用该接口创建调度任务,入参的jobDetail和已经绑定的那个jobDetail前后不一致,会报Trigger does not reference given job!

  • scheduleJob(Trigger trigger)
    创建一个调度任务,如果使用 ForJob()在构建trigger时已经绑定与job的关系,那么,此处可以只传入trigger。

    如果没有使用 ForJob()预设置jobDetail,并且使用该接口创建调度任务,此时会报Trigger's related Job's name cannot be null

Job和Trigger关联问题

Job和Trigger关联问题其实是本篇文章的重点,其他的知识诸如startNow()等API的用法,基本上随便都能搜到,并且很好理解。如果你自认为对quartz很熟悉,那么请问scheduleJob(jobDetail, trigger);scheduleJob(Trigger trigger)的本质区别是什么?

探究Job是啥,Trigger是啥

我们回答下上面的问题,那2个方法的区别是啥?

前面我们在介绍ForJob()时,不是已经讲了区别吗?是的,那不过是表面现象,我们看下下面的代码,执行会报错,你能一眼看出问题所在吗?

 public static void main(String[] args) throws SchedulerException {
      Scheduler scheduler;
      scheduler = StdSchedulerFactory.getDefaultScheduler();
      scheduler.start();

      Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
              .forJob("job1", "jobGroup1")   // 提前绑定关系
              .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ? *")).build();

      scheduler.scheduleJob(trigger);   //创建调度任务

  }

上面例子是基于 Quartz (1) 入门例子中的CronTrigger例子改造而来,我们加入了forJob(jobName,jobGroup)方法提前绑定关系,我们看下执行结果:

Exception in thread "main" org.quartz.JobPersistenceException: The job (group1.Job1) referenced by the trigger does not exist.
	at org.quartz.simpl.RAMJobStore.storeTrigger(RAMJobStore.java:422)

执行结果提示trigger 找不到被引用的job

至此,我们开始揭秘,Quartz框架存在JobStore(上面例子中是RAMJobStore,存储在内存中),里面有个集合属性jobsByKey,作用是存储所有的job实例,当我们配置了Job和Trigger的映射关系后,在创建一个调度任务之前,必须确保Job引用是真实存在的,也就是说RAMJobStore的jobsByKey必须存储该job实例对象:

我们看下RAMJobStore 源码:

//v2.2.3
public class RAMJobStore implements JobStore {
   //存储job实例的集合
  protected HashMap<JobKey, JobWrapper> jobsByKey = new HashMap<JobKey, JobWrapper>(1000);
   //存储trigger实例的集合
  protected HashMap<TriggerKey, TriggerWrapper> triggersByKey = new HashMap<TriggerKey, TriggerWrapper>(1000);
   //通过该方法,获取某个job实例
  public JobDetail retrieveJob(JobKey jobKey) {
   ...
   }

我们来改造上面的例子,单独加入一个job实例,并提取配置引用关系,再调用scheduleJob(trigger);并且验证上述理论:

  public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler;
        scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.start();

       //定义一个job
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("job1", "jobGroup1")
                .storeDurably()    //必须加否则报错‘Jobs added with no trigger must be durable’
                .build();

        scheduler.addJob(jobDetail, true);   //用addJob()单独添加一个job

        Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
                .forJob("job1", "jobGroup1")      
                .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ? *")).build();

        scheduler.scheduleJob(trigger);  //正常执行

    }

至此,我们简单得出结论,Job是个实例,Trigger也是个实例,他们创建后,都各自存在于相应的集合中。

JobDetail有个属性叫durable,表明该任务没有被任何trigger引用时仍保存在Quartz的JobStore中,默认为false。我们这个例子需要设置为为true,因为是先创建一个未被引用的job。

Job和Trigger的映射关系

我们直接给出结论:Job与Trigger是一对多的关系,一个Trigger只能引用一个Job,一个Job可以被多个Trigger引用

其实很好理解,我们知道触发器Trigger需要一个Job来完成实际任务,并且不能三心二意,只能引用一个,否则,触发时,究竟哪个来执行任务呢,岂不容易混淆?而多个Trigger可以复用同一个Job,因为如果多个触发器干的任务相同,那么则可以避免重复定义Job。

另外我们可以得出一个结论,一旦一个job被删了,trigger也会被删掉;若是多个trigger依赖同一个job,那么多个trigger都会被删掉。因为job不存在了,那么引用它的trigger也就没有意义了,否则,触发时,没有人干活了,还触发干吗?

job由name和group决定全局唯一的jobKey,不能添加相同的job

Scheduler

Scheduler负责任务调度,包括创建任务和删除任务、删除触发器

通过StdSchedulerFactory创建的实例时StdScheduler

clear()

清除所有的job和trigger

deleteJob(JobKey jobKey)

入参是jobkey
删除指定的job。同时,会删除所有引用job的trigger

皮之不存毛将焉附,所有引用的job的trigger都该删

unscheduleJob(TriggerKey triggerKey)

删除触发器







参考手册:
https://www.w3cschool.cn/quartz_doc/quartz_doc-kixe2cq3.html
https://www.cnblogs.com/yaopengfei/p/8533333.html
https://www.cnblogs.com/Dorae/p/9357180.html

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 引入依赖 在 `pom.xml` 文件中引入以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> ``` 2. 配置数据源 在 `application.yml` 或 `application.properties` 文件中配置数据源: ``` spring.datasource.url=jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver ``` 3. 配置Quartz 在 `application.yml` 或 `application.properties` 文件中配置Quartz: ``` # 配置Quartz spring.quartz.job-store-type=jdbc spring.quartz.jdbc.initialize-schema=always spring.quartz.properties.org.quartz.threadPool.threadCount=5 ``` `spring.quartz.job-store-type` 指定Quartz使用的存储类型,这里配置为 `jdbc` 表示使用数据库存储。 `spring.quartz.jdbc.initialize-schema` 指定Quartz是否需要初始化数据库表,这里配置为 `always` 表示每次启动应用程序都会初始化数据库表。 `spring.quartz.properties` 是Quartz的属性配置,这里配置了线程池的线程数为5。 4. 编写任务类 编写Quartz任务类,实现 `org.quartz.Job` 接口即可。 ``` @Component public class MyJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("Hello, Quartz!"); } } ``` 5. 配置任务调度器 编写任务调度器类,实现 `org.springframework.scheduling.quartz.SchedulerFactoryBean` 接口即可。 ``` @Configuration public class QuartzConfig { @Autowired private DataSource dataSource; @Autowired private MyJob myJob; @Bean public SchedulerFactoryBean schedulerFactoryBean() { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); schedulerFactoryBean.setDataSource(dataSource); // 自定义JobFactory,用于支持Spring的自动注入 AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory(); jobFactory.setApplicationContext(applicationContext); schedulerFactoryBean.setJobFactory(jobFactory); // 配置JobDetail JobDetail jobDetail = JobBuilder.newJob(myJob.getClass()).withIdentity("myJob").build(); // 配置Trigger SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean(); trigger.setJobDetail(jobDetail); trigger.setRepeatInterval(5000); trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); // 注册Trigger schedulerFactoryBean.setTriggers(trigger.getObject()); return schedulerFactoryBean; } } ``` 6. 自动注入Spring容器 为了支持Spring的自动注入,需要编写一个自定义的 JobFactory。 ``` public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware { private transient AutowireCapableBeanFactory beanFactory; @Override public void setApplicationContext(final ApplicationContext context) { beanFactory = context.getAutowireCapableBeanFactory(); } @Override protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception { final Object job = super.createJobInstance(bundle); beanFactory.autowireBean(job); return job; } } ``` 7. 测试 在需要执行定时任务的方法上添加 `@Scheduled` 注解即可。 ``` @Component public class TestJob { @Scheduled(cron = "0/5 * * * * ?") public void test() { System.out.println("test"); } } ``` 以上是springboot整合quartz常见配置,可以根据具体需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值