springboot 和Quartz整合
Quartz与springboot的整合与和spring 整合相比,简单了很多。
添加依赖
<dependency><!-- spring boot quartz starter依赖 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
Quartz
Quartz中比较重要的概念:
- Schedule
- Job
- JobDatail
- Trigger
- JobKey
- TriggerKey
1.Schedule
schedule是和程序交互的主要API,用于管理任务,是一个用于专门管理任务的容器。一般使用SchedulerFactory来实例化Schedule实例。
scheduler实例化后,可以进行启动(start)、暂停(stand-by)、停止(shutdown)操作,用于控制整个任务容器的生命周期。
注意:
- scheduler被停止后,除非重新实例化,否则不能重新启动;Scheduler的生命周期时从SchedulerFactory创建他开始到scheduler调用shutdown()方法时结束
- 只有当scheduler启动后(即使处于暂停状态也不行),即调用start()方法trigger才会被触发(job才会被执行)。
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
Scheduler sched = schedFact.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(jobClass).
withIdentity(jobName,jobGroup).
build();
Trigger trigger = TriggerBuilder.
newTrigger().
withIdentity(triggerName,triggerGroup).
withSchedule(CronScheduleBuilder.
cronSchedule(cronException)).
build();
try{
sched.schedulerJob(jobDetail,trigger);
sched.start();
}catch(Exception e){
e.printStackTrace();
}
2.Job
Job是一个接口,我们的业务方法一般写在它的实现类中。 当触发器被触发时就会调用Job的execute()方法。所以编写Job实现类是我们主要的工作。
import org.springframework.scheduling.quartz.QuartzJobBean
public class MyJob extends QuartzJobBean {//QuartzJobBean 继承了Job接口
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
//具体业务逻辑
System.out.println("xxxxxxxx\t"+ new Date());
}
}
JobExecutionContext对象中保存了很多信息,可以通过该实例获取Scheduler,JobDetail,Trigger,
JobExecutionContext
public interface JobExecutionContext {
//返回触发该任务的Scheduler实例
public Scheduler getScheduler();
//获取触发该任务的Trigger实例
public Trigger getTrigger();
//返回和该Job关联的JobDetail实例
public JobDetail getJobDetail();
//返回正在被执行的Job实例
public Job getJobInstance();
}
3. Key
Quartz使用key可以唯一标识一个任务或者触发器。
- JobKey 。用于唯一标识一个任务,通常就是通过任务名(jobName),任务组名(jobGroupName)
- TriggerKey。唯一标识一个触发器。(triggerName, triggerGroupName)
注意:
- 同一个分组下的Job或Trigger的名称必须唯一,即一个Job或Trigger的key由名称(name)和分组(group)组成。
下面是JobKey和Key的部分代码:
package org.quartz;
import org.quartz.utils.Key;
public final class JobKey extends Key<JobKey> {
public JobKey(String name) {
super(name, null);
}
public static JobKey jobKey(String name, String group) {
return new JobKey(name, group);
}
}
----------------------------------------------------------------
package org.quartz.utils;
import java.io.Serializable;
import java.util.UUID;
public class Key<T> implements Serializable, Comparable<Key<T>> {
private static final long serialVersionUID = -7141167957642391350L;
public static final String DEFAULT_GROUP = "DEFAULT";
private final String name;
private final String group;
/**
* Construct a new key with the given name and group.
*/
public Key(String name, String group) {
if(name == null)
throw new IllegalArgumentException("Name cannot be null.");
this.name = name;
if(group != null)
this.group = group;
else
this.group = DEFAULT_GROUP;
}
从上面可以看出。如果没有设置组名,则使用默认的组名“DEFAULT”
TriggerKey和上面一样,唯一不同的是用于唯一标识触发器
4.JobDetail
上面介绍了JobKey,创建JobDetail实例的时候就需要制定JobKey。
JobDetail中包含任务的各种属性设置(jobNmae,JobGroup,Job实现类等),以及用于包装Job接口的实例,并且可以保存一些额外的业务信息,这些信息被保存在JobDataMap(类Map结构,只是提供了一些更方便获取数据的方法)
JobDetail实例的创建
JobDetail jobDetail = JobBuilder.newJob(jobClass).//jobClass 被执行的任务
withIdentity(jobName,jobGroup).//jobName-jobGroup 必须唯一
usingJobData(jobDataClass). //封装一些业务信息保存在JobDataMap中
build();
使用withIdentity来指定JobKey。
在JobBuilder中重载了withIdentity()方法
/**
* Use a <code>JobKey</code> with the given name and default group to
* identify the JobDetail.
* <p>If none of the 'withIdentity' methods are set on the JobBuilder,
* then a random, unique JobKey will be generated.</p>
*/
public JobBuilder withIdentity(String name) {
key = new JobKey(name, null);
return this;
}
public JobBuilder withIdentity(String name, String group) {
key = new JobKey(name, group);
return this;
}
public JobBuilder withIdentity(JobKey jobKey) {
this.key = jobKey;
return this;
}
Trigger
Trigger是Quartz另一个特别重要的概念,用于控制任务的执行时间,频次等。
Trigger用于触发Job的执行。当你准备调度一个job时,你创建一个Trigger的实例,然后设置调度相关的属性。Trigger也有一个相关联的JobDataMap,用于给Job传递一些触发相关的参数。Quartz自带了各种不同类型的Trigger,最常用的主要是SimpleTrigger和CronTrigger。
- SimpleTrigger,主要用于一次性执行的Job(只在某个特定的时间点执行一次),或者Job在特定的时间点重复执行N次,每次执行间隔T个时间单位
- CronTrigger。基于日历的调度上非常有用,如“每个星期五的正午”,或者“每月的第十天的上午10:15”等
一个任务和关联多个触发器,这样一个任务就可以在不同时间执行。所以这样对任务的一些操作会涉及到触发器的执行。
- 当暂停一个Job任务时,会暂停该任务关联的所有触发器。
- 如果只是暂停一个触发器,只会暂停指定的触发器。
- 如果移除一个任务,则相关联的触发器也会被移除
- 如果移除触发器,只会移除该触发器,不会影响任务关联的其他触发器。
1. SimpleTrigger触发器 ,人如其名(简单)。该实例一般由SimpleScheduleBuilder创建,提供了很多方便的方法,用于指定触发器的频次。
//每分钟执行一次
public static SimpleScheduleBuilder repeatMinutelyForever() {
}
//每隔xxx分钟执行一次
public static SimpleScheduleBuilder repeatMinutelyForever(int minutes) {
}
//每秒执行一次
public static SimpleScheduleBuilder repeatSecondlyForever() {
}
//每隔xxx秒执行一次
public static SimpleScheduleBuilder repeatSecondlyForever(int seconds) {
}
//设置,每分钟执行的任务的总执行次数
public static SimpleScheduleBuilder repeatMinutelyForTotalCount(int count) {
}
//
public static SimpleScheduleBuilder repeatMinutelyForTotalCount(int count, int minutes) {
}
2. CronTrigger
这个触发器实际开发使用的比较多,因为它提供了一套按日历触发的简单操作。适合各种复杂的操作
其中最为关键的就是CronExpression了
Cron-Expressions用于配置CronTrigger的实例。
Cron Expressions是由七个子表达式组成的字符串,用于描述日程表的各个细节。这些子表达式用空格分隔,并表示:
- Seconds //范围[0-59]
- Minutes //范围[0-59]
- Hours //范围[0-23]
- Day-of-Month //范围[1-31]
- Month //范围[0-11] ,或 JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV和DEC
- Day-of-Week //范围 可以是字符串 “SUN,MON,TUE,WED,THU,FRI和SAT”,也可以是数字[1-7] (1代表周日)
- Year (optional field)
表达式的值可以使用通配符。使用规则如下
- ‘*’:表示该字段可以为任意合法的值。 如果用于秒字段,则表示每秒
- ‘/’: 用于指定值的增量。例如,如果用在秒字段“0/15”,表示从零秒开始每隔15秒执行一次。和”15,30,45,0“表示的一样。
- ‘?’ :只允许在Day-of-Month 和Day-of-Week字段用于两选一。
- “L”:允许用于月日和星期字段。这个角色对于“最后”来说是短暂的,但是在这两个领域的每一个领域都有不同的含义。例如,“月”字段中的“L”表示“月的最后一天” - 1月31日,非闰年2月28日。如果在本周的某一天使用,它只是意味着“7”或“SAT”。但是如果在星期几的领域中再次使用这个值,就意味着“最后一个月的xxx日”,例如“6L”或“FRIL”都意味着“月的最后一个星期五”。您还可以指定从该月最后一天的偏移量,例如“L-3”,这意味着日历月份的第三个到最后一天。当使用’L’选项时,重要的是不要指定列表或值的范围,因为您会得到混乱/意外的结果。
- “W”用于指定最近给定日期的工作日(星期一至星期五)。例如,如果要将“15W”指定为月日期字段的值,则意思是:“最近的平日到当月15日”。
- '#'用于指定本月的“第n个”XXX工作日。例如,“星期几”字段中的“6#3”或“FRI#3”的值表示“本月的第三个星期五”。
0/5 * * * * ? 每隔5秒执行一次
0 0 18 * * ? 每天下午6点执行
12 12 * * * ? 每小时第12分钟12秒执行
参考文献;
Quartz官方文档
动态创建任务的demo
下面的建议版本没有涉及数据,一般来说为了防止服务器重启导致的数据丢失。应当将这些数据保存在数据库中。
@Controller
@RequestMapping("quartz")
public class QuartzController {
private static final String DEFAULT_JOB_GROUP="job_group";
private static final String DEFAULT_TRIGGER_GROP="trigger_group";
private static final AtomicInteger INCREMENT= new AtomicInteger(0);
@Autowired
QuartzUtil quartzUtil;
/**
*根据自己的需要增加接受参数。
*/
@RequestMapping("/addSimpleQuartz")
@ResponseBody
public String addSimpleQuartz(String time){
try {
QuestionnaireJobDTO job =new QuestionnaireJobDTO();
job.setTriggerName("trigger"+INCREMENT.incrementAndGet());//触发器名称
job.setJobName("job"+INCREMENT.get());//任务名称
job.setJobGroup(DEFAULT_JOB_GROUP);//任务组
job.setTriggerGroup(DEFAULT_TRIGGER_GROP);//触发器组
SimpleDateFormat format =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = format.parse(time);//任务执行时间
JobDataMap map = new JobDataMap();
map.put("zz", "cccc");
//此处的任务实现类,可以改为输入参数。更好一点的是,传入一个类的全限定类名。这样可以不重启服务器就可以方便的添加任务了。
//Class jobClass = Class.forName(className);
quartzUtil.addSimpleJob(QuestionnaireJob.class,date, "job", "trigger",map);//添加一个简单任务,在指定时间执行
}catch (Exception e) {
e.printStackTrace();
return "error";
}
return "success";
}
}
创建一个任务
public class QuestionnaireJob extends QuartzJobBean{
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
System.out.println(" executing .............");
}
}
quartz工具类,用于实际操作任务
package com.test.quartz;
import static org.quartz.TriggerBuilder.newTrigger;
import java.util.Date;
import org.quartz.CronScheduleBuilder;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class QuartzUtil {
public static final String DEFAULT_JOB_GROUP="JOB_GROUP";
public static final String DEFAULT_TRIGGER_GROUP="TRIGGER_GROUP";
@Autowired
Scheduler scheduler;
/**
*
*基于日历的调度, 例如每个星期五,需要使用cronExpression表达式,来指定时间
*@author zhangcc
*@date 2018年8月17日
*@param jobClass
*@param cronException cron表达式
*@param jobName 任务名称,在一个任务组内需要唯一
*@param jobGroup 任务组
*@param triggerName 触发器名称,需要在触发器组内唯一
*@param triggerGroup 触发器组
*@return
* @throws SchedulerException
*/
public void addJob(Class<? extends Job> jobClass,String cronException, String jobName, String jobGroup, String triggerName, String triggerGroup) throws SchedulerException{
JobDetail jobDetail = JobBuilder.newJob(jobClass).
withIdentity(jobName,jobGroup).
build();
Trigger trigger = newTrigger().
withIdentity(triggerName,triggerGroup).
withSchedule(CronScheduleBuilder.cronSchedule(cronException)).
build();
scheduler.scheduleJob(jobDetail,trigger);
startSchedule();
}
public void addJob(Class<? extends Job> jobClass,String cronException, String jobName, String triggerName) throws SchedulerException{
addJob(jobClass,cronException,jobName,DEFAULT_JOB_GROUP,triggerName,DEFAULT_TRIGGER_GROUP );
}
/**
* 添加一个简单的任务, 某个具体时间执行
*@author zhangcc
*@date 2018年8月17日
*@param jobClass
*@param date 任务执行时间
*@param jobName 任务名称,需要唯一
*@param triggerName 触发器名称,需要唯一
*@return void
* @throws SchedulerException
*/
public void addSimpleJob(Class<? extends Job> jobClass, Date date, String jobName, String triggerName) throws SchedulerException{
addSimpleJob(jobClass, date, jobName, triggerName, null);
}
/**
* 添加一个简单的任务, 某个具体时间执行
*@author zhangcc
*@date 2018年8月22日
*@param jobClass
*@param date 任务执行时间
*@param jobName 任务名称,需要唯一
*@param triggerName 触发器名称,需要唯一
*@param argsMap 需要传递的一些参数
*@return void
* @throws SchedulerException
*/
public void addSimpleJob(Class<? extends Job> jobClass, Date date, String jobName, String triggerName,JobDataMap argsMap) throws SchedulerException{
addSimpleJob(jobClass, date, jobName, null, triggerName, null, argsMap);
}
public void addSimpleJob(Class<? extends Job> jobClass, Date date, String jobName, String jobGroup,String triggerName,String triggerGroup,JobDataMap argsMap) throws SchedulerException{
JobBuilder jobBuilder =JobBuilder.newJob(jobClass). withIdentity(jobName,DEFAULT_JOB_GROUP);
if(argsMap != null)
jobBuilder.usingJobData(argsMap);
JobDetail jobDetail = jobBuilder.build();
Trigger trigger = newTrigger().
withIdentity(triggerName,DEFAULT_TRIGGER_GROUP).startAt(date).
build();
scheduler.scheduleJob(jobDetail,trigger);
startSchedule();
}
/**
* 继续执行
*@author
*@date 2018年8月22日
*@param jobName 任务名称,需要唯一
*@param triggerName 触发器名称,需要唯一
*@return void
* @throws SchedulerException
*/
public void resumeJob(String jobName,String jobGroup) throws SchedulerException{
JobKey jobKey = JobKey.jobKey(jobName,jobGroup);
if(scheduler.checkExists(jobKey))
scheduler.resumeJob(jobKey);
}
/**
* 继续执行触发器
*@author
*@date 2018年8月22日
*@param jobName 任务名称,需要唯一
*@param triggerName 触发器名称,需要唯一
*@return void
* @throws SchedulerException
*/
public void resumeTrigger(String triggerName,String triggerGroup) throws SchedulerException{
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName,triggerGroup);
if(scheduler.checkExists(triggerKey))
scheduler.resumeTrigger(triggerKey);
}
/**
* 暂停任务。该操作会暂停该任务锁绑定的所有触发器。如果触发器A,B都绑定了任务JOBA,如果暂停JOBA则会暂停触发器A和B
*@author
*@date 2018年8月22日
*@param jobName 任务名称,需要唯一
*@param triggerName 触发器名称,需要唯一
*@return void
* @throws SchedulerException
*/
public void pauseJob(String jobName,String jobGroup) throws SchedulerException{
JobKey jobKey = JobKey.jobKey(jobName,jobGroup);
if(scheduler.checkExists(jobKey))
scheduler.pauseJob(jobKey);
}
/**
* 暂停触发器。只会暂停该触发器
*@author
*@date 2018年8月22日
*@param jobName 任务名称,需要唯一
*@param triggerName 触发器名称,需要唯一
*@return void
* @throws SchedulerException
*/
public void pauseTrigger(String triggerName,String triggerGroup) throws SchedulerException{
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName,triggerGroup);
if(scheduler.checkExists(triggerKey))
scheduler.pauseTrigger(triggerKey);
}
/**
* 移除任务
*@author
*@date 2018年8月22日
*@param jobName 任务名称,需要唯一
*@param triggerName 触发器名称,需要唯一
*@return void
* @throws SchedulerException
*/
public void removeJob(String jobName,String jobGroup) throws SchedulerException{
JobKey jobKey = JobKey.jobKey(jobName,jobGroup);
if(scheduler.checkExists(jobKey))
scheduler.deleteJob(jobKey);
}
public void startSchedule() throws SchedulerException{
if(scheduler.isShutdown()){
scheduler.resumeAll();
}else
scheduler.start();
}
// 获取当前程序所有的任务
public void listJobs() throws SchedulerException{
List<String> jobGroups = scheduler.getJobGroupNames();
for(String jobGroup : jobGroups) {
GroupMatcher<JobKey> groupMatcher=GroupMatcher.jobGroupEquals(jobGroup);
Set<JobKey> jobs =scheduler.getJobKeys(groupMatcher);
System.out.println("jobGroup\t"+jobGroup);
for(JobKey jobKey : jobs) {
System.out.println("\t"+jobKey.getName());
}
}
}
}