Quartz任务调度(1)概念例析快速入门-阿里云开发者社区

开发者社区> jeanheo> 正文

Quartz任务调度(1)概念例析快速入门

简介: <div class="markdown_views"> <h1 id="quartz框架需求引入">Quartz框架需求引入</h1> <p>在现实开发中,我们常常会遇到需要系统在特定时刻完成特定任务的需求,在<a href="http://blog.csdn.net/qwe6112071/article/details/50962613">《spring学习笔记(14)引
+关注继续查看

Quartz框架需求引入

在现实开发中,我们常常会遇到需要系统在特定时刻完成特定任务的需求,在《spring学习笔记(14)引介增强详解:定时器实例:无侵入式动态增强类功能》,我们通过引介增强来简单地模拟实现了一个定时器。它可能只需要我们自己维护一条线程就足以实现定时监控。但在实际开发中,我们遇到的需求会复杂很多,可能涉及多点任务调度,需要我们多线程并发协作、线程池的维护、对运行时间规则进行更细粒度的规划、运行线程现场的保持与恢复等等。如果我们选择自己来造轮子,可能会遇到许多难题。这时候,引入Quartz任务调度框架会是一个很好的选择,它:
1. 允许我们灵活而细粒度地设置任务触发时间,比如常见的每隔多长时间,或每天特定时刻、特定日子(如节假日),都能灵活地配置这些成相应时间点来触发我们的任务
2. 提供了调度环境的持久化机制,可以将任务内容保存到数据库中,在完成后删除记录,这样就能避免因系统故障而任务尚未执行且不再执行的问题。
3. 提供了组件式的侦听器、各种插件、线程池等。通过侦听器,我们可以引入我们的事件机制,配合上异步调用,既能为我们的业务处理类解耦,同时还可能提升用户体验,关于事件机制的基本概念、解耦特性及其异步调用可移步参考我的另一篇文章《spring学习笔记(15)趣谈spring 事件:实现业务逻辑解耦,异步调用提升用户体验 》

实例解析概念

在quartz中,有几个核心类和接口:Job、JobDetail、Trigger、Calendar、Scheduler。
下面我们结合实例来分析这些类的角色定位。
现在我们有一个新闻网站,它有一张任务日志表,记录着我们的不同任务,比如每隔三十分钟要根据文章的阅读量和评论量来生成我们的最热文章列表。在每天早晚12点,定时从其他新闻网站扒取一定量新闻,在每周一晚上12点到3点进行论坛封闭维护,而如果遇到节假日则不维护等。在以上实例中:

  1. 生成最热文章扒取新闻论坛封闭维护都是我们的Job,它定义了我们的需要执行的任务,是一个抽象的接口.
  2. 如果我们要具体到每隔三十分钟生成最热文章早晚12点扒取新闻等,在我们的具体任务执行时刻,我们就需要能够描述Job及其他相关静态信息jobDetail,它相当于是我们的Job+具体实现细节
  3. Trigger则描述了Job执行的时间触发规则,比如每隔三十分钟早晚12点
  4. 而这里的Calendar可以看成是一些日历特定时间点的集合,比如我们这里遇到节假日则不维护,节假日如国庆节、愚人节等等,都是我们的日历特定时间点。
  5. Scheduler就是我们的任务日志表,它是一个容器,记载(容纳)了我们前面的工作、触发时间等内容。

具体用法详解

通过实例,我们对quartz的核心类有了较清晰的功能定位,根据Quratz的不同版本,这几个核心类有较大改动,具体的操作不太相同,但是思路是相同的;比如1.+版本jar包中,JobDetail是个类,直接通过构造方法与Job类关联。SimpleTrigger和CornTrigger是类;在2.+jar包中,JobDetail是个接口,SimpleTrigger和CornTrigger是接口。下面详细地分析它们的具体用法:

1. Job

Job是一个接口,只有一个void execute(JobExecutionContext jec)方法,JobExecutionContext提供了我们的任务调度上下文信息,比如,我们可以通过JobExecutionContext获取job相对应的JobDetail、Trigger等信息,我们在配置自己的内容时,需要实现此类,并在execute中重写我们的任务内容。下面是我们的扒取新闻工作实例:

public class pickNewsJob implements Job {

    @Override
    public void execute(JobExecutionContext jec) throws JobExecutionException {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        System.out.println("在"+sdf.format(new Date())+"扒取新闻");
    }
}

2. JobDetail

Quartz在每次执行任务时,都会创建一个Job实例,并为其配置上下文信息,jobDetail有一个成员属性JobDataMap,存放了我们Job运行时的具体信息,在后面我们会详细提到。
1. 在1.+版本中,它作为一个类,常用的构造方法有:JobDetail(String name, String group, Class jobClass),我们需要指定job的名称,组别和实现了Job接口的自定义任务类。实例如JobDetail jobDetail =new JobDetail("job1", "jgroup1", pickNewsJob.class);
2. 而在2.+版本中,我们则通过一下方法创建 JobBuilder.newJob(自定义任务类).withIdentity(任务名称,组名).build();实例如JobDetail jobDetail = JobBuilder.newJob(pickNewsJob.class).withIdentity(“job1”,”group1”).build();`

3. Scheduler

先讲Scheduler,方便后讲解Trigger时测试。
Scheduler作为我们的“任务记录表”,里面(可以)配置大量的Trigger和JobDetail,两者在 Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。可以通过SchedulerFactory创建一个Scheduler实例。下面是使用Schduler的实例:

SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
//将jobDetail和Trigger注册到一个scheduler里,建立起两者的关联关系
scheduler.scheduleJob(jobDetail,Trigger);

scheduler.start();//开始任务调度

在一个scheduler被创建后,它处于”STAND-BY”模式,在触发任何job前需要使用它的start()方法来启动。同样的,如果我们想根据我们的业务逻辑来停止定时方案执行,可以使用scheduler.standby()方法

3. Trigger

Trigger描述了Job执行的时间触发规则,主要有SimpleTrigger和CronTrigger两个子类。

1. SimpleTrigger

如果嵌入事件机制只触发一次,或意图使Job以固定时间间隔触发,则使用SimpleTrigger较合适,它有多个构造函数,其中一个最复杂的构造函数为:
SimpleTrigger(String name, String group, String jobName, String jobGroup, Date startTime, Date
endTime, int repeatCount, long repeatInterval)
参数依次为触发器名称、触发器所在组名、工作名、工作所在组名、开始日期、结束日期、重复次数、重复间隔。
1. 如果我们不需同时设置这么多属性,可调用其他只有部分参数的构造方法,其他参数也可以通过set方法动态设置。
2. 这里需要注意的是,如果到了我们设置的endTime,即时重复次数repeatCount还没有达到我们预设置的次数。任务也不会再此执行。
下面是1.+版本的创建实例

//创建一个触发器,使任务从现在开始、每隔两秒执行一次,共执行10次
SimpleTrigger simpleTrigger = new SimpleTrigger("triiger1");//至少需要设置名字以标识当前触发器,否则在调用时会报错
simpleTrigger.setStartTime(new Date());
simpleTrigger.setRepeatInterval(2000);
simpleTrigger.setRepeatCount(10);

下面是2.+版本的创建实例

SimpleTrigger simpleTrigger = TriggerBuilder
                .newTrigger()
                .withIdentity("trigger1")//配置触发器名称
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(10, 2))//配置重复次数和间隔时间
                .startNow()//设置从当前开始
                .build();//创建操作

通过TriggerBuilder,我们可以通过方法方便地配置触发器的各种参数。

2. CronTrigger

通过Cron表达式定义复杂的时间调度方案,具体内容我们在下一篇详细提到

4. Calendar

在实际的开发中,我们可能需要根据节假日来调整我们的任务调度方案。实例如下:

//第一步:创建节假日类
 // ②四一愚人节    
Calendar foolDay = new GregorianCalendar();    //这里的Calendar是 java.util.Calendar。根据当前时间所在的默认时区创建一个“日子”
foolDay.add(Calendar.MONTH, 4);    
foolDay.add(Calendar.DATE, 1);    
// ③国庆节    
Calendar nationalDay = new GregorianCalendar();    
nationalDay.add(Calendar.MONTH, 10);    
nationalDay.add(Calendar.DATE, 1);  

//第二步:创建AnnualCalendar,它的作用是排除排除每一年中指定的一天或多天
AnnualCalendar holidays = new AnnualCalendar();
//设置排除日期有两种方法
// 第一种:排除的日期,如果设置为false则为包含(included)   
holidays.setDayExcluded(foolDay, true);  
holidays.setDayExcluded(nationalDay, true); 
//第二种,创建一个数组。
ArrayList<Calendar> calendars = new ArrayList<Calendar>();
calendars.add(foolDay);
calendars.add(nationalDay);
holidays.setDaysExcluded(calendars);

//第三步:将holidays添加进我们的触发器
simpleTrigger.setCalendarName("holidays");

//第四步:设置好然后需要在我们的scheduler中注册
scheduler.addCalendar("holidays",holidays, false,false);,注意这里的第一个参数为calendarName,需要和触发器中添加的Calendar名字像对应。

在这里,除了可以使用AnnualCalendar外,还有CronCalendar(表达式),DailyCalendar(指定的时间范围内的每一天),HolidayCalendar(排除节假日),MonthlyCalendar(排除月份中的数天),WeeklyCalendar(排除星期中的一天或多天)

至此,我们的核心类基本讲解完毕,下面附上我们的完整测试代码:

/*********************1.+版本*********************/
public class pickNewsJob implements Job {

    @Override
    public void execute(JobExecutionContext jec) throws JobExecutionException {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        System.out.println("在" + sdf.format(new Date()) + "扒取新闻");
    }

    public static void main(String args[]) throws SchedulerException {
        JobDetail jobDetail =new JobDetail("job1", "jgroup1", pickNewsJob.class); 
        SimpleTrigger simpleTrigger = new SimpleTrigger("triiger1");
        simpleTrigger.setStartTime(new Date());
        simpleTrigger.setRepeatInterval(2000);
        simpleTrigger.setRepeatCount(10);
        simpleTrigger.setCalendarName("holidays");

        //设置需排除的特殊假日
        AnnualCalendar holidays = new AnnualCalendar();
        // 四一愚人节
        Calendar foolDay = new GregorianCalendar(); // 这里的Calendar是 ava.util.Calendar。根据当前时间所在的默认时区创建一个“日子”
        foolDay.add(Calendar.MONTH, 4);
        foolDay.add(Calendar.DATE, 1);
        // 国庆节
        Calendar nationalDay = new GregorianCalendar();
        nationalDay.add(Calendar.MONTH, 10);
        nationalDay.add(Calendar.DATE, 1);
        //排除的日期,如果设置为false则为包含(included)
        holidays.setDayExcluded(foolDay, true);
        holidays.setDayExcluded(nationalDay, true);
        /*方法2:通过数组设置
        ArrayList<Calendar> calendars = new ArrayList<Calendar>();
        calendars.add(foolDay);
        calendars.add(nationalDay);
        holidays.setDaysExcluded(calendars);*/

        //创建scheduler
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        scheduler.addCalendar("holidays", holidays, false, false);

        scheduler.scheduleJob(jobDetail, simpleTrigger);
        scheduler.start();

    }
}
/*******************2.+版本***************/
public class pickNewsJob implements Job {

    @Override
    public void execute(JobExecutionContext jec) throws JobExecutionException {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        System.out.println("在"+sdf.format(new Date())+"扒取新闻");
    }

    public static void main(String args[]) throws SchedulerException {
        JobDetail jobDetail = JobBuilder.newJob(pickNewsJob.class)
                .withIdentity("job1", "group1").build();
        SimpleTrigger simpleTrigger = TriggerBuilder
                .newTrigger()
                .withIdentity("trigger1")
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(10, 2))
                .startNow()
                .build();

        //设置需排除的特殊假日
        AnnualCalendar holidays = new AnnualCalendar();
        // 四一愚人节
        Calendar foolDay = new GregorianCalendar(); // 这里的Calendar是 ava.util.Calendar。根据当前时间所在的默认时区创建一个“日子”
        foolDay.add(Calendar.MONTH, 4);
        foolDay.add(Calendar.DATE, 1);
        // 国庆节
        Calendar nationalDay = new GregorianCalendar();
        nationalDay.add(Calendar.MONTH, 10);
        nationalDay.add(Calendar.DATE, 1);
        //排除的日期,如果设置为false则为包含(included)
        holidays.setDayExcluded(foolDay, true);
        holidays.setDayExcluded(nationalDay, true);
        /*方法2:通过数组设置
        ArrayList<Calendar> calendars = new ArrayList<Calendar>();
        calendars.add(foolDay);
        calendars.add(nationalDay);
        holidays.setDaysExcluded(calendars);*/

        //创建scheduler
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        scheduler.addCalendar("holidays", holidays, false, false);

        scheduler.scheduleJob(jobDetail, simpleTrigger);
        scheduler.start();
    }
}

可见,两个不同版本的主要区别在于JobDetail和Triiger的配置。

此外,除了使用scheduler.scheduleJob(jobDetail, simpleTrigger)来建立jobDetail和simpleTrigger的关联外,在1.+版本中的配置还可以采用如下所示方式

simpleTrigger.setJobName("job1");//jobName和我们前面jobDetail的的名字一致
simpleTrigger.setJobGroup("jgroup1");//jobGroup和我们之前jobDetail的组名一致
scheduler.addJob(jobDetail, true);//注册jobDetail,此时jobDetail必须已指定job名和组名,否则会抛异常Trigger's related Job's name cannot be null
scheduler.scheduleJob(simpleTrigger);//注册triiger必须在注册jobDetail之后,否则会抛异常Trigger's related Job's name cannot be null

这里还需要注意的是,如果我们使用scheduler.addCalendar("holidays", holidays, false, false)必须在向scheduler注册trigger之前scheduler.scheduleJob(simpleTrigger),否则会抛异常:Calendar not found: holidays

而在2.+版本中,我尝试在创建triiger时用forJob(“job1”, “jgroup1”)来绑定job名和组名

SimpleTrigger simpleTrigger = TriggerBuilder
    .newTrigger()
    .withIdentity("trigger1")
    .forJob("job1", "jgroup1")//在这里绑定
    .withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(10, 2))
    .startNow()
    .build();

//后面是一样的
scheduler.addJob(jobDetail, true);
scheduler.scheduleJob(simpleTrigger);

在运行时,却会抛出异常: Jobs added with no trigger must be durable.
显然是绑定失败了,目前暂未找到解决方法,如果有找到解决方法的朋友,恳请告诉我一下,十分感谢!

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
4146 0
开源调度框架Quartz最佳实践
开源调度框架Quartz最佳实践 Quartz是一个Java调度框架,当前的最新版本为2.2.1。 以Quartz 2.2.1版为例,Quartz最佳实践(用于生产系统)总结如下:1、跳过更新检查Quartz内置了一个“更新检查”特性,因此Quartz项目每次启动后都会检查官网,Quartz是否存在新版本。
890 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
4615 0
Spring Boot集成持久化Quartz定时任务管理和界面展示
前言 本文是对之前的一篇文章Spring+SpringMVC+mybatis+Quartz整合代码部分做的一个修改和补充, 其中最大的变化就是后台框架变成了Spring Boot。
2342 0
SpringBoot整合Quartz定时任务
记录一个SpringBoot 整合 Quartz 的Demo实例   POM.XML文件 org.quartz-scheduler quartz 1.
953 0
spring boot2 整合(四)定时任务Scheduled || Quartz并持久化
在进入正文前,我想把所有java可以实现的定时任务介绍一下,其实这个也是底层实现思路。 本教程大概目录: 线程等待实现定时任务 用Timer实现定时任务 用ScheduledExecutorService实现定时任务 Quartz 定时任务框架单...
1570 0
Quartz中扩展MethodInvokingJobDetailFactoryBean实现对任务调度的拦截
利用Quartz来实现对任务的调度已经被广泛地应用了,一个利用Quartz来进行任务调度的典型配置如下: [java] view plaincopy                      false  ...
651 0
Spring整合的quartz任务调度的实现方式
一、在web.xml中将配置文件的位置指定好。 Web.xml的配置如下: &lt;?xmlversion="1.0"encoding="UTF-8"?&gt; &lt;web-appversion="2.5"       xmlns="http://java.sun.com/xml/ns/javaee"       xmlns:xsi="http://www.w3.org/20
1058 0
+关注
80
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载