一、概念和使用 :
https://www.cnblogs.com/zhanghaoliang/p/7886110.html
Quartz对任务调度的领域问题进行了高度的抽象,提出了调度器、任务和触发器这3个核心的概念,并在org.quartz通过接口和类对重要的这些核心概念进行描述:
●Job:是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中;
●JobDetail:Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。
通过该类的构造函数可以更具体地了解它的功用:JobDetail(java.lang.String name, java.lang.String group, java.lang.Class jobClass),该构造函数要求指定Job的实现类,以及任务在Scheduler中的组名和Job名称;
●Trigger:是一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;而CronTrigger则可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等;
●Calendar:org.quartz.Calendar和java.util.Calendar不同,它是一些日历特定时间点的集合(可以简单地将org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一个日历时间点,无特殊说明后面的Calendar即指org.quartz.Calendar)。一个Trigger可以和多个Calendar关联,以便排除或包含某些时间点。
假设,我们安排每周星期一早上10:00执行任务,但是如果碰到法定的节日,任务则不执行,这时就需要在Trigger触发机制的基础上使用Calendar进行定点排除。针对不同时间段类型,Quartz在org.quartz.impl.calendar包下提供了若干个Calendar的实现类,如AnnualCalendar、MonthlyCalendar、WeeklyCalendar分别针对每年、每月和每周进行定义;
●Scheduler:代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。
Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。可以通过SchedulerFactory创建一个Scheduler实例。Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以访问SchedulerContext内的信息。SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,SchedulerContext为保存和获取数据提供了多个put()和getXxx()的方法。可以通过Scheduler# getContext()获取对应的SchedulerContext实例;
●ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率。
Job有一个StatefulJob子接口,代表有状态的任务,该接口是一个没有方法的标签接口,其目的是让Quartz知道任务的类型,以便采用不同的执行方案。无状态任务在执行时拥有自己的JobDataMap拷贝,对JobDataMap的更改不会影响下次的执行。而有状态任务共享共享同一个JobDataMap实例,每次任务执行对JobDataMap所做的更改会保存下来,后面的执行可以看到这个更改,也即每次执行任务后都会对后面的执行发生影响。
正因为这个原因,无状态的Job可以并发执行,而有状态的StatefulJob不能并发执行,这意味着如果前次的StatefulJob还没有执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。有状态任务比无状态任务需要考虑更多的因素,程序往往拥有更高的复杂度,因此除非必要,应该尽量使用无状态的Job。
如果Quartz使用了数据库持久化任务调度信息,无状态的JobDataMap仅会在Scheduler注册任务时保持一次,而有状态任务对应的JobDataMap在每次执行任务后都会进行保存。
Trigger自身也可以拥有一个JobDataMap,其关联的Job可以通过JobExecutionContext#getTrigger().getJobDataMap()获取Trigger中的JobDataMap。不管是有状态还是无状态的任务,在任务执行期间对Trigger的JobDataMap所做的更改都不会进行持久,也即不会对下次的执行产生影响。
Quartz拥有完善的事件和监听体系,大部分组件都拥有事件,如任务执行前事件、任务执行后事件、触发器触发前事件、触发后事件、调度器开始事件、关闭事件等等,可以注册相应的监听器处理感兴趣的事件。
二、动手实践 :
1. 创建工厂类
/**
* @Data: 2019/9/24
* @Des: 定时任务工厂类
*/
@Component
public class QuartzJobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//调用父类的方法
Object jobInstance = super.createJobInstance(bundle);
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
2. 设置启动时间(SpringBoot中可以通过这种方式在容器启动时启动定时器 / SSM中可以在WebApplicationInitializer实现类的run方法中启动)
SpringBoot 中 :
/**
* @Data: 2019/9/24
* @Des: 容器启动后,会执行run方法中的内容。
*/
@Component
public class QuartzRunner implements ApplicationRunner {
@Autowired
private Scheduler scheduler;
@Override
public void run(ApplicationArguments args) throws Exception {
// 每次都会重新创建JobBean
JobDetail jobDetail = JobBuilder.newJob(MyQuartzJobBean.class).withIdentity(DEFAULT_NAME, DEFAULT_GROUP).build();
// 初始化触发器
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(DEFAULT_NAME, DEFAULT_GROUP) // 触发器id, 属于哪个组
.startNow() // 开始时间
.withSchedule(CronScheduleBuilder.cronSchedule(DEFAULT_CRON)) // 触发规则
.build();
scheduler.start();
// 将当前任务和当前触发器绑定
scheduler.scheduleJob(jobDetail, trigger);
}
}
SSM中
// 每次都会重新创建JobBean
JobDetail jobDetail = JobBuilder.newJob(MyQuartzJobBean.class).withIdentity(DEFAULT_NAME, DEFAULT_GROUP).build();
// 初始化触发器
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(DEFAULT_NAME, DEFAULT_GROUP) // 触发器id, 属于哪个组
.startNow() // 开始时间
.withSchedule(CronScheduleBuilder.cronSchedule(DEFAULT_CRON)) // 触发规则
.build();
scheduler.start();
// 将当前任务和当前触发器绑定
scheduler.scheduleJob(jobDetail, trigger);
}
3. Quartz 配置类
@Configuration
public class QuartzConfig {
/**
* 注入scheduler到spring
*
* @param quartzJobFactory
* @return
* @throws Exception
*/
@Bean(name = "scheduler")
public Scheduler scheduler(QuartzJobFactory quartzJobFactory) throws Exception {
SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
factoryBean.setJobFactory(quartzJobFactory);
factoryBean.afterPropertiesSet();
Scheduler scheduler = factoryBean.getScheduler();
scheduler.start();
return scheduler;
}
}
5. 编写定时任务具体内容 :
@DisallowConcurrentExecution
: 禁止并发执行多个相同定义的JobDetail, 这个注解是加在Job类上的, 但意思并不是不能同时执行多个Job, 而是不能并发执行同一个Job Definition(由JobDetail定义), 但是可以同时执行多个不同的JobDetail, 简单来说:对同一个定时任务来说,@DisallowConcurrentExecution 保证上一轮次任务执行结束前不会重新执行下一轮次任务。eg: 我们设置2s执行一次任务,但是任务执行耗时5s。如果不加该注解,则2s后会新开辟一个线程执行下一轮任务。加上该注解则不会开辟新线程,而是等当前轮次执行结束后才会继续执行下一轮的任务。
@Component
@DisallowConcurrentExecution // 不允许此 Job 并发执行任务(禁止新开线程执行,保证上一个任务执行结束后才会继续执行下一个任务)
public class MyQuartzJobBean extends QuartzJobBean {
private final Logger logger = LoggerFactory.getLogger(MyQuartzJobBean.class);
@Autowired
private MonitorTaskService monitorTaskService;
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
logger.info("定时任务执行 : " + new SimpleDateFormat(DEFAULT_DATE_FORMAT).format(new Date()));
}
}
三、Quartz工具类
import com.iflytek.morningstar.onlinetesting.quartz.domain.QuartzJob;
import lombok.extern.slf4j.Slf4j;
import com.iflytek.morningstar.onlinetesting.common.exception.BadRequestException;
import org.quartz.*;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
import static org.quartz.TriggerBuilder.newTrigger;
@Slf4j
@Component
public class QuartzManage {
private static final String JOB_NAME = "TASK_";
@Resource(name = "scheduler")
private Scheduler scheduler;
public void addJob(QuartzJob quartzJob){
try {
// 构建job信息
JobDetail jobDetail = JobBuilder.newJob(ExecutionJob.class).
withIdentity(JOB_NAME + quartzJob.getQuartzJobId()).build();
//通过触发器名和cron 表达式创建 Trigger
Trigger cronTrigger = newTrigger()
.withIdentity(JOB_NAME + quartzJob.getQuartzJobId())
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression()))
.build();
cronTrigger.getJobDataMap().put(QuartzJob.JOB_KEY, quartzJob);
//重置启动时间
((CronTriggerImpl)cronTrigger).setStartTime(new Date());
//执行定时任务
scheduler.scheduleJob(jobDetail,cronTrigger);
// 暂停任务
if (quartzJob.getIsPause()) {
pauseJob(quartzJob);
}
} catch (Exception e){
log.error("创建定时任务失败", e);
throw new BadRequestException(e.getMessage());
}
}
/**
* 更新job cron表达式
* @param quartzJob
* @throws SchedulerException
*/
public void updateJobCron(QuartzJob quartzJob){
try {
TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getQuartzJobId());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression());
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
//重置启动时间
((CronTriggerImpl)trigger).setStartTime(new Date());
scheduler.rescheduleJob(triggerKey, trigger);
// 暂停任务
if (quartzJob.getIsPause()) {
pauseJob(quartzJob);
}
} catch (Exception e){
log.error("更新定时任务失败", e);
throw new BadRequestException(e.getMessage());
}
}
/**
* 删除一个job
* @param quartzJob
* @throws SchedulerException
*/
public void deleteJob(QuartzJob quartzJob){
try {
JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getQuartzJobId());
scheduler.deleteJob(jobKey);
} catch (Exception e){
log.error("删除定时任务失败", e);
throw new BadRequestException(e.getMessage());
}
}
/**
* 恢复一个job
* @param quartzJob
* @throws SchedulerException
*/
public void resumeJob(QuartzJob quartzJob){
try {
TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getQuartzJobId());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 如果不存在则创建一个定时任务
if(trigger == null){
addJob(quartzJob);
}
JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getQuartzJobId());
scheduler.resumeJob(jobKey);
} catch (Exception e){
log.error("恢复定时任务失败", e);
throw new BadRequestException(e.getMessage());
}
}
/**
* 立即执行job
* @param quartzJob
* @throws SchedulerException
*/
public void runAJobNow(QuartzJob quartzJob){
try {
TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getQuartzJobId());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 如果不存在则创建一个定时任务
if(trigger == null){
addJob(quartzJob);
}
JobDataMap dataMap = new JobDataMap();
dataMap.put(QuartzJob.JOB_KEY, quartzJob);
JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getQuartzJobId());
scheduler.triggerJob(jobKey,dataMap);
} catch (Exception e){
log.error("定时任务执行失败", e);
throw new BadRequestException(e.getMessage());
}
}
/**
* 暂停一个job
* @param quartzJob
* @throws SchedulerException
*/
public void pauseJob(QuartzJob quartzJob){
try {
JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getQuartzJobId());
scheduler.pauseJob(jobKey);
} catch (Exception e){
log.error("定时任务暂停失败", e);
throw new BadRequestException(e.getMessage());
}
}
}
package com.kingfish.common.config;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Date;
public class QuartzJob implements Serializable {
public static final String JOB_KEY = "JOB_KEY";
private Integer quartzJobId;
/**
* 定时器名称
*/
private String jobName;
/**
* Bean名称
*/
private String beanName;
/**
* 方法名称
*/
private String methodName;
/**
* 参数
*/
private String params;
/**
* cron表达式
*/
@NotBlank
private String cronExpression;
/**
* 状态
*/
private Boolean isPause = false;
/**
* 备注
*/
private String remark;
private boolean isDelete;
private Integer createId;
private Date createTime;
private Timestamp updateTime;
public static String getJobKey() {
return JOB_KEY;
}
public Integer getQuartzJobId() {
return quartzJobId;
}
public void setQuartzJobId(Integer quartzJobId) {
this.quartzJobId = quartzJobId;
}
public String getJobName() {
return jobName;
}
public void setJobName(String jobName) {
this.jobName = jobName;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params;
}
public String getCronExpression() {
return cronExpression;
}
public void setCronExpression(String cronExpression) {
this.cronExpression = cronExpression;
}
public Boolean getPause() {
return isPause;
}
public void setPause(Boolean pause) {
isPause = pause;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public boolean isDelete() {
return isDelete;
}
public void setDelete(boolean delete) {
isDelete = delete;
}
public Integer getCreateId() {
return createId;
}
public void setCreateId(Integer createId) {
this.createId = createId;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Timestamp getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Timestamp updateTime) {
this.updateTime = updateTime;
}
}
以上:内容部分参考
https://www.cnblogs.com/cjm123/p/9679171.html
https://www.cnblogs.com/vipstone/p/9275256.html
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正