概述
在实际任务调度中,我们不可能一成不变地按照某个周期性的调度规则运行任务,必须考虑到实现生活中日历上特定日期。
由于节日是每年重复的,所以使用org.quartz.Calendar的AnnualCalendar实现类
注意事项
Quartz 的 Calendar 对象与 Java API 的 java.util.Calendar,它们是应用于不同目的不一样的组件。
Java 的 Calendar 对象是通用的日期和时间工具;许多过去由 Java 的 Date 类提供的功能现在加到了 Calendar 类中了。
Quartz 的 Calendar 专门用于屏闭一个时间区间,使 Trigger 在这个区间中不被触发。
Calendar 排除时间的粒度
Calendar 接口方法参数的类型是 Long。这说明 Quartz Calendar 能够排除的时间细致毫秒级。
你很可能永远都不需要这么细小的位度,因为大部分的 Job 只需要排除特别的日期或许会是小时。然而,假如你真需要排除到毫秒一级的,Calendar 能帮你做到.
BaseCalendar子类说明
注意,所有的Calendar既可以是排除,也可以是包含,取决于:
AnnualCalendar:指定每年的哪一天。使用方式如上例。精度是【天】
CronCalendar:指定Cron表达式。精度取决于Cron表达式,也就是最大精度可以【到秒】
DailyCalendar:指定每天的时间段(rangeStartingTime,
rangeEndingTime),格式是HH:MM[:SS[:mmm]]。也就是最大精度可以【到毫秒】
HolidayCalendar:指定特定的日期,比如20140613。精度到【天】
MonthlyCalendar:指定每月的几号。可选值为1-31。精度是【天】
WeeklyCalendar:指定每星期的星期几,可选值比如为java.util.Calendar.SUNDAY。精度是【天】
示例
要使用 Quartz Calendar,你只需简单的实例化,并加入你要排除的日期,然后用 Scheduler 注册它。最后把这个 Calendar 实例与你想要使用该Calendar 的每一个 Trigger 实例关联起来
package com.xgj.quartz.quartzItself.calendarDemo; import static org.quartz.DateBuilder.dateOf; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SchedulerMetaData; import org.quartz.SimpleTrigger; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.calendar.AnnualCalendar; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This example will demonstrate how calendars can be used to exclude periods of * time when scheduling should not take place. */ public class CalendarExample { public void run() throws Exception { final Logger log = LoggerFactory.getLogger(CalendarExample.class); log.info("------- Initializing ----------------------"); // First we must get a reference to a scheduler SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); log.info("------- Initialization Complete -----------"); log.info("------- Scheduling Jobs -------------------"); // Add the holiday calendar to the schedule AnnualCalendar holidays = new AnnualCalendar(); // fourth of July (July 4) Independence Day Of USA Calendar fourthOfJuly = new GregorianCalendar(2005, 6, 4); holidays.setDayExcluded(fourthOfJuly, true); // halloween (Oct 31) Calendar halloween = new GregorianCalendar(2005, 9, 31); holidays.setDayExcluded(halloween, true); // christmas (Dec 25) Calendar christmas = new GregorianCalendar(2005, 11, 25); holidays.setDayExcluded(christmas, true); // tell the schedule about our holiday calendar sched.addCalendar("holidays", holidays, false, false); // schedule a job to run hourly, starting on halloween // at 10 am Date runDate = dateOf(0, 0, 10, 31, 10); JobDetail job = newJob(SimpleJob.class).withIdentity("job1", "group1") .build(); SimpleTrigger trigger = newTrigger() .withIdentity("trigger1", "group1") .startAt(runDate) .withSchedule( simpleSchedule().withIntervalInHours(1).repeatForever()) .modifiedByCalendar("holidays").build(); // schedule the job and print the first run date Date firstRunTime = sched.scheduleJob(job, trigger); // print out the first execution date. // Note: Since Halloween (Oct 31) is a holiday, then // we will not run until the next day! (Nov 1) log.info(job.getKey() + " will run at: " + firstRunTime + " and repeat: " + trigger.getRepeatCount() + " times, every " + trigger.getRepeatInterval() / 1000 + " seconds"); // All of the jobs have been added to the scheduler, but none of the // jobs // will run until the scheduler has been started log.info("------- Starting Scheduler ----------------"); sched.start(); // wait 30 seconds: // note: nothing will run log.info("------- Waiting 30 seconds... --------------"); try { // wait 30 seconds to show jobs Thread.sleep(30L * 1000L); // executing... } catch (Exception e) { // } // shut down the scheduler log.info("------- Shutting Down ---------------------"); sched.shutdown(true); log.info("------- Shutdown Complete -----------------"); SchedulerMetaData metaData = sched.getMetaData(); log.info("Executed " + metaData.getNumberOfJobsExecuted() + " jobs."); } public static void main(String[] args) throws Exception { CalendarExample example = new CalendarExample(); example.run(); } }
package com.xgj.quartz.quartzItself.calendarDemo; import java.util.Date; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <p> * This is just a simple job that gets fired off many times by example 1 * </p> * */ public class SimpleJob implements Job { private static Logger _log = LoggerFactory.getLogger(SimpleJob.class); /** * Empty constructor for job initialization */ public SimpleJob() { } /** * <p> * Called by the <code>{@link org.quartz.Scheduler}</code> when a * <code>{@link org.quartz.Trigger}</code> fires that is associated with * the <code>Job</code>. * </p> * * @throws JobExecutionException * if there is an exception while executing the job. */ public void execute(JobExecutionContext context) throws JobExecutionException { // This job simply prints out its job name and the // date and time that it is running JobKey jobKey = context.getJobDetail().getKey(); _log.info("SimpleJob says: " + jobKey + " executing at " + new Date()); } }
AnnualCalendar
package com.xgj.quartz.quartzItself.calendarDemo.AnnualCalendar; import static org.quartz.DateBuilder.dateOf; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SchedulerMetaData; import org.quartz.SimpleTrigger; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.calendar.AnnualCalendar; import com.xgj.quartz.quartzItself.calendarDemo.SimpleJob; /** * * * @ClassName: AnnualCalendarDemo * * @Description: 此示例将演示如何使用日历来排除不应该进行调度的时间段。 * * @author: Mr.Yang * * @date: 2017年11月15日 下午5:14:57 */ public class AnnualCalendarDemo { public static void main(String[] args) throws Exception { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("------- 初始化 ----------"); SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); // 声明一个节假日 holidayCalendar,标明要排除的日期 // 法定节日是以每年为周期的,所以使用AnnualCalendar而不是HolidayCalendar AnnualCalendar holidays = new AnnualCalendar(); Calendar fourthOfJuly = new GregorianCalendar(2017, 6, 4); // fourth of // July // (July 4) // 七月四日 holidays.setDayExcluded(fourthOfJuly, true); System.out.println("第一个节假日:" + sdf.format(fourthOfJuly.getTime())); Calendar halloween = new GregorianCalendar(2017, 9, 31); // halloween // (Oct 31) // 万圣节(10月31日) holidays.setDayExcluded(halloween, true); System.out.println("第二节假日:" + sdf.format(halloween.getTime())); Calendar christmas = new GregorianCalendar(2017, 11, 25); // christmas // (Dec 25) // christmas // (Dec 25) holidays.setDayExcluded(christmas, true); System.out.println("第三个节假日:" + sdf.format(christmas.getTime())); sched.addCalendar("holidays", holidays, false, false); // 节假日加入schedule调度器 // 开始在万圣节前夜上午10点,开始任务 Date runDate = dateOf(0, 0, 10, 31, 10); System.out.println("任务开始时间:" + sdf.format(runDate)); JobDetail job = newJob(SimpleJob.class).withIdentity("job1", "group1") .build(); SimpleTrigger trigger = newTrigger() .withIdentity("trigger1", "group1") .startAt(runDate) .withSchedule( simpleSchedule().withIntervalInHours(1).repeatForever()) .modifiedByCalendar("holidays").build(); Date firstRunTime = sched.scheduleJob(job, trigger); // 注意:万圣节(10月31日)是假期,所以直到第二天才会运行! (11月1日) System.out.println(job.getKey() + " 将运行于:" + firstRunTime + " 并重复:" + trigger.getRepeatCount() + " 次, 间隔 " + trigger.getRepeatInterval() / 1000 + " 秒"); System.out.println("------- 开始 Scheduler ----------------"); sched.start(); System.out.println("------- 等待 30 秒... --------------"); try { Thread.sleep(30L * 1000L); } catch (Exception e) { } sched.shutdown(true); System.out.println("------- 关闭调度器 -----------------"); SchedulerMetaData metaData = sched.getMetaData(); System.out.println("执行了: " + metaData.getNumberOfJobsExecuted() + " 个jobs."); } }
CronCalendar
package com.xgj.quartz.quartzItself.calendarDemo.CronCalendar; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; import java.text.SimpleDateFormat; import java.util.Date; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SchedulerMetaData; import org.quartz.SimpleTrigger; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.calendar.CronCalendar; import com.xgj.quartz.quartzItself.calendarDemo.SimpleJob; public class CronCalendarDemo { public static void main(String[] args) throws Exception { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("------- 初始化 ----------"); SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); // 允许执行的时间, 星期参数:"7" = "SAT",2 = MON // 【秒】 【分钟】 【小时】 【月中天】 【月】 【周中天(1-7)】 [【年(可省略)】] String excludeExpression; // 这里设置禁用时间段为,每0-20之间,40-59之间不执行 excludeExpression = "0-20,40-59 * * * * ?"; CronCalendar cronCalendar = new CronCalendar(excludeExpression); // 标明要排除的日期 每天的17点10分 sched.addCalendar("cronCalendar", cronCalendar, false, false); // 节假日加入schedule调度器 Date runDate = new Date(); System.out.println("任务开始时间:" + sdf.format(runDate)); // 任务每10秒执行一次 JobDetail job = newJob(SimpleJob.class).withIdentity("job1", "group1") .build(); SimpleTrigger trigger = newTrigger() .withIdentity("trigger1", "group1") .startAt(runDate) .withSchedule( simpleSchedule().withIntervalInSeconds(10) .repeatForever()) .modifiedByCalendar("cronCalendar").build(); // 触发器加入调度器 Date firstRunTime = sched.scheduleJob(job, trigger); System.out.println(job.getKey() + " 将运行于:" + sdf.format(firstRunTime) + " 并重复:" + trigger.getRepeatCount() + " 次, 间隔 " + trigger.getRepeatInterval() / 1000 + " 秒"); System.out.println("------- 开始 Scheduler ----------------"); sched.start(); try { System.out.println("------- 等待 120 秒(2分钟)... --------------"); Thread.sleep(120L * 1000L); // do something } catch (Exception e) { } sched.shutdown(true); System.out.println("------- 关闭调度器 -----------------"); SchedulerMetaData metaData = sched.getMetaData(); System.out.println("~~~~~~~~~~ 执行了 " + metaData.getNumberOfJobsExecuted() + " 个 jobs."); } }
DailyCalendar
package com.xgj.quartz.quartzItself.calendarDemo.DailyCalendar; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; import java.text.SimpleDateFormat; import java.util.Date; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SchedulerMetaData; import org.quartz.SimpleTrigger; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.calendar.DailyCalendar; import com.xgj.quartz.quartzItself.calendarDemo.SimpleJob; /** * * * @ClassName: DailyCalendarDemo * * @Description: 注意:dailyCalendar.setInvertTimeRange(true); // * 时间反转,为true表示只有这次时间段才会被执行,为false表示排除这时间段 * * @author: Mr.Yang * * @date: 2017年11月15日 下午5:40:00 */ public class DailyCalendarDemo { public static void main(String[] args) throws Exception { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("------- 初始化 ----------"); SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); DailyCalendar dailyCalendar = new DailyCalendar("12:17:30", "12:18:20"); dailyCalendar.setInvertTimeRange(true); // 时间反转,为true表示只有这次时间段才会被执行,为false表示排除这时间段 // 标明要排除的日期 每天的17点10分 sched.addCalendar("dailyCalendar", dailyCalendar, false, false); // 节假日加入schedule调度器 Date runDate = new Date(); System.out.println("任务开始时间:" + sdf.format(runDate)); // 任务每10秒执行一次 JobDetail job = newJob(SimpleJob.class).withIdentity("job1", "group1") .build(); SimpleTrigger trigger = newTrigger() .withIdentity("trigger1", "group1") .startAt(runDate) .withSchedule( simpleSchedule().withIntervalInSeconds(10) .repeatForever()) .modifiedByCalendar("dailyCalendar").build(); Date firstRunTime = sched.scheduleJob(job, trigger); System.out.println(job.getKey() + " 将运行于:" + sdf.format(firstRunTime) + " 并重复:" + trigger.getRepeatCount() + " 次, 间隔 " + trigger.getRepeatInterval() / 1000 + " 秒"); System.out.println("------- 开始 Scheduler ----------------"); sched.start(); System.out.println("------- 等待 360 秒(3分钟)... --------------"); try { Thread.sleep(360L * 1000L); // do something } catch (Exception e) { } sched.shutdown(true); System.out.println("------- 关闭调度器 -----------------"); SchedulerMetaData metaData = sched.getMetaData(); System.out.println("~~~~~~~~~~ 执行了 " + metaData.getNumberOfJobsExecuted() + " 个 jobs."); } }
HolidayCalendar
HolidayCalendar holidayCalendar = new HolidayCalendar(); Calendar calendar = new GregorianCalendar(2017, 10, 1); // 2017年11月1日 holidayCalendar.addExcludedDate(calendar.getTime()); calendar = new GregorianCalendar(2018, 10, 2); // 2018年11月2日 holidayCalendar.addExcludedDate(calendar.getTime()); holidayCalendar.getExcludedDates().forEach(date -> { System.out.println("假期日:"+ sdf.format(date)); }); sched.addCalendar("holidays", holidayCalendar, false, false); // 节假日加入schedule调度器
MonthlyCalendar
月日历,你可以定义一个月当中的若干天,例如你可以设置每个月的第一天触发器不进行触发,当然你还可以定义一个月当中的任何一天。
// 设置2,3,4月不触发任务 MonthlyCalendar monthlyCalendar = new MonthlyCalendar(); monthlyCalendar.setDayExcluded(2, true); monthlyCalendar.setDayExcluded(3, true); monthlyCalendar.setDayExcluded(4, true); sched.addCalendar("monthlys", monthlyCalendar, false, false); // 节假日加入schedule调度器
javaWeeklyCalendar
星期日历,可以定义在一个星期当中的星期几几几 是不触发的日期,例如你可以定义么每个周末(星期天)触发器不触发,你也可以定义一周当中的任何一天或是几天。默认情况SATURDAY ,SUNDAY 这两天是没排除的。
下面的例子设置了每个星期四触发器不触发,并且默认情况周六和周天也是不触发的,这个是默认设置。
如果需要周六周日也触发,那么把它清掉就可以了(weeklyCalendar.setDayExcluded(Calendar.SATURDAY , false)像这样)。
一个需要注意的地方就是传入参数不能直接写数字星期几,因为老外的日子计算的与我们不一样,需要传入(java.util.Calendar)的常量字段,这样才准确。
WeeklyCalendar weeklyCalendar = new WeeklyCalendar(); weeklyCalendar.setDayExcluded(Calendar.THURSDAY, true); sched.addCalendar("weeklys", weeklyCalendar, false, false); // 节假日加入schedule调度器
组合日历的使用
上面的例子都是每一个触发器(trigger)关联一个日历的例子,我们在构建触发器的时候通过.modifiedByCalendar(“日历的key”)关联一个注册到引擎当中的日历,这种情况已经能够满足我们大部分的需求。
但是系统的需求往往是复杂多变的,假设有这样一种情况,需要一个触发器在 每周一到周五,早8点-晚晚5点 每隔1小时执行,那么该如何使用日历呢?
其实我们不用日历,使用一个CronTrigger也是可以搞定的,我们这里只不过是抛砖引玉而已。
那让我们来写一个组合日历使用的例子:
DailyCalendar dailyCalendar = new DailyCalendar("8:00:00", "17:00:00"); dailyCalendar.setInvertTimeRange(false); WeeklyCalendar weeklyCalendar = new WeeklyCalendar(dailyCalendar); sched.addCalendar("weeklyCalendar", weeklyCalendar, false, false);
我们写一个时间间隔的日历dailyCalendar,将其作为参数传递给weeklyCalendar就可以了,这样引擎在计算日历日期的时候会先判断dailyCalendar的时间范围,然后再判断weeklyCalendar是时间范围,当条件都满足的时候,触发器才会被触发
示例源码
代码已托管到Github—> https://github.com/yangshangwei/SpringMaster