前言:Quartz是完全基于Java的,可用于进行定时任务调度的开源框架,Scheduler是Quartz的大脑,所有任务都是由它来控制。那什么时候用到Quartz呢,比如现在写一个接口,公司需要每10分钟调用一次,我们就可以用Quartz。
org.quartz-scheduler
quartz
2.2.1
slf4j-api
org.slf4j
org.springframework
spring-context-support
schedulerFactoryBean()方法 | 初始化SchedulerFactoryBean |
scheduler()方法 | 通过SchedulerFactoryBean获取Scheduler的实例 |
addDatamartSynJob()方法 | 自定义定时任务调用,addCommonCronJob()方法 |
addCommonCronJob()方法 | 添加一些任务调度 |
deleteCommonJob()方法 | 删除任务调度 |
package com.primeton.quartzs.conf;
import com.primeton.quartzs.job.myTest;
import org.quartz.*;
import org.quartz.ee.servlet.QuartzInitializerListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import java.io.IOException;
@Configuration
public class QuartzConfig {
@Bean(name = "SchedulerFactory")
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
//用于quartz集群,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
factory.setOverwriteExistingJobs(true);
//任务调度监听类
//factory.setGlobalTriggerListeners(triggerListenerLogMonitor());
return factory;
}
/*
* quartz初始化监听器
*/
@Bean
public QuartzInitializerListener executorListener() {
return new QuartzInitializerListener();
}
// @Bean
// public TriggerListenerLogMonitor triggerListenerLogMonitor() {
// return new TriggerListenerLogMonitor();
// }
/*
* 通过SchedulerFactoryBean获取Scheduler的实例
*/
@Bean(name = "Scheduler")
public Scheduler scheduler() throws IOException {
Scheduler scheduler = schedulerFactoryBean().getScheduler();
//添加同步任务 author:sujin
addmyTestJob(scheduler);
return scheduler;
}
/**
* 新增一个定时任务(测试)
* @param scheduler
* @author sujin
*/
private void addmyTestJob(Scheduler scheduler){
String startJob = "true";//是否开始
String jobName = "DATAMART_SYNC";
String jobGroup = "DATAMART_SYNC";
String cron = "0 0/1 * * * ?";//定时的时间设置
String className = myTest.class.getName();
if (startJob != null && startJob.equals("true")) {
addCommonCronJob(jobName, jobGroup, cron, scheduler, className);
} else {
deleteCommonJob(jobName, jobGroup, scheduler);
}
}
//end...
private void deleteCommonJob(String jobName, String jobGroup, Scheduler scheduler) {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
try {
scheduler.pauseJob(jobKey);//先暂停任务
scheduler.deleteJob(jobKey);//再删除任务
} catch (SchedulerException e) {
e.printStackTrace();
}
}
/**
* 添加一些定时任务,如日志清理任务
*/
private void addCommonCronJob(String jobName, String jobGroup, String cron, Scheduler scheduler, String className) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
//任务触发
Trigger checkExist = (CronTrigger) scheduler.getTrigger(triggerKey);
if (checkExist == null) {
JobDetail jobDetail = null;
jobDetail = JobBuilder.newJob((Class extends Job>) Class.forName(className))
.requestRecovery(true)//当Quartz服务被中止后,再次启动或集群中其他机器接手任务时会尝试恢复执行之前未完成的所有任务
.withIdentity(jobName, jobGroup)
.build();
jobDetail.getJobDataMap().put("jobName", jobName);
jobDetail.getJobDataMap().put("jobGroup", jobGroup);
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
/*withMisfireHandlingInstructionDoNothing
——不触发立即执行
——等待下次Cron触发频率到达时刻开始按照Cron频率依次执行
withMisfireHandlingInstructionIgnoreMisfires
——以错过的第一个频率时间立刻开始执行
——重做错过的所有频率周期后
——当下一次触发频率发生时间大于当前时间后,再按照正常的Cron频率依次执行
withMisfireHandlingInstructionFireAndProceed
——以当前时间为触发频率立刻触发一次执行
——然后按照Cron频率依次执行*/
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(jobName, jobGroup)
.withSchedule(cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires())
.build();
scheduler.scheduleJob(jobDetail, trigger);
} else {
scheduler.deleteJob(JobKey.jobKey(jobName, jobGroup));
addCommonCronJob(jobName, jobGroup, cron, scheduler, className);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
Job可以理解为就是一个工作任务,代码中就是一个实现了org.quartz.Job或org.quartz.StatefulJob接口的java类。当Scheduler决定运行Job时,execute()方法就会被执行。
package com.primeton.quartzs.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class myTest implements Job{
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("test");
}
}
package com.primeton.quartzs.conf;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 任务调度监听类
*/
@Component
class TriggerListenerLogMonitor implements TriggerListener {
@Override
public String getName() {
return "TriggerListenerLogMonitor";
}
//当与监听器相关联的 Trigger 被触发,Job 上的 execute() 方法将要被执行时,Scheduler 就调用这个方法。
@Override
public void triggerFired(Trigger trigger, JobExecutionContext context) {
System.out.println("TriggerListenerLogMonitor类:" + context.getTrigger().getKey().getName() + " 被执行");
}
/**
* 在 Trigger 触发后,Job 将要被执行时由 Scheduler 调用这个方法。
* TriggerListener 给了一个选择去否决 Job 的执行。
* 假如这个方法返回 true,这个 Job 将不会为此次 Trigger 触发而得到执行。
*/
@Override
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
return false;
}
/**
* Scheduler 调用这个方法是在 Trigger 错过触发时。
* 如这个方法的 JavaDoc 所指出的,你应该关注此方法中持续时间长的逻辑:
* 在出现许多错过触发的 Trigger 时,
* 长逻辑会导致骨牌效应。
* 你应当保持这上方法尽量的小。
*/
@Override
public void triggerMisfired(Trigger trigger) {
System.out.println("Job错过触发");
}
/*多米诺骨牌效应(骨牌效应):该效应产生的能量是十分巨大的。
这种效应的物理道理是:骨牌竖着时,重心较高,倒下时重心下降,倒下过程中,
将其重力势能转化为动能,它倒在第二张牌上,这个动能就转移到第二张牌上,
第二张牌将第一张牌转移来的动能和自已倒下过程中由本身具有的重力势能转化来的动能之和,
再传到第三张牌上......所以每张牌倒下的时候,具有的动能都比前一块牌大,因此它们的速度一个比一个快,
也就是说,它们依次推倒的能量一个比一个大*/
/**
* Trigger 被触发并且完成了 Job 的执行时,Scheduler 调用这个方法。
* 这不是说这个 Trigger 将不再触发了,而仅仅是当前 Trigger 的触发(并且紧接着的 Job 执行) 结束时。
* 这个 Trigger 也许还要在将来触发多次的。
*/
@Override
public void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) {
System.out.println("Job执行完毕,Trigger完成");
}
}
注:在QuartzConfig中注入TriggerListenerLogMonitor,将上面的注释代码去掉即可。
在QuartzConfig的addmyTestJob方法中设置cron,有一个很不错的在线Cron表达式生成器,推荐给大家 点击打开
(*)星号:可以理解为每的意思,每秒,每分,每天,每月,每年...
(?)问号:问号只能出现在日期和星期这两个位置,表示这个位置的值不确定,每天3点执行,所以第六位星期的位置,我们是不需要关注的,就是不确定的值。同时:日期和星期是两个相互排斥的元素,通过问号来表明不指定值。比如,1月10日,比如是星期1,如果在星期的位置是另指定星期二,就前后冲突矛盾了。
(-)减号:表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12
(,)逗号:表达一个列表值,如在星期字段中使用“1,2,4”,则表示星期一,星期二,星期四
(/)斜杠:如:x/y,x是开始值,y是步长,比如在第一位(秒) 0/15就是,从0秒开始,每15秒,最后就是0,15,30,45,60 另:*/y,等同于0/y
0 0 1 * * ? | 每天1点触发 |
0 0 12 * * ? | 每天12点触发 |
0 15 10 ? * * | 每天10点15分触发 |
0 15 10 * * ? | 每天10点15分触发 |
0 15 10 * * ? * | 每天10点15分触发 |
0 15 10 L * ? | 每月最后一天的10点15分触发 |
0 15 10 15 * ? | 每月15号上午10点15分触发 |
0 0-5 14 * * ? | 每天下午的 2点到2点05分每分触发 |
0 0 12 1/5 * ? | 每月的第一个中午开始每隔5天触发一次 |
0 0/2 * * * ? | 每两分钟触发一次 |