Quartz作业调度框架

时间:2021-05-23 08:08:11

Quartz的核心概念 :调度器、触发器、作业 

调度器(Scheduler)

Scheduler负责管理Quartz的运行环境,Quartz它是基于多线程架构的,它启动的时候会初始化一套线程,这套线程会用来执行一些预置的作业。

要创建一个作业并能够被触发调用,必须在Scheduler上面注册一个JobDetail和Trigger。

Scheduler提携了所有Trigger和JobDetail,使它们协调工作。这些Trigger和JobDetail通过自身的name和group属性区分。

1、调度器的创建: 

方法一:

      Scheduler 由SchedulerFactory产生,我们可以通过以下方式获取Scheduler的实例:

     //ServletContext上下文中查找SchedulerFactory

     SchedulerFactory   factory = (SchedulerFactory)

    ServletActionContext.getServletContext().getAttribute("org.quartz.impl.StdSchedulerFactory.KEY");

    //获取Scheduler对象

    Scheduler defScheduler = factory.getScheduler();

方法二:

直接创建一个相应的工厂类,获得调度类

private Scheduler sched;

SchedulerFactory sf = new StdSchedulerFactory();
sched = sf.getScheduler();

2、写相应的run方法(非必须实现的方法),只是为了便于理解:

里面就涉及到 Job, JobDetail, CronTrigger


作业

Job

任务,其实就是一个接口。要创建一个任务,必须得实现这个接口。该接口只有一个execute方法,任务每次被调用的时候都会执行这个execute方法的逻辑。

public class TestJob impletemens org.quartz.Job{

         @Override

         public void execute(JobExecutionContext context) throws JobExecutionException{

                   // you business logic

                   // …

                 System.out.println("########### this is testJob running  ############");

     }

}

 

JobDetail

JobDetail 用来保存我们作业的详细信息。一个JobDetail可以有多个Trigger,但是一个Trigger只能对应一个JobDetail。下面是JobDetail的一些常用的属性和含义

 

参数名

类型

备注

name

String

任务的名称,必须

group

String

任务所在组,默认为DEFAULT

jobClass

Class

任务的实现类,必须

description

String

描述

jobDataMap

JobDataMap

用来给作业提供数据支持的数据结构

volatility

Boolean

重启应用之后是否删除任务的相关信息,默认false

durability

Boolean

任务完成之后是否依然保留到数据库,默认false

shouldRecover

Boolean

应用重启之后时候忽略过期任务,默认false

jobListeners

Set

监听器

 

JobDataMap

这是一个给作业提供数据支持的数据结构,使用方法和java.util.Map一样,非常方便。当一个作业被分配给调度器时,JobDataMap实例就随之生成。

Job有一个StatefulJob子接口,代表有状态的任务,该接口是一个没有方法的标签接口,其目的是让Quartz知道任务的类型,以便采用不同的执行方案。无状态任务在执行时拥有自己的JobDataMap拷贝,对JobDataMap的更改不会影响下次的执行。而有状态任务共享共享同一个JobDataMap实例,每次任务执行对JobDataMap所做的更改会保存下来,后面的执行可以看到这个更改,也即每次执行任务后都会对后面的执行发生影响。

正因为这个原因,无状态的Job可以并发执行,而有状态的StatefulJob不能并发执行,这意味着如果前次的StatefulJob还没有执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。有状态任务比无状态任务需要考虑更多的因素,程序往往拥有更高的复杂度,因此除非必要,应该尽量使用无状态的Job。

如果Quartz使用了数据库持久化任务调度信息,无状态的JobDataMap仅会在Scheduler注册任务时保持一次,而有状态任务对应的JobDataMap在每次执行任务后都会进行保存。

JobDataMap实例也可以与一个触发器相关联。这种情况下,对于同一作业的不同触发器,我们可以在JobDataMap中添加不同的数据,以便作业在不同时间执行时能够提供更为灵活的数据支持(学校上午放眼保健操录音第一版,下午放第二版)。

不管是有状态还是无状态的任务,在任务执行期间对Trigger的JobDataMap所做的更改都不会进行持久,也即不会对下次的执行产生影响。


CronTrigger配置格式:

格式: [秒] [分] [小时] [日] [月] [周] [年]

 序号 说明 
 是否必填  允许填写的值 允许的通配符 
 1  秒  是  0-59    , - * /
 2  分  是  0-59 
  , - * /
 3 小时  是  0-23   , - * /
 4  日  是  1-31   , - * ? / L W
 5  月  是  1-12 or JAN-DEC   , - * /
 6  周  是  1-7 or SUN-SAT   , - * ? / L #
 7  年  否  empty 或 1970-2099  , - * /

 

通配符说明:
* 表示所有值. 例如:在分的字段上设置 "*",表示每一分钟都会触发。
? 表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为"?" 具体设置为 0 0 0 10 * ?
- 表示区间。例如 在小时上设置 "10-12",表示 10,11,12点都会触发。
, 表示指定多个值,例如在周字段上设置 "MON,WED,FRI" 表示周一,周三和周五触发
/ 用于递增触发。如在秒上面设置"5/15" 表示从5秒开始,每增15秒触发(5,20,35,50)。 在月字段上设置'1/3'所示每月1号开始,每隔三天触发一次。
L 表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年[leap]), 在周字段上表示星期六,相当于"7"或"SAT"。如果在"L"前加上数字,则表示该数据的最后一个。例如在周字段上设置"6L"这样的格式,则表示“本 月最后一个星期五" 
W 表示离指定日期的最近那个工作日(周一至周五). 例如在日字段上设置"15W",表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发.如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 "1W",它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,"W"前只能设置具体的数字,不允许区间"-").

提示:

1.'L'和 'W'可以一组合使用。如果在日字段上设置"LW",则表示在本月的最后一个工作日触发

2.周字段的设置,若使用英文字母是不区分大小写的 MON 与mon相同

  # 序号(表示每月的第几个周几),例如在周字段上设置"6#3"表示在每月的第三个周六.注意如果指定"#5",正好第五周没有周六,则不会触发该配置

常用示例:

0 0 12 * * ? 每天12点触发
0 15 10 ? * * 每天10点15分触发
0 15 10 * * ? 每天10点15分触发
0 15 10 * * ? * 每天10点15分触发
0 15 10 * * ? 2005 2005年每天10点15分触发
0 * 14 * * ? 每天下午的 2点到2点59分每分触发
0 0/5 14 * * ? 每天下午的 2点到2点59分(整点开始,每隔5分触发)
0 0/5 14,18 * * ? 每天下午的 2点到2点59分(整点开始,每隔5分触发)
每天下午的 18点到18点59分(整点开始,每隔5分触发)
0 0-5 14 * * ? 每天下午的 2点到2点05分每分触发
0 10,44 14 ? 3 WED 3月分每周三下午的 2点10分和2点44分触发
0 15 10 ? * MON-FRI 从周一到周五每天上午的10点15分触发
0 15 10 15 * ? 每月15号上午10点15分触发
0 15 10 L * ? 每月最后一天的10点15分触发
0 15 10 ? * 6L 每月最后一周的星期五的10点15分触发
0 15 10 ? * 6L 2002-2005 从2002年到2005年每月最后一周的星期五的10点15分触发
0 15 10 ? * 6#3 每月的第三周的星期五开始触发
0 0 12 1/5 * ? 每月的第一个中午开始每隔5天触发一次
0 11 11 11 11 ? 每年的11月11号 11点11分触发(光棍节)

实例代码:

//该方法为非必须实现的方法,quart为本创建类的全局对象
public void run(List inList){		JobDetail job;		CronTrigger trigger;		String misName = null;		for (int i = 0; i < inList.size(); i++) {			Map dataMap = new HashMap();			dataMap = (Map) inList.get(i);			try{				misName = dataMap.get("MIS_NAME").toString();				quart.removeTrigdger(misName); //封装的一个方法				job = new JobDetail(misName, "group1", QuartzJob.class);				trigger = new CronTrigger(misName,"group1", misName, "group1", dataMap.get("MIS_CONDITION").toString());				trigger.setPriority(5);//设置优先级				job.getJobDataMap().put("class_name",						dataMap.get("MIS_CLASS").toString());				job.getJobDataMap().put("page_name",						dataMap.get("MIS_PAGE").toString());				job.getJobDataMap().put("mothed_name",						dataMap.get("MIS_MOTHED").toString());				job.getJobDataMap().put("mis_state",						dataMap.get("MIS_STATE").toString());				if (dataMap.get("MIS_FLAG") != null){					job.getJobDataMap().put("mis_flag",							dataMap.get("MIS_FLAG").toString());				}else{					job.getJobDataMap().put("mis_flag","");				}				sched.addJob(job, true);				sched.scheduleJob(trigger);				// 如果状态为暂停则调用暂停方法				if ("2".equals(dataMap.get("MIS_STATE").toString().substring(0, 1))) {					quart.pauseTrigger(misName);				}			}catch(Exception e){				_log.error("加载自动任务失败,任务名称为"+dataMap.get("MIS_NAME").toString()+",错误原因为:"+e+"  跳过该项加载!");			}		}		//如果还没有启动计划或计划为暂停时,启动计划				try {			if(!sched.isStarted() || sched.isInStandbyMode()){			    sched.start();			}		} catch (SchedulerException e) {			_log.error("启动自动任务失败,错误原因为:"+e);		}	}