Quartz学习笔记(三)JobKey、TriggerKey、Trigger

JobKey与TriggerKey

JobKey是表明Job身份的一个对象,里面封装了Job的name和group,TriggerKey同理。
之前我们在构建JobDetail和Trigger是用的是以下方法来指定他们对于的name和group
JobBuilder里面

 public JobBuilder withIdentity(String name, String group) {
        key = new JobKey(name, group);
        return this;
    }

TriggerBuilder里面

 public TriggerBuilder<T> withIdentity(String name, String group) {
        key = new TriggerKey(name, group);
        return this;
    }

关于withIdentity方法,我们可以直接构造一个TriggerKey或者JobKey,然后通过withIdentity传递给对于的Builder
在TriggerKey和JobKey中分别封装了两个静态方法来获取TriggerKey和JobKey

在这里插入图片描述
在这里插入图片描述
当我们不指定group时,Quartz会用默认的组名DEFAULT

Trigger

所有类型的trigger都有TriggerKey这个属性,表示trigger的身份;除此之外,trigger还有很多其它的公共属性。这些属性,在构建trigger的时候可以通过TriggerBuilder设置。
我们可以通过查看TriggerBuilder里面的一些方法跟Trigger的源码来了解到这些属性。
如下:

public class TriggerBuilder<T extends Trigger> {

    private TriggerKey key;
    private String description;
    private Date startTime = new Date();
    private Date endTime;
    private int priority = Trigger.DEFAULT_PRIORITY;
    private String calendarName;
    private JobKey jobKey;
    private JobDataMap jobDataMap = new JobDataMap();
    
    private ScheduleBuilder<?> scheduleBuilder = null;
    
    private TriggerBuilder() {
        
    }
   .........
  }
public abstract class AbstractTrigger<T extends Trigger> implements OperableTrigger {

    private static final long serialVersionUID = -3904243490805975570L;

    /*
    * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    *
    * Data members.
    *
    * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    */

    private String name;

    private String group = Scheduler.DEFAULT_GROUP;

    private String jobName;

    private String jobGroup = Scheduler.DEFAULT_GROUP;

    private String description;

    private JobDataMap jobDataMap;

    @SuppressWarnings("unused")
    private boolean volatility = false; // still here for serialization backward compatibility

    private String calendarName = null;

    private String fireInstanceId = null;

    private int misfireInstruction = MISFIRE_INSTRUCTION_SMART_POLICY;

    private int priority = DEFAULT_PRIORITY;

    private transient TriggerKey key = null;
    
}

其中比较常用的公共属性有

  • 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号。

除了以上比较常用的属性,Trigger还有优先级(priority)、错过触发(misfire Instructions)和日历示例(calendar)这三个比较重要的属性

优先级(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是一样的。

在构造Trigger,通过TriggerBuilder的withPriority方法来设置

 public TriggerBuilder<T> withPriority(int triggerPriority) {
        this.priority = triggerPriority;
        return this;
    }

错过触发(misfire Instructions)

如果scheduler关闭了,或者Quartz线程池中没有可用的线程来执行job,此时持久性的trigger就会错过(miss)其触发时间,即错过触发(misfire)
当下次调度器启动或者有可以线程时,会检查处于misfire状态的Trigger。而misfire的状态值决定了调度器如何处理这个Trigger。
不同类型的trigger,有不同的misfire机制。它们默认都使用“智能机制(smart policy)”,即根据trigger的类型和配置动态调整行为。
具体的misfire机制在学习具体的trigger时再详细整理一下。

日历(calendar)

Quartz的Calendar对象(不是java.util.Calendar对象)可以在定义和存储trigger的时候与trigger进行关联。
Calendar用于从trigger的调度计划中排除时间段。比如,可以创建一个trigger,每个工作日的上午9:30执行,然后增加一个Calendar,排除掉所有的商业节日。
Calendar排除时间段的单位可以精确到毫秒,同时Quartz提供了很多开箱即用的实现来满足我们的不同需求,如下

类名用法
org.quartz.impl.calendar.HolidayCalendar指定特定的日期,比如20140613 精度到天
org.quartz.impl.calendar.AnnualCalendar指定每年的哪一天,精度是天
org.quartz.impl.calendar.MonthlyCalendar指定每月的几号,可选值为1-31,精度是天
org.quartz.impl.calendar.WeeklyCalendar指定每星期的星期几,可选值比如为java.util.Calendar.SUNDAY,精度是天
org.quartz.impl.calendar.DailyCalendar指定每天的时间段(rangeStartingTime, rangeEndingTime),格式是HH:MM[:SS[:mmm]],也就是最大精度可以到毫秒
org.quartz.impl.calendar.CronCalendar指定Cron表达式,精度取决于Cron表达式,也就是最大精度可以到秒

注意,所有的Calendar既可以是排除,也可以是包含
HolidayCalendar与AnnualCalendar的区别

  • HolidayCalendar考虑的是年,需要指定确定的年月日
  • AnnualCalendar是指每一年的哪一天。

所以通常情况下,比如我想排除每年的5月1日,用AnnualCalendar就比较方便,若用HolidayCalendar,就需要注册多个HolidayCalendar到Scheduler中。

AnnualCalendar

创建与注册

	// 构建SchedulerFactory实例
	SchedulerFactory schedFact = new StdSchedulerFactory();
	// 获取Scheduler实例
	Scheduler scheduler = schedFact.getScheduler();
	//创建AnnualCalendar对象
	AnnualCalendar annualday = new AnnualCalendar();
	Calendar laborDay = GregorianCalendar.getInstance();
	//设置5月1日,这里需要注意在Calendar月份的基础上+1才是真实的月份
	laborDay.set(Calendar.MONTH, Calendar.MAY+1);
	laborDay.set(Calendar.DATE, 1);
	// 排除每年5月1日,true为排除,false为包含
	annualday .setDayExcluded(laborDay, true);
	// 向Scheduler注册日历
	scheduler.addCalendar("annualday", annualday, true, true);

需要注意一下几点

  • AnnualCalendar就算设置了年,也不会生效,它会在每一年都生效,具体逻辑可以查看源码
  • AnnualCalendar .setDayExcluded(java.util.Calendar day, boolean exclude)方法中的exclude是用来决定是包含还是排除
  • setDayExcluded有一个重载的方法,可以传入一个ArrayList<java.util.Calendar>,一次性加入多个java.util.Calendar,但是该方法默认exclude为true,就是默认是排除
  • Scheduler.addCalendar(String calName, Calendar calendar, boolean replace, boolean updateTriggers)方法中的参数
    replace表示是否覆盖之前设置的相同名称的Calendar ,若为false,当add相同名称的Calendar 时,会抛SchedulerException
    updateTriggers表示若已存在的triggers已经引用 了相同名称的Calendar,是否更新triggers绑定的Calendar

绑定Trigger

SimpleTrigger triggerB = TriggerBuilder.newTrigger()
				                               .withIdentity("helloTriggerB", "helloB")
				                               .startNow()
				                               .withSchedule(SimpleScheduleBuilder
				                               .simpleSchedule()
				                               .withIntervalInSeconds(5).repeatForever())
				                               //指定trigger使用Calendar
				                               .modifiedByCalendar("annualday")
				                               .build();

HolidayCalendar

该日历与AnnualCalendar差不多,区别就是设置的year是有效的,如果你希望在未来5年内都排除5月1日这一天,你需要添加5个日期,分别是 2019-5-1,2020-5-1,2021-5-1,2022-5-1,2023-5-1

		// 构建SchedulerFactory实例
		SchedulerFactory schedFact = new StdSchedulerFactory();
		// 获取Scheduler实例
		Scheduler scheduler = schedFact.getScheduler();
		//创建HolidayCalendar对象
		HolidayCalendar holidays = new HolidayCalendar();
		Calendar laborDay1 = new GregorianCalendar(2019, 5, 1);
		Calendar laborDay2 = new GregorianCalendar(2020, 5, 1);
		Calendar laborDay3 = new GregorianCalendar(2021, 5, 1);
		Calendar laborDay4 = new GregorianCalendar(2022, 5, 1);
		Calendar laborDay5 = new GregorianCalendar(2023, 5, 1);
		
		holidays.addExcludedDate(laborDay1.getTime());
		holidays.addExcludedDate(laborDay2.getTime());
		holidays.addExcludedDate(laborDay3.getTime());
		holidays.addExcludedDate(laborDay4.getTime());
		holidays.addExcludedDate(laborDay5.getTime());
		// 向Scheduler注册日历
		scheduler.addCalendar("holidays", holidays, true, true);

需要注意的是

  • HolidayCalendar只能做排除操作
  • HolidayCalendar里面维护了一个TreeSet集合来存放通过addExcludedDate加入的排除日期,所以若是需要排除多个日期,多次调用addExcludedDate方法即可。

MonthlyCalendar

月日历,你可以定义一个月当中的若干天不触发。比如我想排除每个月的2,4,5号不触发

		MonthlyCalendar monthlyCalendar = new MonthlyCalendar();
		monthlyCalendar.setDayExcluded(2, true);
		monthlyCalendar.setDayExcluded(4, true);
		monthlyCalendar.setDayExcluded(6, true);
		// 向Scheduler注册日历
		scheduler.addCalendar("monthlyCalendar", monthlyCalendar, true, true);

WeeklyCalendar

星期日历,可以定义每周内周几不触发。例如设置每周四不触发

		WeeklyCalendar weeklyCalendar = new WeeklyCalendar();
		weeklyCalendar.setDayExcluded(Calendar.THURSDAY, true);
		// 向Scheduler注册日历
		scheduler.addCalendar("weeklyCalendar", weeklyCalendar, true, true);

需要注意的是

  • 当我们定义了WeeklyCalendar ,周六和周日就默认被排除了,如果想要不排除,需要自己把这两天通过setDayExcluded设置为false,即不排除。可以查看如下源码

     public WeeklyCalendar() {
        this(null, null);
    }
    public WeeklyCalendar(Calendar baseCalendar, TimeZone timeZone) {
        super(baseCalendar, timeZone);
    	//默认排除周六和周日
        excludeDays[java.util.Calendar.SUNDAY] = true;
        excludeDays[java.util.Calendar.SATURDAY] = true;
        excludeAll = areAllDaysExcluded();
    }
    
  • WeeklyCalendar .setDayExcluded(int wday, boolean exclude)中的参数务必用java.util.Calendar中的定义的常量,因为国际上一周的开始第一天是周日,可以查看java.util.Calendar中定义的周常量,是从SUNDAY(1)开始的。

DailyCalendar

时间范围日历,定义一个时间范围,可以让触发器在这个时间范围内触发,或者在这个时间范围内不触发,每一个DailyCalendar的实例只能设置一次时间范围,并且这个时间范围不能超过一天的边界。
例如我要排除每天当中晚上20点到21点

		// 构建SchedulerFactory实例
		SchedulerFactory schedFact = new StdSchedulerFactory();
		// 获取Scheduler实例
		Scheduler scheduler = schedFact.getScheduler();
		
		Calendar start = GregorianCalendar.getInstance();
		start.setTime(DateBuilder.dateOf(20, 0, 0));
		
		Calendar end = GregorianCalendar.getInstance();
		end.setTime(DateBuilder.dateOf(21, 0, 0));
		
		DailyCalendar dailyCalendar = new DailyCalendar(start, end);
		dailyCalendar.setInvertTimeRange(false);
		// 向Scheduler注册日历
		scheduler.addCalendar("dailyCalendar", dailyCalendar, true, true);

需要注意的是

  • 可以通过DailyCalendar中的参数invertTimeRange来设置在定义的时间范围内不触发,还是在定义的时间范围外不触发
    根据源码中的描述invertTimeRange=false(默认值)表示在定义的时间范围内不触发
    invertTimeRange=true表示在定义的时间范围外不触发
  • DailyCalendar的构造参数支持直接输出字符串,比如:new DailyCalendar(“20:00:00”, “21:00:00”)
  • DailyCalendar可以支持精确到毫秒级别的控制

CronCalendar

Cron表达式日历,可以写一个表达式来排除一个时间范围,比如可以设置为排除所有的时间,但是工作时间除外,也就是 在早8点-晚5点触发,其他时间暂停。

		// 构建SchedulerFactory实例
		SchedulerFactory schedFact = new StdSchedulerFactory();
		// 获取Scheduler实例
		Scheduler scheduler = schedFact.getScheduler();
		try {
			CronCalendar cronCalendar= new CronCalendar("* * 0-7,18-23 ? * *");
			// 向Scheduler注册日历
			scheduler.addCalendar("cronCalendar", cronCalendar, true, true);
		} catch (ParseException e) {
			e.printStackTrace();
		}

组合日历

我们在构建Trigger是只能通过modifiedByCalendar(“日历的name”)方法关联一个已经注册到调度引擎的日历对象,这种情况已经能够满足我们大部分的需求。
但是如果有跟复杂的需求,比如我们需要在每周的周一至周五,排除晚上8点到24点,情况下按照Trigger配置的周期运行。可以用三种办法实现以上需求

  • CronTrigger,这个稍后在详细介绍,通过构建corn表达式来实现。
  • 组合日历,每中日历都有一个构造方法,可以传递一个日历对象进来,通过这样的方式可以构建一个复杂的日历对象。
  • CronTrigger+组合日历的方式。

组合日历使用的例子:

	// 构建SchedulerFactory实例
		SchedulerFactory schedFact = new StdSchedulerFactory();
		// 获取Scheduler实例
		Scheduler scheduler = schedFact.getScheduler();
		
		DailyCalendar dailyCalendar = new DailyCalendar("20:00:00", "23:59:59");
		dailyCalendar.setInvertTimeRange(false);
		
		//默认是排除周六和周日的
		WeeklyCalendar weeklyCalendar = new WeeklyCalendar(dailyCalendar);
		
		// 向Scheduler注册日历
		scheduler.addCalendar("weeklyCalendar", weeklyCalendar, true, true);

写一个时间间隔的日历dailyCalendar,将其作为参数传递给weeklyCalendar就可以了,这样引擎在计算日历日期的时候会先判断dailyCalendar的时间范围,然后再判断weeklyCalendar是时间范围,当条件都满足的是否,触发器才会被触发。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值