前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >记录Quartz定时任务框架的学习过程(一)

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

作者头像
青衫染红尘
发布2021-04-28 09:38:36
7890
发布2021-04-28 09:38:36
举报
文章被收录于专栏:Surpass' Blog

参考文档:

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 提供参数用的

每个任务JobDetail可以绑定多个Trigger,但一个Trigger只能绑定一个任务。

简单案例

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

代码语言:javascript
复制
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.0</version>
</dependency>

创建一个JobDetail

代码语言:javascript
复制
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

代码语言:javascript
复制
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):

代码语言:javascript
复制
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):

代码语言:javascript
复制
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,分别运行看到结果如下:

Job中断

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

示例

创建任务

代码语言:javascript
复制
//必须实现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

代码语言:javascript
复制
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);  
    }

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

触发器(Trigger)

SimpleTrigger

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

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

10秒后开始执行

累计9次,间隔3秒

永久执行,间隔1秒

CronTrigger

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

代码语言:javascript
复制
CronTrigger trigger = TriggerBuilder.newTrigger()
	.withIdentity("trigger1","group1")
	.withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?"))   //重点是这一句
	.build();

监听器(Listener)

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

JobListener

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

代码语言:javascript
复制
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监听器

全局监听器注册

代码语言:javascript
复制
MailJobListener jobListener = new MailJobListener();
scheduler.getListenerManager().addJobListener(jobListener);

组Job监听器

代码语言:javascript
复制
MailJobListener jobListener = new MailJobListener();
GroupMatcher<JobKey> groupMatcher = GroupMatcher.jobGroupEquals(job.getKey().getGroup());   //组Job匹配
scheduler.getListenerManager().addJobListener(jobListener, groupMatcher);  //绑定

唯一Job监听器

代码语言:javascript
复制
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和他们的监听器

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-04-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Quartz定时框架
    • 举例
      • 基本组成
        • 调度器
        • 触发器
        • 任务
      • 简单案例
        • 任务(Job)
          • 并行
          • 异常
          • Job中断
        • 触发器(Trigger)
          • SimpleTrigger
          • CronTrigger
        • 监听器(Listener)
          • JobListener
          • TriggerListener和SchedulerListener
        • 尾言
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档