记录Quartz定时任务框架的学习过程(一)

参考文档:

https://blog.csdn.net/noaman_wgs/article/details/80984873

https://www.w3cschool.cn/quartz_doc/quartz_doc-2put2clm.html

Quartz定时框架

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

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

举例

拿火车票购票来说,当你下单后,后台就会插入一条待支付的task(job),一般是30分钟,超过30min后就会执行这个job,去判断你是否支付,未支付就会取消此次订单;当你支付完成之后,后台拿到支付回调后就会再插入一条待消费的task(job),Job触发日期为火车票上的出发日期,超过这个时间就会执行这个job,判断是否使用等。

基本组成

  • 调度器(Scheduler)
  • 触发器(Trigger)
  • 任务(Job)

调度器

将触发器和任务组合加入到调度器中,调度器来决定该任务的执行。

调度器图例

触发器

Trigger有两个:

  • SingleTrigger:可以方便的实现一系列的触发机制。
  • CronTrigger:和Cron表达式一块儿使用

触发器用来指定什么时间开始触发,触发多少次,每隔多久触发一次.

触发器图例

任务

Job 其实是由 3 个部分组成:

  • JobDetail: 用于描述这个Job是做什么的
  • 实现Job的类: 具体干活的
  • JobDataMap: 给 Job 提供参数用的
任务图例

<font color=red>每个任务JobDetail可以绑定多个Trigger,但一个Trigger只能绑定一个任务。</font>

简单案例

创建一个Maven工程,添加坐标:

        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.0</version>
        </dependency>

创建一个JobDetail

public class MailJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDetail jobDetail = context.getJobDetail();

        String email = jobDetail.getJobDataMap().getString("email");

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("hh:mm:ss");
        String now = simpleDateFormat.format(new Date());

        System.out.printf("给邮件地址 %s 发出了一封定时邮件, 当前时间是: %s%n", email, now);
    }
}

创建一个Test

public class TestQuartz {

    public static void main(String[] args) {
        try {
            //创建一个调度器
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            //创建触发器的触发规则
            SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder
                    .simpleSchedule()
                    .withIntervalInSeconds(2)
                    .withRepeatCount(10);

            //创建一个触发器
            SimpleTrigger trigger = TriggerBuilder
                    .newTrigger()
                    .withIdentity("trigger1", "group1")  //标识区别唯一的一个触发器
                    .startNow()
                    .withSchedule(scheduleBuilder)
                    .build();

            //创建job,指定JobDetail,datamap给jobdetail传值
            JobDetail jobDetail = JobBuilder.newJob(MailJob.class)
                    .withIdentity("mailjob1", "mailgroup")  //标识区别唯一的一个任务
                    .usingJobData("email", "admin@10086.com")
                    .build();

            //用jobdatamap修改email
            jobDetail.getJobDataMap().put("email", "admin@taobao.com");

            //调度加入这个job和触发器
            scheduler.scheduleJob(jobDetail, trigger);

            //调度启动  触发定时
            scheduler.start();

            //等待20秒,让前面的任务执行完成后,在关闭调度器
            try {
                Thread.sleep(20 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            scheduler.shutdown(true);

        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}

执行main函数打印如下结果

简单示例执行结果

任务(Job)

并行

默认情况下,无论上一次任务是否结束,只要设置的时间到了,下一次任务就会重新开启一个新的线程执行。

比如在备份文件的时候,我们当然不会让并行执行,那么添加注解 @DisallowConcurrentExecution 在任务的JobDetail类上,即可让任务单线程执行。

当任务的执行时间超过任务的时间间隔时,下一个任务会等待上一个任务结束,并非丢弃。

异常

异常的处理分为两种:

  1. 当发生异常时,停止这个任务
  2. 当发生异常时,调整任务,重新运行

示例

创建JobException1(情况1):

public class ExceptionJob1 implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        int i = 0;
        try {
            System.out.println(100/i);
        }catch (Exception e){
            System.out.println("发生异常,取消这个job对应的所有调度");
            JobExecutionException exception = new JobExecutionException(e);
            exception.setUnscheduleAllTriggers(true);
            throw exception;
        }
    }
}

创建JobException2(情况2):

public class ExceptionJob2 implements Job {

    static int i = 0;
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        try {
            System.out.println(100/i);   //主动报错抛出异常
        }catch (Exception e){
            System.out.println("发生了异常,修改一下参数,立即重新执行");
            i = 1;
            JobExecutionException exception = new JobExecutionException(e);
            exception.setRefireImmediately(true);
            throw exception;
        }
    }
}

保留简单案例中Test类,修改 JobDetail jobDetail = JobBuilder.newJob(MailJob.class) 为JobException1,JobException2,分别运行看到结果如下:

异常情况1
异常情况2

Job中断

在任务进行时,我们有时候需要在达到某个条件之后需要终端这个任务的进行,那么这个Job需要实现 InterruptableJob 接口,来实现对任务的中断。

示例

创建任务

//必须实现InterruptableJob 而非 Job才能够被中断
public class StoppableJob implements InterruptableJob {
    private boolean stop = false;
    public void execute(JobExecutionContext context) throws JobExecutionException {
 
        while(true){
 
            if(stop)
                break;
            try {
                System.out.println("每隔1秒,进行一次检测,看看是否停止");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("持续工作中。。。");
        }
     
    }
    public void interrupt() throws UnableToInterruptJobException {
        System.out.println("被调度叫停");
        stop = true;
    }
}

创建Test

public static void main(String[] args) {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
          
        Trigger trigger = newTrigger().withIdentity("trigger1", "group1")
            .startNow()
            .build();
  
        //定义一个JobDetail
        JobDetail job = newJob(StoppableJob.class)
            .withIdentity("exceptionJob1", "someJobGroup")
            .build();
          
        //调度加入这个job
        scheduler.scheduleJob(job, trigger);
  
        //启动
        scheduler.start();
        Thread.sleep(5000);
        System.out.println("过5秒,调度停止 job");
        //key 就相当于这个Job的主键
        scheduler.interrupt(job.getKey());
         
        //等待20秒,让前面的任务都执行完了之后,再关闭调度器
        Thread.sleep(20000);
        scheduler.shutdown(true);  
    }

<font color=red>说明:</font>实现 InterruptableJob 是为了让任务中有一个内置的 interrupt 来进行中断操作,并不是整整的中断,需要根据自身业务做标识进行实现,

触发器(Trigger)

SimpleTrigger

简单案例中已经对触发器继续了一些应用,在这里对一些常用的定时进行举例:

  • 下一个8秒的倍数(只针对开始时间)

    //从当前开始,8的倍数秒开始执行
    Date startTime = DateBuilder.nextGivenSecondDate(null, 8);
    SimpleTrigger trigger = (SimpleTrigger) newTrigger().withIdentity("trigger1", "group1").startAt(startTime).build();
    
  • 10秒后开始执行

    Date startTime = DateBuilder.futureDate(10, IntervalUnit.SECOND);
    SimpleTrigger trigger = (SimpleTrigger) newTrigger().withIdentity("trigger1", "group1").startAt(startTime).build();
    
  • 累计9次,间隔3秒

    SimpleScheduleBuilder scheduleBuilder =SimpleScheduleBuilder.simpleSchedule()
        .withRepeatCount(8)
        .withIntervalInSeconds(3)
        
    SimpleTrigger trigger = (SimpleTrigger) newTrigger()
        .withIdentity("trigger1", "group1")
        .withSchedule(scheduleBuilder)
        .startNow()  //立即开始
        .build();
    
  • 永久执行,间隔1秒

    SimpleScheduleBuilder scheduleBuilder =SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInSeconds(1)
    
    SimpleTrigger trigger = (SimpleTrigger) newTrigger()
                        .withIdentity("trigger1", "group1")
                        .startAt(startTime)
                        .withSchedule(scheduleBuilder)
                        .build();
    

CronTrigger

和Cron表达式一起使用,推荐!!!

CronTrigger trigger = TriggerBuilder.newTrigger()
    .withIdentity("trigger1","group1")
    .withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?"))   //重点是这一句
    .build();

监听器(Listener)

关于Listener,在quartz中有Job监听器,Trigger监听器,Scheduler监听器,监听器会对很多操作进行监控,开发人员也可以在此时进行一些操作,例如日志打印等。

JobListener

我们将简单案例中的MailJob进行监听,创建Listener实现类,里面有一些必要的重写方法。

public class MailJobListener implements JobListener {
    @Override
    public String getName() {
        return "发送邮件进行定时操作";
    }

    /**
     * 准备执行时的操作
     * @param jobExecutionContext
     */
    @Override
    public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
        System.out.println("准备执行:\t" + jobExecutionContext.getJobDetail().getKey());
    }

    /**
     * 取消操作时的方法
     * @param jobExecutionContext
     */
    @Override
    public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
        System.out.println("取消操作:\t" + jobExecutionContext.getJobDetail().getKey());
    }

    /**
     * 执行结束时的操作
     * @param jobExecutionContext
     * @param e
     */
    @Override
    public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {
        System.out.println("执行结束:\t" + jobExecutionContext.getJobDetail().getKey());
        System.out.println();
    }
}

注册监听器到调度器中,监听器注册也分为三种:

  • 全局Job监听器
  • 组Job监听器
  • 唯一Job监听器

全局监听器注册

MailJobListener jobListener = new MailJobListener();
scheduler.getListenerManager().addJobListener(jobListener);

组Job监听器

MailJobListener jobListener = new MailJobListener();
GroupMatcher<JobKey> groupMatcher = GroupMatcher.jobGroupEquals(job.getKey().getGroup());   //组Job匹配
scheduler.getListenerManager().addJobListener(jobListener, groupMatcher);  //绑定

唯一Job监听器

MailJobListener jobListener = new MailJobListener();
KeyMatcher<JobKey> keyMatcher = KeyMatcher.keyEquals(job.getKey());
scheduler.getListenerManager().addJobListener(jobListener, keyMatcher);

TriggerListener和SchedulerListener

这两个监听器的实现方式和JobListener的实现方式类似,这里不在赘述,近介绍监听器内方法的作用。

TriggerListener

方法名 解释
triggerFired() 触发器被激发,job即将被运行时
vetoJobExecution() 触发器被激发,job即将被运行:triggerFired先执行,此方法后执行,如果返回true,则任务被终止
triggerMisfired() 当Trigger错过被激发时执行,比如当前时间有很多触发器都需要执行,但是线程池中的有效线程都在工作,那么有的触发器就有可能超时,错过这一轮的触发。
triggerComplete() 触发器完成后执行
getName() 返回一个字符串主要说明该监听器的名称等,一般用作日志记录

SchedulerListener

调度器监听器方法较多,这里选择几个常用的以做举例

方法名 解释
jobScheduled() 在有新的 JobDetail 部署时调用此方法。
jobUnscheduled 在有新的 JobDetail卸载时调用此方法
triggersPaused() 在Trigger被挂起时调用此方法
triggerResumed() 在Trigger被重新激活时调用此方法
schedulerStarted() 调度器启动完成之后执行
schedulerShuttingdown() 调度器正在被终止之后执行
schedulerShutdown() 调度器终止完成之后执行
schedulerStarting() 调度器正在被启动时执行

尾言

此篇主要介绍了Quartz定时任务框架的基本内容合大概用法,限于在main方法中测试是否有效。

包括定时任务三大基本组件:Job,Trigger,Scheduler和他们的监听器。

推荐阅读更多精彩内容