前置文章:
Quartz并发、Misfire、监听器上手实例
Quartz的持久化、集群使用实例
在看过前面两篇文章的介绍后,我们终于来到最后一个关于Quartz的介绍章节,那就是如何在程序运行期间,动态地管理我们的Quartz任务。
- 获取所有JOB的信息(包含名称、分组、上次和下次触发时间、cron表达式值、当前状态、对应的类名等);
- 暂停某个具体的JOB;
- 暂停所有的JOB;
- 恢复某个具体的JOB;
- 恢复所有的JOB;
- 开始某个具体的JOB(仅一次);
- 删除某个具体的JOB;
- 添加某个具体的JOB并立即执行;
- 更改JOB的运行规则;
一、准备Job
我们需要先搭建一个普通的Job运行系统,这部分内容在先前两篇文章中都详细介绍过了,此处不再详述。
在开始之前,先引入相关必要的maven依赖。
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.projectlombok
lombok
1.18.8
provided
org.springframework.boot
spring-boot-starter-jdbc
org.springframework.boot
spring-boot-starter-quartz
mysql
mysql-connector-java
8.0.17
如下是一个普通的JOB。
@Slf4j
@DisallowConcurrentExecution
public class HelloJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info("{}开始执行时间是:{}", jobExecutionContext.getJobDetail().getKey().getName(), new Date());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("{}结束执行时间是:{}", jobExecutionContext.getJobDetail().getKey().getName(), new Date());
}
}
如下是关于该JOB的配置。
@Configuration
@PropertySource("cron.properties")
public class HelloJobConfig {
@Value("${helloJob.cron}")
private String helloJobCron;
// HelloJob的启动配置信息
@Bean
public JobDetail helloJobDetail() {
return JobBuilder.newJob(HelloJob.class)
.withIdentity("helloJob", "myJobGroup")
.storeDurably()
.build();
}
@Bean
public Trigger helloJobTrigger() {
return TriggerBuilder.newTrigger()
.forJob(helloJobDetail())
.withIdentity("helloJobTrigger", "myTriggerGroup")
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(helloJobCron))
.build();
}
}
如下是该JOB配置在cron.properties
中的表达式。
helloJob.cron=0/10 * * * * ?
以及如下的位于application.properties
中的关于Quartz的配置。
## Quartz配置
# 启动jdbcJobStore而非RamJobStore
spring.quartz.job-store-type=jdbc
# 是否覆盖持久化的job信息
spring.quartz.overwrite-existing-jobs=true
# 调度器的配置
spring.quartz.properties.org.quartz.scheduler.instanceName=MyJobScheduler
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
# 线程池的配置
spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
spring.quartz.properties.org.quartz.threadPool.threadCount=20
spring.quartz.properties.org.quartz.threadPool.threadPriority=5
# misfire配置
spring.quartz.properties.org.quartz.jobStore.misfireThreshold=60000
# jobStore的其它配置
spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
spring.quartz.properties.org.quartz.jobStore.tablePrefix=qrtz_
spring.quartz.properties.org.quartz.jobStore.isClustered=true
数据源使用spring的数据源,此处省略,可参考前述文章。
如下是非必须的关于调度器scheduler的配置。
@Configuration
public class SchedulerConfig implements SchedulerFactoryBeanCustomizer {
@Override
public void customize(SchedulerFactoryBean schedulerFactoryBean) {
schedulerFactoryBean.setStartupDelay(5);
schedulerFactoryBean.setAutoStartup(true);
schedulerFactoryBean.setOverwriteExistingJobs(true);
}
}
至此,我们的Job运行已经没有问题了,观察数据库表,发现也可以正常持久化了。
二、管理Job
首先,我们先定义一个关于JOB信息的实体类。
@Data
public class JobDTO {
@NotBlank(message = "Job名称不能为空")
private String jobName;
@NotBlank(message = "Job分组不能为空")
private String jobGroup;
private String triggerName;
private String triggerGroup;
private Date previousFireDate;
private Date nextFireDate;
private String cronExpression;
private String jobStatus;
private String jobDesc;
private String jobClassName;
}
其次,我们需要定义Job管理有哪些操作。
public interface JobService {
static final String NOT_EXISTS_THIS_JOB = "没有这个JOB!";
static final String NOT_EXISTS_THIS_TRIGGER = "没有这个Trigger";
List getAllJobs();
String pauseJob(JobDTO jobDTO);
String pauseAllJobs();
String resumeJob(JobDTO jobDTO);
String resumeAllJobs();
String startJob(JobDTO jobDTO);
String deleteJob(JobDTO jobDTO);
String addJob(JobDTO jobDTO);
String modifyJobTrigger(JobDTO jobDTO);
}
然后逐一实现这些操作。
@Slf4j
@Service("jobService")
public class JobServiceImpl implements JobService {
@Autowired
private Scheduler scheduler;
@Override
public List getAllJobs() {
// 获取所有的JobKey
GroupMatcher allMatchers = GroupMatcher.anyJobGroup();
Set jobKeySet = null;
try {
jobKeySet = scheduler.getJobKeys(allMatchers);
} catch (SchedulerException e) {
log.error("getAllJobs:{}", e);
}
return getAllJobList(jobKeySet);
}
@Override
public String pauseJob(JobDTO jobDTO) {
JobKey jobKey = JobKey.jobKey(jobDTO.getJobName(), jobDTO.getJobGroup());
try {
if (!scheduler.checkExists(jobKey)) {
return NOT_EXISTS_THIS_JOB;
}
scheduler.pauseJob(jobKey);
} catch (SchedulerException e) {
log.error("pauseJob:{}", e);
return "暂停JOB失败";
}
return "暂停JOB成功";
}
@Override
public String pauseAllJobs() {
try {
scheduler.pauseAll();
} catch (SchedulerException e) {
log.error("pauseAllJobs:{}", e);
return "全部暂停JOB失败";
}
return "全部暂停JOB成功";
}
@Override
public String resumeJob(JobDTO jobDTO) {
JobKey jobKey = JobKey.jobKey(jobDTO.getJobName(), jobDTO.getJobGroup());
try {
if (!scheduler.checkExists(jobKey)) {
return NOT_EXISTS_THIS_JOB;
}
scheduler.resumeJob(jobKey);
} catch (SchedulerException e) {
log.error("resumeJob:{}", e);
return "恢复JOB失败";
}
return "恢复JOB成功";
}
@Override
public String resumeAllJobs() {
try {
scheduler.resumeAll();
} catch (SchedulerException e) {
log.error("resumeAllJobs:{}", e);
return "全部恢复JOB失败";
}
return "全部恢复JOB成功";
}
@Override
public String startJob(JobDTO jobDTO) {
JobKey jobKey = JobKey.jobKey(jobDTO.getJobName(), jobDTO.getJobGroup());
try {
if (!scheduler.checkExists(jobKey)) {
return NOT_EXISTS_THIS_JOB;
}
scheduler.triggerJob(jobKey);
} catch (SchedulerException e) {
log.error("startJob:{}", e);
return "启动JOB失败";
}
return "启动JOB成功";
}
@Override
public String deleteJob(JobDTO jobDTO) {
JobKey jobKey = JobKey.jobKey(jobDTO.getJobName(), jobDTO.getJobGroup());
try {
if (!scheduler.checkExists(jobKey)) {
return NOT_EXISTS_THIS_JOB;
}
scheduler.deleteJob(jobKey);
} catch (SchedulerException e) {
log.error("deleteJob:{}", e);
return "删除JOB失败";
}
return "删除JOB成功";
}
@Override
public String addJob(JobDTO jobDTO) {
Class clazz = null;
try {
clazz = Class.forName(jobDTO.getJobClassName());
} catch (ClassNotFoundException e) {
log.error("addJob:{}", e);
return NOT_EXISTS_THIS_JOB;
}
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(jobDTO.getTriggerName(), jobDTO.getTriggerGroup())
.withSchedule(CronScheduleBuilder.cronSchedule(jobDTO.getCronExpression()))
.startNow()
.build();
JobDetail jobDetail = JobBuilder.newJob(clazz)
.withIdentity(jobDTO.getJobName(),jobDTO.getJobGroup())
.build();
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
log.error("addJob:{}", e);
return "添加JOB失败";
}
return "添加JOB成功";
}
@Override
public String modifyJobTrigger(JobDTO jobDTO) {
TriggerKey triggerKey = TriggerKey.triggerKey(jobDTO.getTriggerName(),jobDTO.getTriggerGroup());
Trigger newTrigger = TriggerBuilder.newTrigger()
.withIdentity(jobDTO.getTriggerName(), jobDTO.getTriggerGroup())
.withSchedule(CronScheduleBuilder.cronSchedule(jobDTO.getCronExpression()))
.startNow()
.build();
try {
if (!scheduler.checkExists(triggerKey)) {
return NOT_EXISTS_THIS_TRIGGER;
}
scheduler.rescheduleJob(triggerKey, newTrigger);
} catch (SchedulerException e) {
log.error("modifyJobTrigger:{}", e);
return "更改JOB的Trigger失败";
}
return "更改JOB的Trigger成功";
}
private List getAllJobList(Set jobKeySet) {
List jobList = new ArrayList<>();
for (JobKey jobKey : jobKeySet) {
JobDTO jobDTO = new JobDTO();
setJobInfo(jobKey, jobDTO, jobList);
}
return jobList;
}
private void setJobInfo(JobKey jobKey, JobDTO jobDTO, List jobDTOList) {
JobDetail jobDetail = null;
List triggerList = null;
try {
// 设置JOB信息
jobDTO.setJobName(jobKey.getName());
jobDTO.setJobGroup(jobKey.getGroup());
jobDetail = scheduler.getJobDetail(jobKey);
jobDTO.setJobDesc(jobDetail.getDescription());
jobDTO.setJobClassName(jobDetail.getJobClass().getCanonicalName());
// 设置trigger信息,本实例中只存在cronTrigger,同一个job可能会存在多个trigger
triggerList = (List) scheduler.getTriggersOfJob(jobKey);
for (CronTrigger trigger : triggerList) {
Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
jobDTO.setJobStatus(triggerState.name());
jobDTO.setCronExpression(trigger.getCronExpression());
jobDTO.setNextFireDate(trigger.getNextFireTime());
jobDTO.setPreviousFireDate(trigger.getPreviousFireTime());
TriggerKey triggerKey = trigger.getKey();
jobDTO.setTriggerName(triggerKey.getName());
jobDTO.setTriggerGroup(triggerKey.getGroup());
jobDTOList.add(jobDTO);
}
} catch (SchedulerException e) {
log.error("setJobInfo:{}", e);
}
}
需要注意的是,所有和数据库的持久化操作都是Quartz自动管理的,我们不需要操作任何数据库数据。
最后,我们只需要建立一个Rest层供外部调用即可。
@Slf4j
@RestController("/jobManage")
public class JobRest {
@Autowired
private JobService jobService;
@GetMapping("/getAllJobs")
public Object getAllJobs(){
return jobService.getAllJobs();
}
@PostMapping("/pauseJob")
public Object pauseJob(@Validated @RequestBody JobDTO jobDTO){
return jobService.pauseJob(jobDTO);
}
@PostMapping("/pauseAllJobs")
public Object pauseAllJobs(){
return jobService.pauseAllJobs();
}
@PostMapping("/resumeJob")
public Object resumeJob(@Validated @RequestBody JobDTO jobDTO){
return jobService.resumeJob(jobDTO);
}
@PostMapping("/resumeAllJobs")
public Object resumeAllJobs(){
return jobService.resumeAllJobs();
}
@PostMapping("/startJob")
public Object startJob(@Validated @RequestBody JobDTO jobDTO){
return jobService.startJob(jobDTO);
}
@PostMapping("/deleteJob")
public Object deleteJob(@Validated @RequestBody JobDTO jobDTO){
return jobService.deleteJob(jobDTO);
}
@PostMapping("/addJob")
public Object addJob(@Validated @RequestBody JobDTO jobDTO){
return jobService.addJob(jobDTO);
}
@PostMapping("/modifyJobTrigger")
public Object modifyJobTrigger(@Validated @RequestBody JobDTO jobDTO){
return jobService.modifyJobTrigger(jobDTO);
}
}
在进行如上代码实验的时候,除了观察Job是否运行之外,还可以观察数据库中Quartz的jobDetail数据,从而确定Job的状态信息。
全文完。