编写Job类 (任务类),实现Job接口,重写exeute方法,此方法就是要执行的任务,类似于Runable
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("任务正在执行 - "+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
}
JobDetail是对Job的封装,可以设置许多属性,还包括一个比较重要的JobDataMap,用来存储Job实例的状态信息
调度器需要借助JobDetail对象来添加Job实例
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("任务1","组1")
.build();
触发器用来告诉调度程序任务什么时候触发,框架提供了5种触发器类型,但两个最常用的SimpleTrigger和CronTrigger
//触发器
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("触发器1","组1")
//触发策略 可以设置是间隔执行,还是到某个时间执行
//此处为间隔1秒执行一次,一直执行
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
.build();
优先级数字越大触发器优先级越高,在触发器并发执行情况下,优先级越高的触发器执行越靠前
若触发器优先级未设置,那么优先级最低
Trigger trigger = TriggerBuilder.newTrigger()
//设置trigger的优先级
.withPriority(10)
.withIdentity("触发器1","组1")
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
.build();
Scheduler称之为调度器,用于管理触发器和Job,例如启动某一任务,暂停某一任务等职责。
Job会被注册进Scheduler中,Scheduler通过调用Trigger来执行
//调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
//将job与trigger都放入调度器中
scheduler.scheduleJob(jobDetail, trigger);
//启动定时任务
scheduler.start();
调度器的几种行为
看名字就知道,这是任务执行时的上下文,在实现Job方法重写excute时,作为参数传入任务中
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
//从context中获取job与trigger的名称
System.out.println("任务名为:"+context.getJobDetail().getKey()+"---触发器名为:"+context.getTrigger().getKey().getName());
}
}
当scheduler调用一个job,就会将jobExecuteContext传递给job的execute方法
job能够通过该对象访问到quartz运行时候的环境以及job本身的明细数据
jobDataMap用于存储Job实例的状态信息,在excute执行中,可以通过JobExecuteContext去获取map中的值
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("任务1","组1")
//将map信息存放进jobDataMap中
.usingJobData("jobKey","jobValue")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("触发器1","组1")
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
//将map信息存放进jobDataMap中
.usingJobData("triggerKey","triggerValue")
.build();
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
//从context中获取JobDataMap存储的信息
System.out.println(context.getJobDetail().getJobDataMap().get("jobKey"));
System.out.println(context.getTrigger().getJobDataMap().get("triggerKey"));
}
}
在Job任务每次执行时,JobDetail都会生成一个新的实例(包括内部的Job),例如间隔一秒执行一次任务,那么间隔一秒就会生成一个新的jobdetail实例
这是为了规避并发访问的问题,例比如一个任务要执行10秒,而调度算法是每秒钟触发1次,创建新的实例就是为了使每次调度都能执行,此时就有可能多个任务被并发执行
这两个注解一般搭配使用
@DisallowConcurrentExecution
@PersistJobDataAfterExecution
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
//用户判断是否是同一个对象
System.out.println("jobDetail:"+System.identityHashCode(context.getJobDetail()));
System.out.println("job:"+System.identityHashCode(context.getJobInstance()));
}
}
注意点: @DisallowConcurrentExecution是对JobDetail实例生效,也如果定义两个JobDetail,引用一个Job类,是可以并发执行的
定时器要调度多个定时任务,就得有一个线程池来进行任务的并发处理
当执行schedulerFactory.getScheduler()时,会初始化一个线程池SimpleThreadPool
在源码中,默认创建的线程池是一个SimpleThreadPool
此SimpleThreadPool是一个比较简单的线程池实现,只有线程数这一个属性,不像其他功能比较丰富的线程池有像核心线程数、最大线程数、队列大小等参数
此SimpleThreadPool的默认线程数为10
可以在配置文件或配置类中进行配置
//代码配置
Properties prop = new Properties();
// 线程池配置
prop.put("org.quartz.threadPool.threadCount", "20");
SchedulerFactory schedulerFactory = new StdSchedulerFactory(prop);
Scheduler scheduler = schedulerFactory.getScheduler();
列出了以下几种常见的情况
当job达到触发时间时,所有线程都被其他job占用,没有可用线程
在job需要触发的时间点,scheduler停止了(可能是手动调用pasue等方法,但也可能是意外停止的)
job使用了@ DisallowConcurrentExecution注解,job不能并发执行,当达到下一个job执行点的时候,上一个任务还没有完成
job指定了过去的开始执行时间,例如当前时间是8点00分0O秒,指定开始时间为7点00分00秒
(1) 增加线程池数,防止线程数不够的情况
(2) 设置任务执行的阈值,在到时间点后一段时间内仍可以执行
Properties prop = new Properties();
//1.线程池配置
prop.put("org.quartz.threadPool.threadCount", "20");
//2.设置任务执行时间阈值 单位是毫秒
prop.put("org.quartz.jobStore.misfireThreshold", "10000");
SchedulerFactory schedulerFactory = new StdSchedulerFactory(prop);
Scheduler scheduler = schedulerFactory.getScheduler();
(3) 设置错过的处理策略
具体的处理策略有很多,实际使用时可以自行百度
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("触发器1", "组1")
.withSchedule(
SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever()
// 设置错失触发后的调度策略
.withMisfireHandlingInstructionNowWithRemainingCount()
)
.build();
maven依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-quartzartifactId>
dependency>
(1) 启动类上添加@EnableScheduling
(2) 配置定时任务@Scheduled
(1) quartz配置
默认情况下,Quartz 会加载 classpath 下的 quartz.properties 作为配置文件。如果找不到,则会使用 quartz 框架自己 jar 包下 org/quartz 底下的 quartz.properties 文件
/**
* @author ruoxi
* @createTime 2022/9/14 16:41
*/
@Configuration
public class QuartzConfig implements SchedulerFactoryBeanCustomizer {
@Bean
public Properties properties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
// 对quartz.properties文件进行读取
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
// 在quartz.properties中的属性被读取并注入后再初始化对象
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setQuartzProperties(properties());
return schedulerFactoryBean;
}
/**
* quartz初始化监听器
*/
@Bean
public QuartzInitializerListener executorListener() {
return new QuartzInitializerListener();
}
/**
* 通过SchedulerFactoryBean获取Scheduler的实例
*/
@Bean
public Scheduler scheduler() throws IOException {
return schedulerFactoryBean().getScheduler();
}
/**
* 使用阿里的druid作为数据库连接池
*/
@Override
public void customize(@NotNull SchedulerFactoryBean schedulerFactoryBean) {
schedulerFactoryBean.setStartupDelay(2);
schedulerFactoryBean.setAutoStartup(true);
schedulerFactoryBean.setOverwriteExistingJobs(true);
}
}
(3)使用
/**
* @author ruoxi
* @createTime 2022/9/14 16:45
*/
@Service
@Log4j
public class QuartzService {
@Autowired
private Scheduler scheduler;
/**
* 新增定时任务
*
* @param jName 任务名称
* @param jGroup 任务组
* @param tName 触发器名称
* @param tGroup 触发器组
* @param cron cron表达式
*/
public void addjob(String jName, String jGroup, String tName, String tGroup, String cron) {
try {
// 构建JobDetail
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity(jName, jGroup)
.build();
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(tName, tGroup)
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(cron))
.build();
// 启动调度器
scheduler.start();
scheduler.scheduleJob(jobDetail, trigger);
} catch (Exception e) {
System.out.println("创建定时任务失败" + e);
}
}
public void pausejob(String jName, String jGroup) throws SchedulerException {
scheduler.pauseJob(JobKey.jobKey(jName, jGroup));
}
public void resumejob(String jName, String jGroup) throws SchedulerException {
scheduler.resumeJob(JobKey.jobKey(jName, jGroup));
}
public void rescheduleJob(String jName, String jGroup, String cron) throws SchedulerException {
TriggerKey triggerKey = TriggerKey.triggerKey(jName, jGroup);
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
// 按新的trigger重新设置job执行,重启触发器
scheduler.rescheduleJob(triggerKey, trigger);
}
public void deletejob(String jName, String jGroup) throws SchedulerException {
scheduler.pauseTrigger(TriggerKey.triggerKey(jName, jGroup));
scheduler.unscheduleJob(TriggerKey.triggerKey(jName, jGroup));
scheduler.deleteJob(JobKey.jobKey(jName, jGroup));
}
}
具体的cron表达式可以看这篇博客 Cron表达式