Quartz中的任务动态管理实例

前置文章:
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的状态信息。

全文完。

你可能感兴趣的:(Quartz中的任务动态管理实例)