定时器2——定时框架Quartz1

1. Quartz的几个常用API

1.1 Scheduler——与调度程序交互的主程序接口

        Scheduler调度程序任务执行计划表,只有安排进行执行计划的任务job(通过scheduler.schedulerjob方法安排进行执行计划),当它预先定义的执行时间到了的时候(任务触发trigger),该任务才会执行。

Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

1.2 Job

1.2.1 介绍

        我们预先定义的希望在未来时间能被调度程序执行的任务类,可以自定义。

        job是工作任务调度的接口,任务类需要实现该接口,该接口中定义execute方法,类似JDK提供的TimeTask类的run方法,在里面编写任务执行的业务逻辑。

例如:

public class HelloJob implements Job {

	@Override
	public void execute(JobExecutionContext arg0) throws JobExecutionException {
		Calendar calendar = Calendar.getInstance();
		Date time = calendar.getTime();
		String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
		System.out.println("任务开始执行,时间是:" + format);
	}

}

1.2.2 Job实例在Quartz中的生命周期

        每次调度器执行job的时候,它在调用execute方法前会创建一个新的job实例,当调用完成后,关联的Job对象实例会被释放,释放的实例会被垃圾回收机制回收。

例如:我们可以在Job中的构造函数中打印一句话来验证该生命周期

package cn.bjc.quartz.job;

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

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class HelloJob implements Job {
	
	public HelloJob() {
		super();
		System.out.println("hello Job");
	}

	@Override
	public void execute(JobExecutionContext arg0) throws JobExecutionException {
		Calendar calendar = Calendar.getInstance();
		Date time = calendar.getTime();
		String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
		System.out.println("任务开始执行,时间是:" + format);
	}

}

运行结果如图:

我们发现,每次执行构造函数都会打印一句话,这说明在调用execute方法前会创建一个新的job实例

1.2.3 有状态的Job和无状态的Job

无状态的Job:默认的Job状态,每次调用时都会创建一个新的JobDataMap,这个有点类似Struts2中的值栈。

例如:我们在jobDetail中传入参数count,默认值为0,作为计数器,然后在job中打印每次运行之后的count值

HelloJob.java

​
package cn.bjc.quartz.job;

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

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.TriggerKey;

public class HelloJob implements Job {
	
	// 定义计数器
	private Integer count;
	public void setCount(Integer count) {
		this.count = count;
	}


	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		
		System.out.println("count = " + (++count)); // 计数器+1,并且打印出来
		
		// 将计数器放回到JobDetail的JobDataMap中
		context.getJobDetail().getJobDataMap().put("count", count);
		
		Calendar calendar = Calendar.getInstance();
		Date time = calendar.getTime();
		String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
		System.out.println("任务开始执行,时间是:" + format);
	}

}

HelloSchedulerDemo.java

package cn.bjc.quartz.main;

import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

import cn.bjc.quartz.job.HelloJob;

public class HelloSchedulerDemo {

	public static void main(String[] args) throws Exception {
		// 1. 调度器(Scheduler),从工厂获取
		Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); // 内部默认是new StdSchedulerFactory()
		// 2. 任务实例(JobDetail),通过JobBuilder创建
		JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)	// 加载任务类,与HelloJob绑定
							.withIdentity("job1", "group1")	// 参数一:任务的名称(唯一实例);参数二:任务组的名称,对任务进行分组
							.usingJobData("msg", "日志打印")  // 传递参数到job
							.usingJobData("count", 0)		// 传入计数器,默认值为0
							.build();
		// 3. 触发器(Trigger)
		Trigger trigger = TriggerBuilder.newTrigger()
							.withIdentity("trigger1", "group1") // 参数一:触发器的名称;参数二:触发器组的名称,对触发器进行分组 
							.startNow()	// 启动时间,这里设置马上启动
							.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5))// 每5秒重复执行
							.usingJobData("msg", "simpleTrigger")  // 传递参数到job
							.build();
		// 最后,让调度器关联任务和触发器,保证按照触发器定义的条件去执行任务。
		scheduler.scheduleJob(jobDetail, trigger);
		// 启动
		scheduler.start();
		// 关闭
		// scheduler.shutdown();
	}
}

运行结果:

发现,三次执行的结果,count值均为1。这个就说明了,在默认情况下是无状态的Job且每次调用时都会创建一个新的JobDataMap。

有状态的Job:可以理解为多次job调用期间可以持有一些状态信息,这些状态信息存储在JobDataMap中。

要实现有状态的job,只需要在job类上加上注解@PersistJobDataAfterExecution即可

例如:

package cn.bjc.quartz.job;

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

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.quartz.TriggerKey;

@PersistJobDataAfterExecution
public class HelloJob implements Job {
	
	// 定义计数器
	private Integer count;
	public void setCount(Integer count) {
		this.count = count;
	}


	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		
		System.out.println("count = " + (++count)); // 计数器+1,并且打印出来
		
		// 将计数器放回到JobDetail的JobDataMap中
		context.getJobDetail().getJobDataMap().put("count", count);
		
		Calendar calendar = Calendar.getInstance();
		Date time = calendar.getTime();
		String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
		System.out.println("任务开始执行,时间是:" + format);
	}

}

 运行结果:

很明显,计数器在随着调度程序的执行而累加。 

1.3 JobBuilder

        用于声明一个任务实例,也可以定义关于该任务的详情,比如任务名、组名等。这个声明的实例将会作为一个实际执行的任务。

1.4 JobDetail——任务实例

        使用JobDetail来定义定时任务的实例,JobDetail实例是通过JobBuilder类创建的。

         JobDetail为job实例提供了许多设置属性,以及JobDataMap成员变量属性,它用来存储特定Job实例的状态信息,调度器需要借助JobDetail对象来添加Job实例。

例如:

JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)	// 加载任务类,与HelloJob绑定
							.withIdentity("job1", "group1")	// 参数一:任务的名称(唯一实例);参数二:任务组的名称,对任务进行分组
							.build();

注意:这里如果没有指定组名,默认的组名是" DEFAULT " 

JobDetail的重要属性:name、group、jobClass、jobDataMap

例如:

System.out.println("任务名称:" + jobDetail.getKey().getName());
System.out.println("任务所属的组:" + jobDetail.getKey().getGroup());
System.out.println("任务类:" + jobDetail.getJobClass().getName());

jobDetail还可以携带参数到Job实例,通过usingJobData方法

例如:

JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)	// 加载任务类,与HelloJob绑定
							.withIdentity("job1", "group1")	// 参数一:任务的名称(唯一实例);参数二:任务组的名称,对任务进行分组
							.usingJobData("msg", "日志打印")  // 传递参数到job
							.build();

1.5 JobExecutionContext

        当scheduler调用一个job,就会将JobExecutionContext传递给Job的execute()方法,job能通过JobExecutionContext对象访问到Quartz运行时候的环境以及job本身的明细数据。

1.5.1 获取JobDetail的内容

public void execute(JobExecutionContext context) throws JobExecutionException {
		
	// 获取JobDetail的内容
	JobKey key = context.getJobDetail().getKey();
	System.out.println("工作任务名称:" + key.getName());		//job1
	System.out.println("工作任务的组:" + key.getGroup());		// group1
	System.out.println("任务类:" + context.getJobDetail().getJobClass().getName());  // cn.bjc.quartz.job.HelloJob
    // 获取JobDataMap
	JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
	System.out.println(jobDataMap.getString("msg"));
}

1.5.2 获取Trigger的内容

JobDataMap jobDataMap2 = context.getTrigger().getJobDataMap();
System.out.println(jobDataMap2.getString("msg"));
TriggerKey key = context.getTrigger().getKey();
System.out.println(key.getName());
System.out.println(key.getGroup());

1.5.3 获取其他内容

// 任务本次运行时间
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(context.getFireTime()));  

// 任务下一次运行时间
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(context.getNextFireTime()));  

1.6 JobDataMap

        JobDataMap可以包含不限量的(序列化)数据对象,在job实例执行的时候,可以使用其中的数据。

1.6.1 使用Map获取

1. 在进行任务调度时,JobDataMap存储在JobExecutionContext中,非常方便获取。

2. JobDataMap可以用来装载任何可序列化的数据对象,当job实例对象被执行时,这些参数对象会传递给它。

3. JobDataMap实现了java.util.Map接口,并且添加了非常方便的方法用来存取基本数据类型

例如:获取trigger中的JobDataMap储存的msg数据

JobDataMap jobDataMap2 = context.getTrigger().getJobDataMap();
System.out.println(jobDataMap2.getString("msg"));

注意:要获取其数据,需要在执行的时候通过usingJobData传递参数 

例如:

Trigger trigger = TriggerBuilder.newTrigger()
			.withIdentity("trigger1", "group1") // 参数一:触发器的名称;参数二:触发器组的名称,对触发器进行分组 
			.startNow()	// 启动时间,这里设置马上启动
		.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5))// 每5秒重复执行
			.usingJobData("msg", "simpleTrigger")  // 传递参数到job
			.build();

1.6.2 setter方法

        Job实现类中添加setter方法对应JobDataMap的键值,Quartz框架默认的JobFactory实现类在初始化Job实例对象时会自动的调用这些setter方法。

例如:

package cn.bjc.quartz.job;

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

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class HelloJob implements Job {
	
	private String msg;
	public void setMsg(String msg) {
		this.msg = msg;
	}

	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		
		System.out.println(msg);
		
		Calendar calendar = Calendar.getInstance();
		Date time = calendar.getTime();
		String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
		System.out.println("任务开始执行,时间是:" + format);
	}

}

注意:在使用setter方法的时候,如果遇到同名的key,Trigger中的.usingJobData会覆盖JobDetail中的.usingJobData

1.7 TriggerBuilder

        用于创建触发器Trigger实例

相关的API

1. newTrigger():创建一个TriggerBuilder实例

TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger();

2. withIdentity:给TriggerBuilder实例设置身份标志

triggerBuilder .withIdentity("trigger1", "group1"); 

3. startNow():马上执行

triggerBuilder.startNow()

3. startAt(Date startTime):设置任务的开始时间

triggerBuilder.startAt(new Date())

4. endAt(Date endTime):设置任务的结束时间

triggerBuilder.endAt(new Date())

5. withSchedule(ScheduleBuilder scheduleBuilder):设置任务触发条件

// 设置任务每5秒执行一次
triggerBuilder.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5));

6. build():创建触发器

例如:

Trigger trigger = TriggerBuilder.newTrigger()
			.withIdentity("trigger1", "group1") // 参数一:触发器的名称;参数二:触发器组的名称,对触发器进行分组 
			.startNow()	// 启动时间,马上启动
		.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5))// 每5秒重复执行
			.build();

1.8 Trigger——触发器

        Trigger对象是用来触发执行job的,当调度一个job的时候,我们实例一个触发器然后调整它的属性来满足job执行的条件。表明任务在什么时候会执行,定义了一个已经安排的任务将会在什么时候执行的时间条件。例如:每2s执行一次

Trigger trigger = newTrigger()
      .withIdentity("trigger1", "group1")
      .startNow()
            .withSchedule(simpleSchedule()
              .withIntervalInSeconds(40)
              .repeatForever())            
      .build();

1.8.1 Trigger介绍 

Quartz有一些不同的触发器类型,不过,用的最多的是SimpleTrigger和CronTrigger,其实例是通过TriggerBuilder来创建的

1.8.2 Trigger下的相关的概念

1. JobKey:表示job实例的标志,触发器被触发时,该指定的job实例会被执行。

// 获取trigger的jobkey
JobKey jobKey = context.getTrigger().getJobKey();
System.out.println("trigger的名称:" + jobKey.getName());
System.out.println("trigger的组:" + jobKey.getGroup());

2. startTime:表示触发器的时间表,第一次开始被触发的时间,其数据类型是java.util.Date

3. endTime:指定触发器终止被处罚的时间,它的数据类型也是java.util.Date

Trigger trigger = context.getTrigger();
Date startTime = trigger.getStartTime();
Date endTime = trigger.getEndTime();

1.8.3 SimpleTrigger触发器

        SimpleTrigger对于设置和使用是最为简单的一种QuartzTrigger。它是为那种需要在特定的日期/时间启动,且以一个可能的时间间隔重复执行N次的Job所设计的。

API

1. SimpleScheduleBuilder.repeatSecondlyForever(5):每5秒执行一次

2. SimpleScheduleBuilder.withRepeatCount(2):重复执行3次

例如:任务每5秒执行一次,重复执行3次

SimpleScheduleBuilder.repeatSecondlyForever(5).withRepeatCount(2)

1.8.4 CronTrigger触发器——基于日历的作业调度器

        如果你需要像日历那样按日程来触发任务,而不是想SimpleTrigger那样每隔特定的时间间隔触发,CronTrigger通常比SimpleTrigger更有用,因为它是基于日历的作业调度器。

        使用CronTrigger,你可以指定诸如“每个周五中午”,或者“每个工作日9:30”或者“从周一、周三、周五的上午9:00到上午10:00之间每隔5分钟”这样日程来安排触发。

        甚至像SimpleTrigger一样,CronTrigger也有一个startTime以指定日程从什么时候开始,也有一个(可选的)endTime以指定日程不再继续。

1. cron表达式

        cron表达式被用来配置CronTrigger实例,cron表达式是一个由7个子表达式组成的字符串,每个子表达式都描述了一个单独的日程细节,这些子表达式用空格分隔,分别表示

1. Seconds 秒

2. Minutes 分

3. Hours 时

4. Day-of-Month 月中的天

5. Month 月

6. Day-of-Week 周中的天

7. Year(optional field) 年 (可选的域)

参数取值如下表:

字段 是否必填 允许值 运行的特殊字符
0-59 ,    -     *      /
0-59 ,     -     *      /
小时 0-23 ,     -     *      /
1-31 ,     -     *      /     ?     L     W     C
1-12 或者JAN-DEC ,     -     *      /
1-7或者SUN-SAT ,     -     *      /     ?     L     C     #
不填写,或者1970-2099 ,     -     *      /

单个表达式可以包含范围或者列表。

例如:周中的天可以表示为

"MON-FRI" :周一到周五

"MON,WED,FRI" :周一、周三、周五

"MON-WED,SAT":周一到周三和周六

特殊字符的用法

符号 含义
* 表示域中的每个可能的值。例如:Month域中的*表示每个月,在Day-of-Week中的*表示周中的每一天
?

表示不指定值,使用的 场景为不需要关心当前设置这个字段的值。因为月份中的日期与星期中的日期,这两个元素是互斥的,

因此应该通过设置一个问号(?)来表明你不想设置的那个字段

- 表示区间,例如:在小时上设置10-12,表示10,11,12点都会触发
, 表示指定多个值。例如:在周字段上设置“MON,WED,FRI”,表示周一、周三、周五触发
/ 表示值的增量。例如:如果分钟域中放入‘ 0/15 ’,它表示每个15分钟,从0开始,如果在分钟域中使用' 3/20 '表示小时中每隔20分钟,从第3分钟开始或者另外相同的形式就是' 3,23,43 '
L

可以在Day-of-Month与Day-of-Week中使用,表示last,但是在这两个域中的意义不同。

例如:

1. 在day-of-month中,L表示这个月的最后一天

2. 在day-of-week中,L表示7或者“ SAT ”,但是如果在day-of-week域中,L跟在别的值后面,则表示“当月的最后的周xxx”。例如:6L或者FRIL都表示本月的最后一个周五。

注意:在使用L选项的时候,最重要的是不要指定列表或者值范围,否则可能会导致混乱

W 用来指定给定目标最接近的周几(在day-of-week中指定)。例如:如果你为day-of-month域指定为15W,则表示距离月中15号最近的周几
# 表示月中的第几个周几。例如:day-of-week域中6#3或者FRI#3表示月中的第三个周五

注意:

1.  ?的用法,如果指定了日,那么周就不用指定了,如果指定了周那么日就不用指定了。原因很好理解。 

2.  L和W可以一起使用(企业中可用在工资计算)

3.  #可表示月中第几个周几(企业中用于计算母亲节父亲节)

4.  周字段英文字母不区分大小写,例如:MON=mon

2. 几个cron表达式

表达式 含义
0 0 10,14,16 * * ? 每天上午10点,下午12点,下午四点
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时,从0分开始每隔30分钟发送一次
0 0 12 ? * WED 每个星期三中午12点
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/55 14 * * ? 在每天下午2点到下午2:55期间,从0开始到55分钟触发
0 0/55 14,18 * * ? 在每天下午2点到下午2:55期间和下午6点带6:55期间,从0开始到55分钟触发
0 0-5 14 * * ? 在每天下午2点到下午2:05期间每1分钟
0 10,44 14 ? 3 WED 每年三月星期三的下午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 200-2005年的每月的最后一个星期五上午10:15触发
0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发

     例如:

     JobTriggerCron.java

package cn.bjc.quartz.job;

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

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.PersistJobDataAfterExecution;
import org.quartz.Trigger;
import org.quartz.TriggerKey;

public class JobTriggerCron implements Job {

	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		Calendar calendar = Calendar.getInstance();
		Date time = calendar.getTime();
		String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
		System.out.println("任务开始执行,时间是:" + format);
	}

}

JobTriggerCronMain.java

package cn.bjc.quartz.main;

import java.util.Date;

import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

import cn.bjc.quartz.job.JobTriggerCron;

public class JobTriggerCronMain {
	
	public static void main(String[] args) throws Exception {
		Date startDate = new Date();
		startDate.setTime(startDate.getTime() + 3000);
		Date endDate = new Date();
		endDate.setTime(endDate.getTime() + 10000);
		// 1. 调度器
		Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
		// 2. 任务实例
		JobDetail jobDetail = JobBuilder.newJob(JobTriggerCron.class)
							.withIdentity("job1", "group1")	// 参数一:任务的名称(唯一实例);参数二:任务组的名称,对任务进行分组
							.build();
		// 3. 触发器(Trigger)
		Trigger trigger = TriggerBuilder.newTrigger()
							.withIdentity("trigger1", "group1") // 参数一:触发器的名称;参数二:触发器组的名称,对触发器进行分组 
							//.startAt(startDate)
							//.endAt(endDate)
							.startNow()
							.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?"))// 日历每5秒执行
							.build();
		// 最后,让调度器关联任务和触发器,保证按照触发器定义的条件去执行任务。  SimpleScheduleBuilder
		scheduler.scheduleJob(jobDetail, trigger);
		// 启动
		scheduler.start();
	}
}

 运行结果:

 

发布了128 篇原创文章 · 获赞 6 · 访问量 3228

猜你喜欢

转载自blog.csdn.net/weixin_43318134/article/details/103792703