Quartz 学习

入门 —— Quartz 需要了解的几个概念:

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

例程:

// QuartzUtil.java
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

public class QuartzUtil {
    public static void main(String[] args) {
        try {
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            Trigger trigger = newTrigger()
                    .withIdentity("trigger1", "group1")
                    .startNow()
                    .withSchedule(
                            simpleSchedule().withIntervalInSeconds(2)
                                    .withRepeatCount(5) // 0-5
                    ).build();
            JobDetail jobDetail = newJob(MailJob.class)
                    .withIdentity("mailJob", "mailgroup")
                    .usingJobData("email", "admin@10086.com")
                    .build();

            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.start();
            Thread.sleep(15000);    // 15秒
            scheduler.shutdown();
        } catch (SchedulerException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}
// MailJob.java
import org.quartz.*;

import java.text.SimpleDateFormat;
import java.util.Date;

public class MailJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDetail detail = context.getJobDetail();
        JobDataMap data = detail.getJobDataMap();
        String email = data.getString("email");

        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        String now = sdf.format(new Date());

        System.out.printf("时间%s,发送邮件%s%n", now, email);
    }
}

注:Trigger 和 Job 都可以通过 withIdentity() 函数进行分组,分组的目的是方便管理,例如同时启动或关闭同一组的 Trigger 或 Job。

Job 管理:

一、Job 的组成部分:

  • JobDetail:对具体的 Job 进行描述
  • Job 类:具体执行任务的 Job 类
  • JobDataMap:给 Job 提供参数数据

除了上面例程添加数据的方式,还可以在 JobDetail 对象创建好之后,通过获得 JobDataMap 对象,添加或修改参数数据:

jobDetail.getJobDataMap().put("email", "admin@taobao.org");
jobDetail.getJobDataMap().put("test", "test data");

二、Job 的并发

Quartz 默认是并发的。调度器 Scheduler 什么时候关闭,需要考虑 Job 本身的执行时间、Job 执行的间隔时间(一般是单线程执行时考虑)。
可以设置非并发执行:

@DisallowConcurrentExecution
public class MailJob implements Job

三、Job 异常处理

通常有两种办法:

  • 通知所有管理这个 Job 的调度器 Scheduler,停止运行它
  • 修改参数,重新运行

分别如下:

// ExceptionJobOne.java
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class ExceptionJobOne 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("异常,取消调度");
            JobExecutionException exception = new JobExecutionException(e);
            exception.setUnscheduleAllTriggers(true);
            throw exception;
        }
    }
}
// ExceptionJobTwo.java
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class ExceptionJobTwo implements Job {
    private 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;
        }
    }
}

中断 Job:

需要实现 InterruptableJob 接口,而不只是 Job 接口:

// StoppableJob.java
import org.quartz.*;

public class StoppableJob implements InterruptableJob {
    private boolean stop = false;

    @Override
    public void interrupt() {
        System.out.println("调度叫停");
        stop = true;
    }
    @Override
    public void execute(JobExecutionContext context) {
        // Job 一直运行,直到被叫停
        while (!stop) {
            try {
                System.out.println("一秒一次,检测是否停止");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("持续工作中");
        }
    }
}
// QuartzUtil.java
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;

public class QuartzUtil {
    public static void main(String[] args) {
        try {
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            Trigger trigger = newTrigger()
                    .withIdentity("trigger1", "group1")
                    .startNow()
                    .build();
            JobDetail jobDetail = newJob(StoppableJob.class)
                    .withIdentity("stoppableJob", "someGroup")
                    .build();

            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.start();
            Thread.sleep(5000); // 让 Job 飞一会

            scheduler.interrupt(jobDetail.getKey());    // group.job

            scheduler.shutdown();
        } catch (SchedulerException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

触发器:

触发器:指定什么时间开始触发、触发多少次、隔多久触发一次。

SimpleTrigger:

一、约定时间长度,触发执行(DateBuilder.nextGivenSecondDate 不准时):

// MailJob.java
import org.quartz.*;

import java.text.SimpleDateFormat;
import java.util.Date;

public class MailJob implements Job {
    @Override
    public void execute(JobExecutionContext context) {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        String now = sdf.format(new Date());

        System.out.printf("发送邮件, 执行时间:%s%n", now);
    }
}
// QuartzUtil.java
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;

public class QuartzUtil {
    public static void main(String[] args) {
        try {
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            // 到规定时间后,才触发执行。返回值为实际执行时间(时间很少是准的,一般就几秒钟)
            Date startTime = DateBuilder.nextGivenSecondDate(null, 8);

            SimpleTrigger trigger = (SimpleTrigger) newTrigger()
                    .withIdentity("trigger1", "group1")
                    .startAt(startTime).build();
            JobDetail jobDetail = newJob(MailJob.class)
                    .withIdentity("mailJob", "mailGroup").build();

            // 返回调度时间,也就是实际执行时间(scheduleTime==startTime)
            Date scheduleTime = scheduler.scheduleJob(jobDetail, trigger);
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
            System.out.printf("new Date:%s%n", sdf.format(new Date())); // 当前时间

            System.out.printf("startTime:%s%n", sdf.format(startTime));
            System.out.printf("任务:%s,scheduleTime:%s,运行%d次,间隔%d毫秒%n",
                    jobDetail.getKey(), sdf.format(scheduleTime),
                    trigger.getRepeatCount() + 1, trigger.getRepeatInterval());

            scheduler.start();
            Thread.sleep(20000); // 让 Job 飞一会
            scheduler.shutdown();
        } catch (SchedulerException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

二、规定时间长度,触发执行:

与上面代码的区别在于 startTime 的不同,效果上在于这个是准时的:

Date startTime = DateBuilder.futureDate(10,
                    DateBuilder.IntervalUnit.SECOND);

三、无限重复:

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

CronTrigger:

Cron 是 Linux 下的一个定时器,功能很强大,但是表达式较复杂。可以触发的情况更多。
CronTrigger 使用 Cron 表达式设置触发时间和次数。表达式由秒、分、时、日(Day of Month)、月、星期(Day of Week:星期几)、年(可选),7个部分组成,用空格分割。
其中字段中可出现的字符如下:
首先所有字段都可以使用的字符有:逗号(,)、减号(-)、星号(*)、斜杠(/)。
然后是每个字段自己能使用的字符,和有效范围:

  • 日(Day of Month):?、L、W、C。有效范围为 0 - 31
  • 月:有效范围为 1 - 12 或 JAN - DEC
  • 星期(Day of Week):?、L、C、#。有效范围为 1 - 7 或 SUN - SAT,所以 1 对应着星期天 SUN,2 对应星期一 MON,依此类推 —— 星期为 num - 1
  • 年:有效范围为 1970 - 2099

以上一些特殊字符的含义如下:

  • 逗号 ,:表示列出枚举值(即匹配一个列表)。例如 second 字段为 5,20,那么每分钟内,5 秒和 20 秒的时候,会分别触发一次
  • 减号 -:表示范围。例如 minute 字段为 5 - 20,那么每个小时的 5 分至 20 分,每分钟都会触发一次
  • 星号 *:匹配该字段的任意值(即每个值,因为匹配就触发了)。例如每分钟、每秒
  • 斜杠 /:用法是 —— 起始时间/间隔时间,即从起始时间开始,每隔一段时间都触发一次。例如,minute 字段为 5/20,那么每小时内,在 5 分、25 分、45 分时,分别触发一次

  • 问号 ?:如上,只能用在日(Day of Month)、星期(Day of Week)两个字段。它也匹配字段的任意值,但因为两个字段互相影响,所以实际为 "无意义的值",如其字面上的意思
  • 字母 L:只能用在日、星期,表示最后的一个指定日期。例如,星期为 5L,那么只在最后的一个星期四触发
  • 字母 W:只能出现在 Day of Month,表示有效工作日(Work Day),即周一至周五,那么调度器会在离指定日期最近的有效工作日触发 —— 如果 Day of Month 是 5W。如果 5 日是星期六,选择就近的工作日星期五,所以 4 日触发;如果 5 日是星期日,就在星期一(6 号)触发;如果 5 日是 Work Day,那么就在 5 日触发。此外,W 就近寻找时,不会跨月份
  • LW 连用:Latest Work Day of Month,某月最后的一个有效工作日(但最后一个工作日不一定是星期五)
  • 井号 #:只能出现在 Day of Week,用于确定每个月,第几个星期几(游戏规则是 DayofWeek#num)。如 4#2,表示某月的第 2 个星期 3

Cron 表达式的例子就算了,网上都有。下面是 CronTrigger 的例程:

// MailJob.java
import org.quartz.Job;
import org.quartz.JobExecutionContext;

import java.text.SimpleDateFormat;
import java.util.Date;

public class MailJob implements Job {
    @Override
    public void execute(JobExecutionContext context) {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        System.out.println(sdf.format(new Date()));
    }
}
// QuartzUtil.java
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.StdSchedulerFactory;

import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;

public class QuartzUtil {
    public static void main(String[] args) {
        try {
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            CronTrigger trigger = newTrigger()
                    .withIdentity("trigger1", "group1")
                    .withSchedule(cronSchedule("0/2 * * * * ?")).build();   // 每两秒触发一次
            JobDetail jobDetail = newJob(MailJob.class)
                    .withIdentity("mailJob", "mailGroup").build();

            scheduler.scheduleJob(jobDetail, trigger);
            System.out.println(trigger.getCronExpression());

            scheduler.start();
            Thread.sleep(20000); // 让 Job 飞一会
            scheduler.shutdown();
        } catch (SchedulerException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

监听器 Listener

quartz 的监听器有 Job 监听器、Trigger 监听器、Scheduler 监听器,从而可以对不同层面进行监控。用得较多的是 Job 监听器,用于监听是否执行了,下面只介绍它。

Job 监听器:

监听器类需要实现 JobListener 接口,其有四个抽象方法:

  • String getName():返回一个字符串,对 JobListener 的名称进行说明。对于注册为全局的监听器,getName() 主要用于记录日志;对于特定 Job 的 JobListener,注册在 JobDetail 上的监听器名称,必须匹配监听器类 getName() 方法的返回值(说白了,这里好像不用管了)
  • void jobToBeExecuted(JobExecutionContext):Job 执行前调用
  • void jobExecutionVetoed(JobExecutionContext):Job 执行前,被取消执行时(Job 中断、TriggerListener 否决等)调用
  • void jobWasExecuted(JobExecutionContext, JobExecutionException):执行后调用
// MailJobListener.java
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;

public class MailJobListener implements JobListener {
    @Override
    public String getName() {
        return "listener of mail job";
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        System.out.println("准备执行:" + context.getJobDetail().getKey());
    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
        System.out.println("取消执行:" + context.getJobDetail().getKey());
    }

    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        System.out.println("执行结束:" + context.getJobDetail().getKey());
    }
}
// QuartzUtil.java 和上一个例程相比,只有下面的一些改动
// MailJob 监听
MailJobListener mailJobListener = new MailJobListener();
KeyMatcher<JobKey> keyMatcher = KeyMatcher.keyEquals(mailJobDetail.getKey());
scheduler.getListenerManager().addJobListener(mailJobListener, keyMatcher);

持久化

posted @ 2019-05-21 11:29  不抛弃,不放弃  阅读(403)  评论(0编辑  收藏  举报