当前位置: 首页 > news >正文

Quartz 快速入门案例,看这一篇就够了

前言

Quartz 是基于 Java 实现的任务调度框架,对任务的创建、修改、删除、触发以及监控这些操作直接提供了 api,这意味着开发人员拥有最大的操作权,也带来了更高的灵活性。

什么是任务调度?

任务调度指在将来某个特定的时间、固定的时间周期或规律变化的时间周期到达时自动调度并执行指定的任务。

文章稍长,建议点赞收藏😉,点击跳转目录👉 SpringBoot 整合 Quartz 👇

一、Quartz 是什么?

Quartz 是一个开源的作业调度框架,它完全由 Java 写成,并设计用于 J2SE 和 J2EE 应用中。它提供了巨大的灵活性而不牺牲简单性。你能够用它来为执行一个作业而创建简单的或复杂的调度。它有很多特征,如:数据库支持,集群,插件,EJB 作业预构建,JavaMail 及其它,支持 cron-like 表达式等等。

官网地址👉 http://www.quartz-scheduler.org

示例代码:

import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.StdSchedulerFactory;public class QuartzTest {public static void main(String[] args) {try {// Grab the Scheduler instance from the Factory Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// and start it offscheduler.start();scheduler.shutdown();} catch (SchedulerException se) {se.printStackTrace();}}
}

在start()和shutdown()之间可以执行一些操作

// define the job and tie it to our HelloJob class
JobDetail job = JobBuilder.newJob(HelloJob.class).withIdentity("job1", "group1").build();// Trigger the job to run now, and then repeat every 40 seconds
Trigger trigger=TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(40).repeatForever())            .build();// Tell quartz to schedule the job using our trigger
scheduler.scheduleJob(job, trigger);

二、核心概念

常用API概述
Scheduler与调度程序交互的主要API
Job你想要调度器执行的任务组件需要实现的接口
JobDetail用于定义作业的实例
Trigger定义执行给定作业的计划的组件
JobBuilder用于定义/构建 JobDetail 实例,用于定义作业的实例
TriggerBuilder用于定义/构建触发器实例

1.任务Job

我们想要调度的任务都必须实现 org.quartz.job 接口,然后实现接口中定义的 execute( ) 方法

代码如下(示例):

@Slf4j
public class HelloJob implements Job {@Overridepublic void execute(JobExecutionContext context) {log.info("Hello Job 执行时间: {}", DateUtil.now());}
}

那么如何给job实例增加属性或配置呢?如何在job的多次执行中,跟踪job的状态呢?

答案就是 JobDataMap,JobDetail对象的一部分

JobDataMap 可以包含不限量的(序列化的)数据对象,在 job 实例执行的时候,可以使用其中的数据;
JobDataMap 是 Java Map接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。

如下示例:

 // define the job and tie it to our DumbJob classJobDetail job = newJob(DumbJob.class).withIdentity("myJob", "group1") // name "myJob", group "group1".usingJobData("jobSays", "Hello World!").usingJobData("myFloatValue", 3.141f).build();
public class HelloJob implements Job {public HelloJob() {}public void execute(JobExecutionContext context)throws JobExecutionException{JobKey key = context.getJobDetail().getKey();JobDataMap dataMap = context.getJobDetail().getJobDataMap();String jobSays = dataMap.getString("jobSays");float myFloatValue = dataMap.getFloat("myFloatValue");System.err.println("Instance " + key + " of HelloJob says: " + jobSays + ", and val is: " + myFloatValue);}
}

Job 状态

在调用 execute 方法之前都会创建一个新的 Job 实例,这就牵引出了 Job 状态的概念

有无状态说明
无状态的 Job每次调用时都会创建一个新的 JobDataMap
有状态的 Job多次 Job 调用可以持有一些状态信息,这些状态信息存储在 JobDataMap 中

跟踪 Job状态时,需要在任务类上加个注解@PersistJobDataAfterExecution,让 Job 变成有状态

@Slf4j
@PersistJobDataAfterExecution
public class HelloJob implements Job {private Integer executeCount;public void setExecuteCount(Integer executeCount) {this.executeCount = executeCount;}@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {String data = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));log.info("execute count: {}, current time: {}",++executeCount, data);//将累加的 count 存入JobDataMap中jobExecutionContext.getJobDetail().getJobDataMap().put("executeCount", executeCount);}
}
/** OUTPUT:
execute count: 1, current time: 2020-11-17 22:28:48
execute count: 2, current time: 2020-11-17 22:28:52
execute count: 3, current time: 2020-11-17 22:28:57
**/

2.触发器 Trigger

Trigger 作为执行任务的调度器。我们如果想要凌晨1点执行备份数据的任务,那么 Trigger 就会设置凌晨1点执行该任务。其中 Trigger 又分为 SimpleTriggerCronTrigger 两种,通过一个 TriggerKey 唯一标识

公共属性

属性概述
JobKey表示job实例的标识,触发器被触发时,该指定的job实例会执行
StartTime表示触发器的时间表 首次被触发的时间,值类型是Java.util.Date
EndTime指定触发器的不再触发的时间,它的值类型是Java.util.Date

优先级(priority)

如果 Trigger 有多个,你可以为 Trigger 设置 priority(优先级) 属性(priority属性的值可以是任意整数,正数、负数),优先级高的 Trigger 会被首先触发,如果没有为trigger设置优先级,trigger使用默认优先级,值为5;

PS:🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧

  • 只有同时触发的trigger之间才会比较优先级。10:59触发的trigger总是在11:00触发的trigger之前执行。
  • 如果trigger是可恢复的,在恢复后再调度时,优先级与原trigger是一样的。

🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧

SimpleTrigger 及 CronTrigger

触发器对比
SimpleTrigger在一个指定时间段内执行一次作业任务或是在指定时间间隔内执行多次作业任务
CronTrigger基于日历的作业调度器,Cron表达式配置CronTrigger的实例,而不是像SimpleTrigger那样精确指定间隔时间,比SimpleTrigger更常用

SimpleTrigger

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

比如我想要在一个指定的时间段内执行一次任务,我们只需要这样写:

Trigger trigger = TriggerBuilder.newTrigger().withIdentity("testTrigger", "testTriggerGroup").startAt(startTime) //自定义执行时间.build();

再者我想在指定的时间间隔内多次执行该任务,我们可以这样写:

Trigger trigger = TriggerBuilder.newTrigger().withIdentity("testTrigger", "testTriggerGroup").withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)).withRepeatCount(2)) // 每5秒执行一次,连续执行3次后停止,从 0 开始计数.build();

我们来总结一下上面的示例:

  • SimpleTrigger具备的属性有:开始时间结束时间重复次数重复的时间间隔

  • 重复次数的值可以为 0正整数、或常量 SimpleTrigger.REPEAT_INDEFINITELY

  • 重复的时间间隔属性值必须大于 0 或长整型的正整数,以 毫秒 作为时间单位,当重复的时间间隔为 0 时,意味着与 Trigger 同时触发执行

  • 结束时间和重复次数同时存在时,以结束时间优先

CronTrigger

跟 SimpleTrigger 执行间隔时间触发的相比,CronTrigger 更加灵活,它是基于日历的作业调度器。使用 CronTrigger 我们可以执行某个时间点执行,例如 “每天的凌晨1点执行”、“每个工作日的 12 点执行”,也可以像 SimpleTrigger 那样执行一个开始时间和结束时间运行任务

Cron表达式 是用来配置 CronTrigger 实例,它是一个由 7 个子表达式组成的字符串(在线Cron表达式生成器)

使用示例


Trigger trigger = TriggerBuilder.newTrigger().withIdentity("testTrigger", "testTriggerGroup").withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * 6 4 ?")).build();

3.实例 JobDetail

Quartz 在每次执行 Job 时,都重新创建一个 Job 实例,所以它不直接接受一个 Job 的实例,相反它接收一个 Job 实现类。描述 Job 的实现类及其它相关的静态信息,如 Job 名字、描述等。

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

4.调度器 Scheduler

Scheduler为任务的调度器,它会将任务 job 及触发器 Trigger 整合起来,负责基于 Trigger 设定的时间来执行 Job。

三、体系结构

四、SpringBoot 整合 Quartz

上面我们大概介绍了 Quartz,那么该如何使用了,请往下看:

1.引入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

2.Quartz 配置

 quartz:# 参见 org.springframework.boot.autoconfigure.quartz.QuartzPropertiesjob-store-type: jdbcwait-for-jobs-to-complete-on-shutdown: truescheduler-name: SpringBootDemoSchedulerproperties:org.quartz.threadPool.threadCount: 5org.quartz.threadPool.threadPriority: 5org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: trueorg.quartz.jobStore.misfireThreshold: 5000org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTXorg.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate# 在调度流程的第一步,也就是拉取待即将触发的triggers时,是上锁的状态,即不会同时存在多个线程拉取到相同的trigger的情况,也就避免的重复调度的危险。org.quartz.jobStore.acquireTriggersWithinLock: true
工作数据概述
RAMJobStore将其所有数据保存在RAM中,是使用最简单的JobStore,它也是性能最高的(在CPU时间方面
JDBCJobStore通过JDBC将其所有数据保存在数据库中

说明: 🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶

  • JDBCJobStore几乎与任何数据库一起使用,已被广泛应用于Oracle,PostgreSQL,MySQL,MSSQLServer,HSQLDB和DB2。
  • 要使用JDBCJobStore,必须首先创建一组数据库表以供Quartz使用。
  • 可以在Quartz发行版的docs/dbTables目录中找到表创建SQL脚本

🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶🪶

3.自定义任务

HelloJob.java

@Slf4j
public class HelloJob implements Job {@Overridepublic void execute(JobExecutionContext context) {log.info("Hello Job 执行时间: {}", DateUtil.now());}
}

TestJob.java

@Slf4j
public class TestJob implements Job {@Overridepublic void execute(JobExecutionContext context) {log.info("Hello Job 执行时间: {}", DateUtil.now());}
}

4.实现动态管理定时任务

对任务的暂停恢复删除等操作,只需调用Scheduler 的对应方法即可

JobKey jobKey = JobKey.jobKey(“jobName”, “jobGroup”);
scheduler.pauseJob(jobKey);//暂停任务
scheduler.resumeJob(jobKey);//恢复任务
scheduler.deleteJob(jobKey);//删除任务

封装任务调度类

JobForm.java

@Data
public class JobForm {/*** 定时任务全类名*/@NotBlank(message = "类名不能为空")private String jobClassName;/*** 任务组名*/@NotBlank(message = "任务组名不能为空")private String jobGroupName;/*** 定时任务cron表达式*/@NotBlank(message = "cron表达式不能为空")private String cronExpression;
}

JobService.java

public interface JobService {/*** 添加并启动定时任务** @param form 表单参数 * @throws Exception 异常*/void addJob(JobForm form) throws Exception;/*** 删除定时任务** @param form 表单参数 * @throws SchedulerException 异常*/void deleteJob(JobForm form) throws SchedulerException;/*** 暂停定时任务** @param form 表单参数 * @throws SchedulerException 异常*/void pauseJob(JobForm form) throws SchedulerException;/*** 恢复定时任务** @param form 表单参数 * @throws SchedulerException 异常*/void resumeJob(JobForm form) throws SchedulerException;/*** 重新配置定时任务** @param form 表单参数 * @throws Exception 异常*/void cronJob(JobForm form) throws Exception;/*** 查询定时任务列表** @param currentPage 当前页* @param pageSize    每页条数* @return 定时任务列表*/PageInfo<JobAndTrigger> list(Integer currentPage, Integer pageSize);
}

JobServiceImpl.java

@Service
@Slf4j
public class JobServiceImpl implements JobService {private final Scheduler scheduler;private final JobMapper jobMapper;@Autowiredpublic JobServiceImpl(Scheduler scheduler, JobMapper jobMapper) {this.scheduler = scheduler;this.jobMapper = jobMapper;}/*** 添加并启动定时任务*{@link JobForm}* @return * @throws Exception 异常*/@Overridepublic void addJob(JobForm form) throws Exception {// 启动调度器scheduler.start();// 构建Job信息JobDetail jobDetail = JobBuilder.newJob(JobUtil.getClass(form.getJobClassName()).getClass()).withIdentity(form.getJobClassName(), form.getJobGroupName()).build();// Cron表达式调度构建器(即任务执行的时间)CronScheduleBuilder cron = CronScheduleBuilder.cronSchedule(form.getCronExpression());//根据Cron表达式构建一个TriggerCronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(form.getJobClassName(), form.getJobGroupName()).withSchedule(cron).build();try {scheduler.scheduleJob(jobDetail, trigger);} catch (SchedulerException e) {log.error("【定时任务】创建失败!", e);throw new Exception("【定时任务】创建失败!");}}/*** 删除定时任务*{@link JobForm}* @throws SchedulerException 异常*/@Overridepublic void deleteJob(JobForm form) throws SchedulerException {scheduler.pauseTrigger(TriggerKey.triggerKey(form.getJobClassName(), form.getJobGroupName()));scheduler.unscheduleJob(TriggerKey.triggerKey(form.getJobClassName(), form.getJobGroupName()));scheduler.deleteJob(JobKey.jobKey(form.getJobClassName(), form.getJobGroupName()));}/*** 暂停定时任务*{@link JobForm}* @throws SchedulerException 异常*/@Overridepublic void pauseJob(JobForm form) throws SchedulerException {scheduler.pauseJob(JobKey.jobKey(form.getJobClassName(), form.getJobGroupName()));}/*** 恢复定时任务*{@link JobForm}* @throws SchedulerException 异常*/@Overridepublic void resumeJob(JobForm form) throws SchedulerException {scheduler.resumeJob(JobKey.jobKey(form.getJobClassName(), form.getJobGroupName()));}/*** 重新配置定时任务*{@link JobForm}* @throws Exception 异常*/@Overridepublic void cronJob(JobForm form) throws Exception {try {TriggerKey triggerKey = TriggerKey.triggerKey(form.getJobClassName(), form.getJobGroupName());// 表达式调度构建器CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(form.getCronExpression());CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);// 根据Cron表达式构建一个Triggertrigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();// 按新的trigger重新设置job执行scheduler.rescheduleJob(triggerKey, trigger);} catch (SchedulerException e) {log.error("【定时任务】更新失败!", e);throw new Exception("【定时任务】创建失败!");}}/*** 查询定时任务列表** @param currentPage 当前页* @param pageSize    每页条数* @return 定时任务列表*/@Overridepublic PageInfo<JobAndTrigger> list(Integer currentPage, Integer pageSize) {PageHelper.startPage(currentPage, pageSize);List<JobAndTrigger> list = jobMapper.list();return new PageInfo<>(list);}
}

任务调度接口

JobController.java

@RestController
@RequestMapping("/job")
@Slf4j
public class JobController {private final JobService jobService;@Autowiredpublic JobController(JobService jobService) {this.jobService = jobService;}/*** 保存定时任务*/@PostMappingpublic ResponseEntity<ApiResponse> addJob(@Valid JobForm form) {try {jobService.addJob(form);} catch (Exception e) {return new ResponseEntity<>(ApiResponse.msg(e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);}return new ResponseEntity<>(ApiResponse.msg("操作成功"), HttpStatus.CREATED);}/*** 删除定时任务*/@DeleteMappingpublic ResponseEntity<ApiResponse> deleteJob(JobForm form) throws SchedulerException {if (StrUtil.hasBlank(form.getJobGroupName(), form.getJobClassName())) {return new ResponseEntity<>(ApiResponse.msg("参数不能为空"), HttpStatus.BAD_REQUEST);}jobService.deleteJob(form);return new ResponseEntity<>(ApiResponse.msg("删除成功"), HttpStatus.OK);}/*** 暂停定时任务*/@PutMapping(params = "pause")public ResponseEntity<ApiResponse> pauseJob(JobForm form) throws SchedulerException {if (StrUtil.hasBlank(form.getJobGroupName(), form.getJobClassName())) {return new ResponseEntity<>(ApiResponse.msg("参数不能为空"), HttpStatus.BAD_REQUEST);}jobService.pauseJob(form);return new ResponseEntity<>(ApiResponse.msg("暂停成功"), HttpStatus.OK);}/*** 恢复定时任务*/@PutMapping(params = "resume")public ResponseEntity<ApiResponse> resumeJob(JobForm form) throws SchedulerException {if (StrUtil.hasBlank(form.getJobGroupName(), form.getJobClassName())) {return new ResponseEntity<>(ApiResponse.msg("参数不能为空"), HttpStatus.BAD_REQUEST);}jobService.resumeJob(form);return new ResponseEntity<>(ApiResponse.msg("恢复成功"), HttpStatus.OK);}/*** 修改定时任务,定时时间*/@PutMapping(params = "cron")public ResponseEntity<ApiResponse> cronJob(@Valid JobForm form) {try {jobService.cronJob(form);} catch (Exception e) {return new ResponseEntity<>(ApiResponse.msg(e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);}return new ResponseEntity<>(ApiResponse.msg("修改成功"), HttpStatus.OK);}@GetMappingpublic ResponseEntity<ApiResponse> jobList(Integer currentPage, Integer pageSize) {if (ObjectUtil.isNull(currentPage)) {currentPage = 1;}if (ObjectUtil.isNull(pageSize)) {pageSize = 10;}PageInfo<JobAndTrigger> all = jobService.list(currentPage, pageSize);return ResponseEntity.ok(ApiResponse.ok(Dict.create().set("total", all.getTotal()).set("data", all.getList())));}}

演示示例

启动项目,输入http://localhost:8080/demo/job.html,进入管理界面,如图所示:

点击添加按钮,添加测试任务(每秒执行一次

在这里插入图片描述

在这里插入图片描述
添加完成后,查看后台日志,成功执行相关任务,其他操作类似,这里就不一一列举了

五、Quartz 监听器

Quartz的监听器用于当任务调度中你所关注事件发生时,能够及时获取这一事件的通知。类似于任务执行过程中的邮件、短信类的提醒

Quartz监听器主要有JobListenerTriggerListenerSchedulerListener三种,分别表示任务、触发器、调度器对应的监听器

1.JobListener

任务调度中,与任务 Job 相关的事件包括: Job 开始要执行的提示,执行完成的提示,接口如下:

public interface JobListener {String getName();void jobToBeExecuted(JobExecutionContext context);void jobExecutionVetoed(JobExecutionContext context);void jobWasExecuted(JobExecutionContext context,JobExecutionException jobException);
}

方法说明

  • getName():用于获取改JobListener 的名称

  • jobToBeExecuted():Scheduler 在 JobDetail 将要被执行时调用这个方法

  • jobExecutionVetoed():Scheduler 在 JobDetail 即将被执行,但又被 TriggerListener 否决时会调用该方法

  • jobWasExecuted():Scheduler 在 JobDetail 被执行之后调用这个方法

代码示例

Job 任务类

@Slf4j
@PersistJobDataAfterExecution
public class TestJob implements Job {@Overridepublic void execute(JobExecutionContext jobExecutionContext){System.out.println("TestJob 执行啦");}
}

JobListener

public class MyJobListener implements JobListener {@Overridepublic String getName() {String name = getClass().getSimpleName();System.out.println("监听器的名称是:" + name);return name;}@Overridepublic void jobToBeExecuted(JobExecutionContext context) {String jobName = context.getJobDetail().getKey().getName();System.out.println("Job的名称是:" + jobName + "\tScheduler在JobDetail将要被执行时调用这个方法");}@Overridepublic void jobExecutionVetoed(JobExecutionContext context) {String jobName = context.getJobDetail().getKey().getName();System.out.println("Job的名称是:" + jobName + "\tScheduler在JobDetail即将被执行,但又被TriggerListerner否决时会调用该方法");}@Overridepublic void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {String jobName = context.getJobDetail().getKey().getName();System.out.println("Job的名称是:" + jobName + "\tScheduler在JobDetail被执行之后调用这个方法");}
}

任务调度类

@Slf4j
public class TestScheduler {public static void main(String[] args) throws Exception {// 获取任务调度的实例Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// 定义任务调度实例, 并与TestJob绑定JobDetail jobDetail = JobBuilder.newJob(TestJob.class).usingJobData("executeCount", 0).withIdentity("testJob", "testJobGroup").build();// 定义触发器, 会马上执行一次, 接着5秒执行一次Trigger trigger = TriggerBuilder.newTrigger().usingJobData("testInfo", "trigger数据存放").withIdentity("testTrigger", "testTriggerGroup").startNow().withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)).build();// 创建并注册一个全局的Job Listenerscheduler.getListenerManager().addJobListener(new MyJobListener(),EverythingMatcher.allJobs());// 使用触发器调度任务的执行scheduler.scheduleJob(jobDetail, trigger);// 开启任务scheduler.start();}
}
/** OUTPUT:
监听器的名称是:MyJobListener
Job的名称是:testJob Scheduler在JobDetail将要被执行时调用这个方法
TestJob 执行啦
监听器的名称是:MyJobListener
Job的名称是:testJob Scheduler在JobDetail被执行之后调用这个方法
**/

2. TriggerListener

任务调度中,与触发器 Trigger 相关的事件包括: 触发器触发、触发器未正常触发、触发器完成等

public interface TriggerListener {public String getName();public void triggerFired(Trigger trigger, JobExecutionContext context);public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);public void triggerMisfired(Trigger trigger);public void triggerComplete(Trigger trigger, JobExecutionContext context, int triggerInstructionCode);
}

方法说明

  • getName():用于获取触发器的名称

  • triggerFired():当与监听器相关联的Trigger被触发,Job上的execute()方法将被执行时,Scheduler就调用该方法。

  • vetoJobExecution():在 Trigger 触发后,Job 将要被执行时由 Scheduler 调用这个方法。TriggerListener 给了一个选择去否决 Job 的执行。假如这个方法返回 true,这个 Job 将不会为此次 Trigger 触发而得到执行。

  • triggerMisfired():Scheduler 调用这个方法是在 Trigger 错过触发时。你应该关注此方法中持续时间长的逻辑:在出现许多错过触发的 Trigger 时,长逻辑会导致骨牌效应。你应当保持这上方法尽量的小。

  • triggerComplete():Trigger 被触发并且完成了 Job 的执行时,Scheduler 调用这个方法。

代码示例

Job 任务类

@Slf4j
@PersistJobDataAfterExecution
public class TestJob implements Job {@Overridepublic void execute(JobExecutionContext jobExecutionContext){System.out.println("TestJob 执行啦");}
}

TriggerListener

public class MyTriggerListener implements TriggerListener {private String name;public MyTriggerListener(String name) {this.name = name;}@Overridepublic String getName() {return name;}@Overridepublic void triggerFired(Trigger trigger, JobExecutionContext context) {String triggerName = trigger.getKey().getName();System.out.println(triggerName + " 被触发");}@Overridepublic boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {String triggerName = trigger.getKey().getName();System.out.println(triggerName + " 没有被触发");return false; // true:表示不会执行Job的方法}@Overridepublic void triggerMisfired(Trigger trigger) {String triggerName = trigger.getKey().getName();System.out.println(triggerName + " 错过触发");}@Overridepublic void triggerComplete(Trigger trigger, JobExecutionContext jobExecutionContext, Trigger.CompletedExecutionInstruction completedExecutionInstruction) {String triggerName = trigger.getKey().getName();System.out.println(triggerName + " 完成之后触发");}}

任务调度类

@Slf4j
public class TestScheduler {public static void main(String[] args) throws Exception {// 获取任务调度的实例Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// 定义任务调度实例, 并与TestJob绑定JobDetail jobDetail = JobBuilder.newJob(TestJob.class).usingJobData("executeCount", 0).withIdentity("testJob", "testJobGroup").build();// 定义触发器, 会马上执行一次, 接着5秒执行一次Trigger trigger = TriggerBuilder.newTrigger().usingJobData("testInfo", "trigger数据存放").withIdentity("testTrigger", "testTriggerGroup").startNow().withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)).build();// 创建并注册一个全局的Trigger Listenerscheduler.getListenerManager().addTriggerListener(new MyTriggerListener("simpleTrigger"), EverythingMatcher.allTriggers());// 使用触发器调度任务的执行scheduler.scheduleJob(jobDetail, trigger);// 开启任务scheduler.start();}
}
/** OUTPUT:
testTrigger 被触发
testTrigger 没有被触发
TestJob 执行啦
testTrigger 完成之后触发
**/

2. SchedulerListener

SchedulerListener 会在Scheduler的生命周期中关键事件发生时被调用。与Scheduler有关的事件包括:增加一个job/trigger删除一个job/triggerscheduler发生严重错误关闭scheduler等。

public interface SchedulerListener {public void jobScheduled(Trigger trigger);public void jobUnscheduled(String triggerName, String triggerGroup);public void triggerFinalized(Trigger trigger);public void triggersPaused(String triggerName, String triggerGroup);public void triggersResumed(String triggerName, String triggerGroup);public void jobsPaused(String jobName, String jobGroup);public void jobsResumed(String jobName, String jobGroup);public void schedulerError(String msg, SchedulerException cause);public void schedulerStarted();public void schedulerInStandbyMode();public void schedulerShutdown();public void schedulingDataCleared();
}

方法说明

  • jobScheduled():用于部署JobDetail时调用
  • jobUnscheduled():用于卸载JobDetail时调用
  • triggerFinalized():当一个 Trigger 来到了再也不会触发的状态时调用这个方法。除非这个 Job 已设置成了持久性,否则它就会从 Scheduler 中移除。
  • triggersPaused():Scheduler 调用这个方法是发生在一个 Trigger 或 Trigger 组被暂停时。假如是 Trigger 组的话,triggerName 参数将为 null。
  • triggersResumed():Scheduler 调用这个方法是发生成一个 Trigger 或 Trigger 组从暂停中恢复时。假如是 Trigger 组的话,假如是 Trigger 组的话,triggerName 参数将为 null。参数将为 null。
  • jobsPaused():当一个或一组 JobDetail 暂停时调用这个方法。
  • jobsResumed():当一个或一组 Job 从暂停上恢复时调用这个方法。假如是一个 Job 组,jobName 参数将为 null。
  • schedulerError():在 Scheduler 的正常运行期间产生一个严重错误时调用这个方法。
  • schedulerStarted():当Scheduler 开启时,调用该方法
  • schedulerInStandbyMode(): 当Scheduler处于StandBy模式时,调用该方法
  • schedulerShutdown():当Scheduler停止时,调用该方法
  • schedulingDataCleared():当Scheduler中的数据被清除时,调用该方法。

代码示例

Job 任务类

@Slf4j
@PersistJobDataAfterExecution
public class TestJob implements Job {@Overridepublic void execute(JobExecutionContext jobExecutionContext){System.out.println("TestJob 执行啦");}
}

SchedulerListener

public class MySchedulerListener implements SchedulerListener {@Overridepublic void jobScheduled(Trigger trigger) {String jobName = trigger.getJobKey().getName();System.out.println(jobName + " 完成部署");}@Overridepublic void jobUnscheduled(TriggerKey triggerKey) {System.out.println(triggerKey + " 完成卸载");}@Overridepublic void triggerFinalized(Trigger trigger) {System.out.println("触发器被移除 " + trigger.getJobKey().getName());}@Overridepublic void triggerPaused(TriggerKey triggerKey) {System.out.println(triggerKey + " 正在被暂停");}@Overridepublic void triggersPaused(String triggerGroup) {System.out.println("触发器组 " + triggerGroup + " 正在被暂停");}@Overridepublic void triggerResumed(TriggerKey triggerKey) {System.out.println(triggerKey + " 正在从暂停中恢复");}@Overridepublic void triggersResumed(String triggerGroup) {System.out.println("触发器组 " + triggerGroup + " 正在从暂停中恢复");}@Overridepublic void jobAdded(JobDetail jobDetail) {System.out.println(jobDetail.getKey() + " 添加工作任务");}@Overridepublic void jobDeleted(JobKey jobKey) {System.out.println(jobKey + " 删除工作任务");}@Overridepublic void jobPaused(JobKey jobKey) {System.out.println(jobKey + " 工作任务正在被暂停");}@Overridepublic void jobsPaused(String jobGroup) {System.out.println("工作任务组 " + jobGroup + " 正在被暂停");}@Overridepublic void jobResumed(JobKey jobKey) {System.out.println(jobKey + " 正在从暂停中恢复");}@Overridepublic void jobsResumed(String jobGroup) {System.out.println("工作任务组 " + jobGroup + " 正在从暂停中恢复");}@Overridepublic void schedulerError(String msg, SchedulerException cause) {System.out.println("产生严重错误时调用:   " + msg + "  " + cause.getUnderlyingException());}@Overridepublic void schedulerInStandbyMode() {System.out.println("调度器在挂起模式下调用");}@Overridepublic void schedulerStarted() {System.out.println("调度器 开启时调用");}@Overridepublic void schedulerStarting() {System.out.println("调度器 正在开启时调用");}@Overridepublic void schedulerShutdown() {System.out.println("调度器 已经被关闭 时调用");}@Overridepublic void schedulerShuttingdown() {System.out.println("调度器 正在被关闭 时调用");}@Overridepublic void schedulingDataCleared() {System.out.println("调度器的数据被清除时调用");}
}

任务调度类

@Slf4j
public class TestScheduler {public static void main(String[] args) throws Exception {// 获取任务调度的实例Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// 定义任务调度实例, 并与TestJob绑定JobDetail jobDetail = JobBuilder.newJob(TestJob.class).usingJobData("executeCount", 0).withIdentity("testJob", "testJobGroup").build();// 定义触发器, 会马上执行一次, 接着5秒执行一次Date endTime = new Date();endTime.setTime(endTime.getTime()+5000);Trigger trigger = TriggerBuilder.newTrigger().usingJobData("testInfo", "trigger数据存放").withIdentity("testTrigger", "testTriggerGroup").startNow().endAt(endTime) //设置了停止时间.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)).build();// 创建SchedulerListenerscheduler.getListenerManager().addSchedulerListener(new MySchedulerListener());// 使用触发器调度任务的执行scheduler.scheduleJob(jobDetail, trigger);// 开启任务scheduler.start();}
}
/** OUTPUT:
testJobGroup.testJob 添加工作任务
testJob 完成部署
调度器 正在开启时调用
调度器 开启时调用
TestJob 执行啦
触发器被移除 testJob
testJobGroup.testJob 删除工作任务
**/

总结

以上就是今天要讲的内容,本文仅仅简单介绍了quartz的入门案例,实现动态管理定时任务,以上方法亲测有效,希望能给大家一个参考。

创作不易,关注💖、点赞👍、收藏🎉就是对作者最大的鼓励👏,欢迎在下方评论留言🧐

相关文章:

Quartz 快速入门案例,看这一篇就够了

前言 Quartz 是基于 Java 实现的任务调度框架&#xff0c;对任务的创建、修改、删除、触发以及监控这些操作直接提供了 api&#xff0c;这意味着开发人员拥有最大的操作权&#xff0c;也带来了更高的灵活性。 什么是任务调度&#xff1f; 任务调度指在将来某个特定的时间、固…...

图解LeetCode——1233. 删除子文件夹(难道:中等)

一、题目 你是一位系统管理员&#xff0c;手里有一份文件夹列表 folder&#xff0c;你的任务是要删除该列表中的所有 子文件夹&#xff0c;并以 任意顺序 返回剩下的文件夹。 如果文件夹 folder[i] 位于另一个文件夹 folder[j] 下&#xff0c;那么 folder[i] 就是 folder[j] …...

Doris--简单使用

一、数据表的创建与数据导入 1.1、创建表 1.1.1、单分区 CREATE TABLE table1 (siteid INT DEFAULT 10,citycode SMALLINT,username VARCHAR(32) DEFAULT ,pv BIGINT SUM DEFAULT 0 -- 聚合模型&#xff0c; value column 使用sum聚合 ) AGGREGATE KEY(siteid, citycode, …...

使用GPT让你的RStudio如虎添翼

API的的调用目前来说不限制地区&#xff0c;但是OpenAI的API的申请限制了地区。运行的时候&#xff0c;如果出现了429&#xff0c;意味着你被限流了&#xff0c;需要等一会才行。 前提是&#xff0c;你需要注册一个OpenAI的账户&#xff0c;然后在https://openai.com/api/ 里申…...

Python 算法交易实验45 再探量化

说明 去年大部分精力都在构建底层架构和工具了,一直都没有时间搞量化。目前底层的数据库服务(ADB)和清洗(衍生 AETL) 工具已经好了,我想尽快的把量化启动起来。 内容 1 思想 作为交易来说,只有买卖。通过数据分析与模型,我们获得的增强点是决策。在合适的时候进行买卖的…...

Dubbo加载配置文件方式,加载流程,加载配置文件源码解析

配置方法 API配置 以Java编码的方式组织配置&#xff0c;Dubbo3配置API详解 &#xff1a;https://dubbo.apache.org/zh/docs3-v2/java-sdk/reference-manual/config/api/#bootstrap-api public static void main(String[] args) throws IOException {ServiceConfig<Greet…...

十大开源测试工具和框架,一定有你需要的

目录 前言 Katalon Studio Selenium Appium JMeter SOAP UI Robot Framework Watir JUnit Robotium Citrus 总结 前言 免费的开源框架和工具由于其开源特性&#xff0c;现在逐渐成为自动化测试的首选解决方案。区别在于&#xff0c;你是喜欢使用类库编写一个全新的…...

加密技术在android中的应用

1、算法基础 算法基础参照linux的全盘加密与文件系统加密在android中的应用 消息摘要算法 对称加密算法 非对称加密算法...

备战蓝桥杯【一维前缀和】

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…...

研报精选230214

目录 【行业230214艾瑞股份】中国增强现实&#xff08;AR&#xff09;行业研究报告【行业230214国信证券】信息安全深度剖析5&#xff1a;密评和信创双催化&#xff0c;密码产业开启从1到N【行业230214民生证券】磁性元器件深度报告&#xff1a;乘新能源之风&#xff0c;磁性元…...

【SSL/TLS】准备工作:证书格式

证书格式1. 格式说明1.1 文件编码格式1.2 文件后缀格式2. xca导出格式1. 格式说明 1.1 文件编码格式 1. PEM格式: 使用Base 64 ASCII进行编码的纯文本格式。后缀为“.pem”, ".cer", ".crt", ".key" 2. DER格式 二进制编码格式&#xff0c;文件…...

Linux常用命令---系统常用命令

Linux系统常用命令场景一&#xff1a; 查看当前系统内核版本相关信息场景二&#xff1a; sosreport 命令场景三&#xff1a; 如何定位并确定命令&#xff1f;场景四&#xff1a;查看当前系统运行负载怎场景五&#xff1a; 查看当前系统的内存可用情况场景六&#xff1a;查看网卡…...

C 结构体

C 数组允许定义可存储相同类型数据项的变量&#xff0c;结构是 C 编程中另一种用户自定义的可用的数据类型&#xff0c;它允许您存储不同类型的数据项。结构用于表示一条记录&#xff0c;假设您想要跟踪图书馆中书本的动态&#xff0c;您可能需要跟踪每本书的下列属性&#xff…...

手语检测识别

论文&#xff1a;Real-Time Sign Language Detection using Human Pose Estimation Github&#xff1a;https://github.com/google-research/google-research/tree/master/sign_language_detection SLRTP 2020 手语识别任务包括手语检测&#xff08;Sign language detection&a…...

android fwk模块之Sensor架构

本文基于Android 12源码整理&#xff0c;包含如下内容&#xff1a; 通信架构应用层实现使用方式SensorManager抽象接口具体实现fwk层的实现native中的SensorManager的初始化流程native中的消息队列初始化与数据读取sensorservice实现HAL层的实现通信架构 应用层实现 涉及代码&…...

安装less-loader5出现webpack版本不兼容

今天遇到一个问题&#xff1a; 安装less-loader5之后其它包提示peerDependencies WARNING&#xff0c;意思是包版本不兼容。 【难题】 虽然NPM已经很自动化了&#xff0c;但依赖问题真的是一个难题&#xff0c;无法自动解决&#xff0c;需要人工干预调整。 【解决办法】 去查…...

Java 网络编程

1.UDP和TCPUDP和TCP是传输层协议中最核心的两种协议他们的特点分别是UDP: 无连接,不可靠传输,面向数据报,全双工TCP: 有连接,是可靠传输,面向字节流,全双工有无连接有连接:就好比两个人打电话,打电话的一方发出连接请求,被打电话的一方选择确认连接,此时双方才能进行通话无连接…...

BEV学习记录

近期可能要经常性的开展BEV工作&#xff0c;打算把自己觉着不错的网站拿出来记录一下。 首先贴上来我还没有细读的一篇觉着不错的文章。 自动驾驶感知新范式——BEV感知经典论文总结和对比&#xff08;上&#xff09;_苹果姐的博客-CSDN博客_bev视角 开山之作--LSS ECCV 202…...

Webrtc Native C++切换音频输入源

modules/audio_device/audio_device_impl.cc #include “api/audio_options.h” #include “modules/audio_device/include/factory.h” // 创建一个 AudioDeviceModule 对象 auto audio_device_module = webrtc::AudioDeviceModule::Create( webrtc::AudioDeviceModule::kPl…...

裸辞5个月,面试了37家公司,终于找到理想工作了

上半年裁员&#xff0c;下半年裸辞&#xff0c;有不少人高呼裸辞后躺平真的好快乐&#xff01;但也有很多人&#xff0c;裸辞后的生活五味杂陈。 面试37次终于找到心仪工作 因为工作压力大、领导PUA等各种原因&#xff0c;今年2月下旬我从一家互联网小厂裸辞&#xff0c;没想…...

Mybatis-plus@DS实现动态切换数据源应用

目录1 DS实现动态切换数据源原理2 不可在事务中切换数据库分析解决3 原因解析1 DS实现动态切换数据源原理 首先mybatis-plus使用com.baomidou.dynamic.datasource.AbstractRoutingDataSource继承 AbstractDataSource接管数据源&#xff1b;具体实现类为com.baomidou.dynamic.d…...

SpringBoot的创建和使用

SpringBoot是什么&#xff1f;SpringBoot诞生的目的就是为了简化Spring开发&#xff0c;而相对于Spring&#xff0c;SpringBoot算是一个很大的升级&#xff0c;就如同汽车手动挡变成了自动挡。Spring&#xff1a;SpringBoot&#xff1a;SpringBoot的优点SpringBoot让Spring开发…...

居家电话客服宝典

客服分类从销售的流程来分&#xff0c;客服分为售前和售后。售前一般都带有销售性质&#xff0c;工资主要靠提成&#xff0c;售后一般是解答问题&#xff0c;工资主要看服务质量和差评量。从工作模式来分&#xff0c;客服分为在线客服和热线客服。在线客服以打字聊天为主&#…...

开发方案设计

1、开发流程产品需求设计-->需求粗评-->做设计方案-->粗估时-->需求细评-->排期-->开发-->提测、修bug-->code review-->上线设计方案主要是写实现思路、模块划分code review&#xff1a;完善代码&#xff0c;发现未考虑到的边界问题2、具体实现方案…...

文件路径模块pathlib

文件路径模块pathlib 文章目录文件路径模块pathlib1.概述2.创建路径2.1.创建非windos平台路径2.2.动态拼接路径joinpath2.3.替换文件名称 with_name2.4.创建固定目录2.5.创建文件夹和文件1.创建多级目录mkdir2.创建空文件3.路径解析3.1.根据路径分隔符解析路径parts3.2.获取父级…...

spring cloud篇——什么是服务熔断?服务降级?服务限流?spring cloud有什么优势?

文章目录一、spring cloud 有什么优势二、服务熔断2.1、雪崩效应2.2、DubboHystrixCommand三、服务降级四、服务限流4.1、限流算法4.2、应用级限流4.3、池化技术4.4、分布式限流4.5、基于Redis 功能的实现限流4.6、基于令牌桶算法的实现4.6.1 、Java实现一、spring cloud 有什么…...

Tomcat构建

软件架构C/S:Client/Server.需要安装才能使用。B/S:Brower/Server。有浏览器就可以。资源分类动态资源&#xff1a;每个用户访问相同的资源后&#xff0c;得到的结果可能不一样&#xff0c;称为动态资源。动态资源被访问后&#xff0c;先转换为静态资源&#xff0c;再被浏览器解…...

入门深度学习——基于全连接神经网络的手写数字识别案例(python代码实现)

入门深度学习——基于全连接神经网络的手写数字识别案例&#xff08;python代码实现&#xff09; 一、网络构建 1.1 问题导入 如图所示&#xff0c;数字五的图片作为输入&#xff0c;layer01层为输入层&#xff0c;layer02层为隐藏层&#xff0c;找出每列最大值对应索引为输…...

预算砍砍砍,IT运维如何降本增效

疫情短暂过去&#xff0c;一个乐观的共识正在蔓延&#xff1a;2023年的互联网&#xff0c;绝对不会比2022年更差。 “降本”是过去一年许多公司的核心策略&#xff0c;营销大幅缩水、亏损业务大量撤裁&#xff0c;以及层出不穷的裁员消息。而2023年在可预期的经济复苏下&#…...

10.Jenkins用tags的方式自动发布java应用

Jenkins用tags的方式自动发布java应用1.配置jenkins&#xff0c;告诉jenkins&#xff0c;jdk的安装目录&#xff0c;maven的安装目录2.构建一个maven项目指定构建参数&#xff0c;选择Git Paramete在源码管理中&#xff0c;填写我们git项目的地址&#xff0c;调用变量构建前执行…...

wordpress 照片展示/西安seo顾问培训

1 详解1.1 Buffer 结构Buffer 是抽象类&#xff0c;子类共有7个实现&#xff0c;他们都是 abstract 类型&#xff0c;如下图所示&#xff1a;1.2 Buffer 创建allocateallocate 通过指定 buffer 的容量&#xff0c;然后新建对象&#xff0c;ByteBuffer 还可以通过 allocateDirec…...

兰州网站制作公司怎么样/国外seo

PHP命名定义有的对大小写敏感&#xff0c;有的则不是&#xff0c;小编给大家整理下&#xff0c;PHP哪些大小写敏感&#xff0c;哪些大小写不敏感&#xff0c;哪些大小写敏感是可以自行定义的。 PHP是否区分大小写汇总大全&#xff1a; PHP区分大小写&#xff1a;所有的变量均区…...

wordpress海外建站/南京疫情最新消息

转&#xff1a;nohup 和>/dev/null 2>&1 一、用途&#xff1a;nohup表示永久运行。&表示后台运行 在应用Unix/Linux时&#xff0c;我们一般想让某个程序在后台运行&#xff0c;nohup ./start-mysql.sh & 该命令的一般形式为&#xff1a;nohup command & …...

网站建设论文伯乐在线/公众号推广引流

2019独角兽企业重金招聘Python工程师标准>>> 对于请求头Content-Type&#xff0c;默认application/x-www-form-urlencoded。 1、&#xff08;可用&#xff09; CloseableHttpClient client HttpClients.createDefault(); // 实例化一个post对象 Ht…...

网站空间怎么收费/电商网站规划

取决于stl的实现版本&#xff0c;&s[0]不一定是连续的。不强制要求内部实现是怎么做的&#xff0c;不过.c_str()和.data()返回的视图必须是保证连续的&#xff1f;&#xff1f;...

收银系统/广西seo优化

软件预期图像&#xff1a; 今日事logo如下&#xff1a; 软件进展如图&#xff1a; 遇到的问题&#xff1a; 在颜色设计上&#xff0c;不知道哪种更加美观&#xff0c;尝试中。 logo设计不是很完善。 目前正在边学边做阶段&#xff0c;遇到的问题较多&#xff0c;但通过查询资料…...