quartz任务调度框架使用及源码讲解

常用任务调度组件介绍

1. quartz(最新2.3.1版本)

quartz中,关键组成部分为:Job、Trigger、Scheduler

  • Scheduler: 调度程序维护作业详细信息(JobDetail)和触发器的注册表。一旦注册,调度程序将负责在相关的触发器触发时(调度时间到达时)执行作业。
  • Job:调度程序当触发器触发时,具体执行的作业任务。
  • Triiger:控制某个具体作业任务执行的规律,如什么时候开始执行,什么时候结束执行,期间执行间隔什么(每秒/分钟/小时等)。

1.1 Job与JobDetail介绍

job是调度器调度执行的具体任务,JobDetail是对这个job任务进行具体的修饰,比如这个任务的基本描述(description),唯一标识(JobKey)等信息。

1.1.1 Job

Job是一个接口,里面只有一个方法:execute(JobExecutionContext context)

 public interface Job {

    void execute(JobExecutionContext context)
        throws JobExecutionException;

}

任何需要任务调取程序执行的任务类都需要实现Job接口,execute(JobExecutionContext context)方法中实现具体任务。如图中MyJob类实现Job接口: public class MyJob implements Job {

public class MyJob implements Job {
    
    public void execute(JobExecutionContext jobExecutionContext) {
            JobKey jobKey=jobExecutionContext.getJobDetail().getKey();
            System.out.println(new SimpleDateFormat("yyyyMMddhhmmsssSSS").format(new Date())+":执行"+ jobKey.getName()+":"+jobKey.getGroup());
    }
}

myJob作业就是打印任务详情(JobDetail下面将介绍)。

1.1.2 JobDetail介绍

之前介绍过,任务调度程序Schedule必须是作业详细信息(JobDetail)和触发器,而JobDetail又是对Job(如MyJob)信息的具体修饰。

JobDetail的是一个接口,其实现类只有一个:

JobDetail可以通过JobBuilder类的build方法进行创建(最终创建的是JobDetailmple),JobBuilder结构如下:

在通过build()创建jobDetail之前,可以通过 newJob()方法管理具体的作业任务(如MyJob),设置描述信息widthDescription()、唯一标识withdIdentity(),执行数据usingJobData()等。

下面是创建JobDetail的代码:

            JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
                    .withDescription("my first job")
                    .withIdentity("jobName", "jobGroupName")
                    .build();

截止为止,我们已经对Quartz中三要素之一:job与JobDetail 有了一个了解,那么如何控制作业的执行,将有下面Trigger去实现。

1.2 Trigger介绍

Trigger就是一个触发器,控制某一任务具体在什么时候触发执行,比如在某一时间点, 或者在某一时间段,执行几次,执行间隔等。

Trigger实现有如下六种,其中已经实现类:CalendarIntervalTriggerImpl, CronTriggerImpl, DailyTimeIntervalTriggerImpl, SimpleTriggerImpl

1.2.1 SimpleTriggerImple 简单触发器

           根据其名字子可知,简单的触发器,是触发器类型中最简单的一种,其只能用于在给定的时间点触发作业,并可选地在指定的时间间隔内重复。下面是我创建一个SimpleTriggerImple:

  Trigger trigger = TriggerBuilder.newTrigger()//1创建TriggerBuilder
                    .withIdentity("triggerName", "triggerGroupName")//2定义触发器唯一标识
                    .withSchedule(repeatSecondlyForever())//3-1设置执行触发时间
                    .startAt(date)//3-2设置开始时间
                    .endAt(end)//3-2设置结束时间
                    .build();//4通过2-3的数据,生成SimpleTriggerBuilder

其中第2步,通过设置trigger的名字和分组生成唯一标识TriggerKey 源码:

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

重点第3步,通过设置不同的ScheduleBuilder,如果是生成SimpleTriggerImple,那这边必须是SimpleScheduleBuilder如果是CronTriggerImple则必须是CronScheduleBuilder,以此类推CalendarIntervalTriggerImpl=>CalendarIntervalScheduleBuilder,DailyTimeIntervalTriggerImpl=>DailyTimeIntervalcheduleBuilder。

其中repeatSecondlyForever()是SimpleScheduleBuilderstatic方法,返回值是SimpleScheduleBuilder实例,还有类似方法,如:repeatHourlyForever()、repeatMinutelyForTotalCount()、repeatMinutelyForTotalCount()等,具体方法作用我们通过其方法名称可以了解。

其他以withXXX方法,我们可以调用他们进行设置,比如执行间隔,触发器不生效时的处理策略等。详情可以通过quartz的api进行了解。

TriggerBuilder 最后通过build()方法生成一个SimpleTriggerImple(源码):

 public T build() {
        if (this.scheduleBuilder == null) {
            //当scheduleBuilder为空时,默认赋值SimpleScheduBuilder
            this.scheduleBuilder = SimpleScheduleBuilder.simpleSchedule();
        }
        //在通过SimpleScheduBuilder去生成simplerTriggerImple
        MutableTrigger trig = this.scheduleBuilder.build();
        trig.setCalendarName(this.calendarName);
        trig.setDescription(this.description);
        trig.setStartTime(this.startTime);
        trig.setEndTime(this.endTime);
        if (this.key == null) {
            this.key = new TriggerKey(Key.createUniqueName((String)null), (String)null);
        }

        trig.setKey(this.key);
        if (this.jobKey != null) {
            trig.setJobKey(this.jobKey);
        }

        trig.setPriority(this.priority);
        if (!this.jobDataMap.isEmpty()) {
            trig.setJobDataMap(this.jobDataMap);
        }

        return trig;
    }

simpleTriggerImple触发器创建完后,如果将JobDetailImple与SimpleTriggerImple,注册到调度程序:

    /**
     * SimpleTrigger简单定时任务,定点执行。
     */
    public static void demo1() throws ParseException {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = null;
        try {
            //DateBuilder 获取各种时间点的方法类
            Date startDate=DateBuilder.evenSecondDate(new SimpleDateFormat("yyyyMMddHHmmssSSS").parse("20190728234700341"));
            System.out.println(startDate);
            Date endDate=DateBuilder.evenMinuteDate(new SimpleDateFormat("yyyyMMddHHmmssSSS").parse("20190728235000341"));
            System.out.println(endDate);
            scheduler = schedulerFactory.getScheduler();
            JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
                    .withDescription("my first job")
                    .withIdentity("jobName", "jobGroupName")
                    .build();
             Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("triggerName", "triggerGroup")
                    .withSchedule(repeatSecondlyForever())
                    .startAt(startDate)
                    .endAt(endDate)
                    .build();
            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.start();
        } catch (SchedulerException e1) {
            e1.printStackTrace();
        }
    }

此方法运行的结果是:程序启动后,将在当时间为 2019-07-28 23:47:00.341 执行,每秒执行一次,无限循环,且在时间为2019-07-28 23:50:00.341一下分钟前结束。执行结果如下,47分1秒开始执行,50分59秒结束:


Sun Jul 28 23:47:01 CST 2019
Sun Jul 28 23:51:00 CST 2019
20190728114701020:执行jobName:jobGroupName
20190728114702001:执行jobName:jobGroupName
20190728114703001:执行jobName:jobGroupName
......
20190728115057000:执行jobName:jobGroupName
20190728115058000:执行jobName:jobGroupName
20190728115059000:执行jobName:jobGroupName

其中DateBuilder类中提供了很多获取时间的方法,具体可以查看API文档。


1.2.2 CronTriggerImple介绍

cronTrrigger触发器,相对于Simple触发器,可以进行复杂的调度执行计划,通过使用Cron定义具体的执行计划,通过定义不同的cron表达式,cronTrigger几乎可以替代其他的触发器。CronTriggerImple创建方式类似于SimpleTriggerImple,其却区别在于上节中的第三步,ScheduleBuilder的设置。其中创建CronTriggerImple则必须通过设置CronScheduleBuilder,详见代码:


    /**
     * CromTrigger定时任务
     */
    public static final void demo2()
    {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = null;
        try {
            scheduler = schedulerFactory.getScheduler();
            JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
                    .withDescription("my first job")
                    .withIdentity("jobName", "jobGroupName")
                    .build();
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("triggerName", "triggerGroup")
                    .withSchedule(cronSchedule("*/2 * * * * ?"))
                    .build();
            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.start();
        } catch (SchedulerException e1) {
            e1.printStackTrace();
        }
    }

与SimpleTrigger的区别在于withSchedule() 中使用了cronSchedule("*/2 * * * * ?") ,cronSchedule方法是CronScheduleBuilder中的static方法。类似cronSchedule的方法还有如下图中红框中的方法,通过其方法名可以了解方法的作用,详情见CronScheduleBuilder的API:

最常用的方法就是cronSchedule(String cron)。通过定义cron字符串,定义任务的执行计划,如“*/2 * * * * ?”:每两秒钟执行一次。执行结果如下:

20190729121536085:执行jobName:jobGroupName
20190729121538001:执行jobName:jobGroupName
20190729121540002:执行jobName:jobGroupName
20190729121542002:执行jobName:jobGroupName
20190729121544002:执行jobName:jobGroupName
......

其中cron编写使用规则可以参考:https://blog.csdn.net/fanrenxiang/article/details/80361582

1.2.3 DailyTimeIntervalTriggerImple与CalendarIntervalTriggerImple介绍

  • DailyTimeIntervalTriggerImple是DailyTimeIntervalTrigger的一个具体实现,用于基于每天重复的时间间隔触发。
    /**
     * DailyTimeIntervalTrigger定时任务
     */
    public static final void demo3()
    {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = null;
        try {
            scheduler = schedulerFactory.getScheduler();
            JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
                    .withDescription("my first job")
                    .withIdentity("jobName", "jobGroupName")
                    .build();
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("triggerName", "triggerGroup")
                    //通过设置执行次数和开始执行时间。当在每天的12点03分执行2次后结束,其他方式详情参考DailyTimeIntervalScheduleBuilder api
                    .withSchedule(dailyTimeIntervalSchedule().startingDailyAt(TimeOfDay.hourAndMinuteOfDay(12,3)).endingDailyAfterCount(2))
                    .build();
            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.start();
        } catch (SchedulerException e1) {
            e1.printStackTrace();
        }
    }

运行结果:

20190729120300085:执行jobName:jobGroupName
20190729120400001:执行jobName:jobGroupName
20190730120300008:执行jobName:jobGroupName
20190730120400005:执行jobName:jobGroupName
  • CalendarIntervalTriggerImple是CalendarIntervalTrigger一个具体的触发器,用于根据重复的日历时间间隔触发作业。
    /**
     * CalendarIntervalTrigger定时任务
     */
    public static final void demo4()
    {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = null;
        try {
            scheduler = schedulerFactory.getScheduler();
            JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
                    .withDescription("my first job")
                    .withIdentity("jobName", "jobGroupName")
                    .build();
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("triggerName", "triggerGroup")
                    //当前时间执行,每隔三秒执行一次。其他方法详情参考CalendarIntervalScheduleBuilder api
                    .withSchedule(calendarIntervalSchedule().withInterval(3, DateBuilder.IntervalUnit.SECOND))
                    .build();
            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.start();
        } catch (SchedulerException e1) {
            e1.printStackTrace();
        }
    }

运行结果:

20190730122545578:执行jobName:jobGroupName
20190730122548492:执行jobName:jobGroupName
20190730122551494:执行jobName:jobGroupName
20190730122554493:执行jobName:jobGroupName

很显然,这其他触发器的使用方式类似,详情请参考DailyTimeIntervalScheduleBuilder与CalendarIntervalScheduleBuilder。

根据上面几节的介绍,我们不难发现,很多核心对象的创建都是来至于XXXBuilder,比如:

  • JobDetialImple 通过JobBuilder的build方法生成
  • XXXTriggerImple 通过 TriggerBuilder的build方法(最终还是调用XXXXScheduleBuilder的build方法)生成
  • XXXXScheduleBuilder
  • 获取特定条件的日期,通过DateBuilder中很多方法可以获取。

综上,有关Job、JobDeatil、Trigger介绍已经告一段落,下面我将介绍如何将运行时参数传递到quartz作业,以及如何在作业中维护状态。

1.3作业参数和作业状态

保存作业的参数和状态一般使用JobDataMap,他的作用就是保存作业实例的状态信息。将作业添加到调度程序时,JobDataMap实例只存储一次。在每次执行带有@PersistJobDataAfterExecution注释的作业之后,它们还会被重新持久化,如果执行作业上没有此注解,将不会对数据进行持久化。JobDataMap实例也可以用触发器存储。如果您的作业存储在调度程序中,供多个触发器定期/重复使用,但是每个独立的触发器都希望为作业提供不同的数据输入,那么这种方法非常有用。

在作业实例中添加JobDataMap 用于保存作业参数

@PersistJobDataAfterExecution//只有此注解,才会对JobDataMap数据持久化
@DisallowConcurrentExecution
public class ColorJob implements Job {

    public static final String  FAVORITE_COLOR="favoriteColor";
    public static final String  EXECUTION_COUNT="executionCount";
    private int count=1;
    public void execute(JobExecutionContext jobExecutionContext) {

        JobDataMap data = jobExecutionContext.getJobDetail().getJobDataMap();//获取当前作业的参数
        String favoriteColor = data.getString(FAVORITE_COLOR);//根据key值获取对应参数获状态
        Integer executionCount =data.getInt(EXECUTION_COUNT);
        JobKey jobKey=jobExecutionContext.getJobDetail().getKey();
        System.out.println(new SimpleDateFormat("yyyyMMddhhmmssSSS").format(new Date())+":执行"+ jobKey.getName()+":"+jobKey.getGroup()+"  favorite color is " + favoriteColor + "\n" +
                "  execution count (from job map) is " + executionCount + "\n" +
                "  execution count (from job member variable) is " + count);
        executionCount++;
        data.put(EXECUTION_COUNT, executionCount);//对JobDataMap参数状态进行更新
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}
    public static final void demo() {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = null;
        try {
            scheduler = schedulerFactory.getScheduler();
            JobDetail jobDetail1 = JobBuilder.newJob(ColorJob.class)
                    .usingJobData(ColorJob.FAVORITE_COLOR,"Green")//通过JobBuilder对接设置JobDataMap值
                    .usingJobData(ColorJob.EXECUTION_COUNT,1)
                    .withIdentity("job1", "jobGroupName")
                    .build();
            Trigger trigger1 = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1", "triggerGroup")
                    .withSchedule(simpleSchedule()
                            .withIntervalInSeconds(2)
                            .withRepeatCount(4))
                    .build();
//            jobDetail1.getJobDataMap().put(ColorJob.FAVORITE_COLOR, "Green");
//            jobDetail1.getJobDataMap().put(ColorJob.EXECUTION_COUNT, 1);
            JobDetail jobDetail2 = JobBuilder.newJob(ColorJob.class)
                    .usingJobData(ColorJob.FAVORITE_COLOR,"Red")
                    .usingJobData(ColorJob.EXECUTION_COUNT,1)
                    .withIdentity("job2", "jobGroupName")
                    .build();
            Trigger trigger2 = TriggerBuilder.newTrigger()
                    .withIdentity("trigger2", "triggerGroup")
                    .withSchedule(simpleSchedule()
                            .withIntervalInSeconds(1)
                            .withRepeatCount(4))
                    .build();
//            jobDetail2.getJobDataMap().put(ColorJob.FAVORITE_COLOR, "Red");//通过JobDetail设置JobDataMap值 
//            jobDetail2.getJobDataMap().put(ColorJob.EXECUTION_COUNT, 1);
            scheduler.scheduleJob(jobDetail1, trigger1);
            scheduler.scheduleJob(jobDetail2, trigger2);
            scheduler.start();
            //scheduler.shutdown(true);
        } catch (SchedulerException e1) {
            e1.printStackTrace();
        }
    }

上面代码中,通过两种方式设置作业参数,一种是通过JobBuilder设置,一种是通过JobDetail设置。其中通过JobBuilder的设置方式还有其他几种,如下图:

上面程序运行的结果如下:

20190730033215781:执行job2:jobGroupName  favorite color is Red
  execution count (from job map) is 1
  execution count (from job member variable) is 1
20190730033215783:执行job1:jobGroupName  favorite color is Green
  execution count (from job map) is 1
  execution count (from job member variable) is 1
20190730033216721:执行job2:jobGroupName  favorite color is Red
  execution count (from job map) is 2
  execution count (from job member variable) is 1
20190730033217674:执行job1:jobGroupName  favorite color is Green
  execution count (from job map) is 2
  execution count (from job member variable) is 1
20190730033217716:执行job2:jobGroupName  favorite color is Red
  execution count (from job map) is 3
  execution count (from job member variable) is 1
20190730033218718:执行job2:jobGroupName  favorite color is Red
  execution count (from job map) is 4
  execution count (from job member variable) is 1
20190730033219675:执行job1:jobGroupName  favorite color is Green
  execution count (from job map) is 3
  execution count (from job member variable) is 1
20190730033219719:执行job2:jobGroupName  favorite color is Red
  execution count (from job map) is 5
  execution count (from job member variable) is 1
20190730033221679:执行job1:jobGroupName  favorite color is Green
  execution count (from job map) is 4
  execution count (from job member variable) is 1
20190730033223675:执行job1:jobGroupName  favorite color is Green
  execution count (from job map) is 5
  execution count (from job member variable) is 1

job1和job2的任务的作业参数都被独立的保存下来了,并且进行了持久化。

1.4 任务处理异常JobExecutionException介绍

作业可以引发的异常,当执行作业时遇到错了,以指示Quartz调度程序在执行时发生错误,以及作业请求是否要立即重新启动(使用相同的JobExecutionContext,或作业是否希望被取消调度)

当作业执行期间抛出Exception时,我们可以通过JobExecutionException 封装Exception异常,并通过JobExecutionException实例,去执行具体的异常操作,比如,重新启动任务、停止当前任务的触发器,停止当前任务的所有触发器JobExecutionException的详情见下:

其中:

  • refireImmediately():是指立即重新启动当前作业
  • setUnscheduleFiringTrigger(boolean unscheduleTrigg):指停止当前作业的触发器,即终止当前作业
  • setUnscheduleAllTriggers(boolean unscheduleAllTriggs):指停止当前作业其他所有触发器,如果有3个触发器,执行此作业,其他2个触发器也会停止触发

实例代码如下:

MyExceptionJob1:

public class MyExceptionJob1 implements Job {
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobKey jobKey=context.getJobDetail().getKey();
        try {
            int zero = 0;
            int calculation = 4815 / zero;
        }
        catch (Exception e) {
            System.out.println(new SimpleDateFormat("yyyyMMddhhmmssSSS").format(new Date())+":执行Error in job"+ jobKey.getName()+":"+jobKey.getGroup());
            JobExecutionException e2 =
                    new JobExecutionException(e);
            // this job will refire immediately
            e2.refireImmediately();//立即重新启动作业
            System.out.println(e.fillInStackTrace());
            throw e2;
        }
    }
}

MyExceptionJob2:

public class MyExceptionJob2 implements Job {
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobKey jobKey=context.getJobDetail().getKey();
        try {
            int zero = 0;
            int calculation = 4815 / zero;
        }
        catch (Exception e) {
            System.out.println(new SimpleDateFormat("yyyyMMddhhmmssSSS").format(new Date())+":执行Error in job"+ jobKey.getName()+":"+jobKey.getGroup());
            JobExecutionException e2 =
                    new JobExecutionException(e);
            // Quartz will automatically unschedule
            // all triggers associated with this job
            // so that it does not run again
            //e2.setUnscheduleAllTriggers(true);//停止该作业的所有触发器
            e2.setUnscheduleFiringTrigger(true);//停止当前作业触发器
            throw e2;
        }
    }
}

主方法:

  public static final void demo() {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = null;
        try {
            scheduler = schedulerFactory.getScheduler();
            JobDetail jobDetail1 = JobBuilder.newJob(MyExceptionJob1.class)
                    .withIdentity("job1", "jobGroupName")
                    .build();
            Trigger trigger1 = TriggerBuilder.newTrigger()
                    .usingJobData(ColorJob.EXECUTION_COUNT,1)
                    .withSchedule(simpleSchedule()
                            .withIntervalInSeconds(2)
                            .repeatForever())
                    .build();
            JobDetail jobDetail2 = JobBuilder.newJob(MyExceptionJob2.class)
                    .withIdentity("job2", "jobGroupName")
                    .build();
            Trigger trigger2 = TriggerBuilder.newTrigger()
                    .withIdentity("trigger2", "triggerGroup")
                    .withSchedule(simpleSchedule()
                            .withIntervalInSeconds(1)
                            .repeatForever())
                    .build();
            JobDetail jobDetail3= JobBuilder.newJob(MyExceptionJob2.class)
                    .withIdentity("job3", "jobGroupName")
                    .build();
            Trigger trigger3 = TriggerBuilder.newTrigger()
                    .withIdentity("trigger3", "triggerGroup")
                    .withSchedule(simpleSchedule()
                            .withIntervalInSeconds(1)
                            .repeatForever())
                    .build();
            scheduler.scheduleJob(jobDetail1, trigger1);
            scheduler.scheduleJob(jobDetail2, trigger2);
            scheduler.scheduleJob(jobDetail3, trigger3);
            scheduler.start();
            //scheduler.shutdown(true);
        } catch (SchedulerException e1) {
            e1.printStackTrace();
        }
    }

执行结果:

20190730062017644:执行Error in jobjob3:jobGroupName
20190730062017650:执行Error in jobjob1:jobGroupName
java.lang.ArithmeticException: / by zero
20190730062017651:执行Error in jobjob2:jobGroupName
20190730062019304:执行Error in jobjob1:jobGroupName
java.lang.ArithmeticException: / by zero
20190730062021304:执行Error in jobjob1:jobGroupName
java.lang.ArithmeticException: / by zero
20190730062023304:执行Error in jobjob1:jobGroupName
java.lang.ArithmeticException: / by zero
20190730062025305:执行Error in jobjob1:jobGroupName
java.lang.ArithmeticException: / by zero
20190730062027304:执行Error in jobjob1:jobGroupName
java.lang.ArithmeticException: / by zero

可见,job2和job3都已经停止作业触发,只有job1在不停的重复执行。

1.3 Schedule介绍

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值