调度框架Quartz

引言

针对耗时操作、定时操作和周期操作等,常常会通过异步线程调用执行这些操作。实现定时异步操作有多种方案,比如使用Spring boot异步机制和调度框架等。Spring boot异步机制使用简单,只需定义异步方法便可执行异步操作,但是任务监控、任务复杂操作不易实现。调度框架管理任务更加灵活,提供更加丰富的功能,常见的调度框架有Quartz、ElasticJob和XXL-JOB等

一、Quartz简介

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

Quartz 具有如下几个关键概念:

  1. Job 定义了一个任务的执行逻辑。
  2. JobDetail 是 Job 的一个执行实例。
  3. Trigger 是 JobDetail 的执行计划,定义了 JobDetail 什么时候执行、异常情况怎么执行等。
  4. Scheduler是调度容器,用来注册 JobDetail 和 Trigger,管理执行任务的执行、暂停、恢复等操作。
  5. 监听器,分为 JobListener、TriggerListener和SchedulerListener,分别用来监听 JobDetail、Trigger 和Scheduler 的事件并做出增强处理,给任务协调执行提供了支持。

1、Job

通过实现Job接口,定义任务的执行逻辑。在Job接口中,有一个方法 execute(JobExecutionContext var1),JobExecutionContext为任务执行的上下文,可以通过这个参数来获取执行任务实例JobDetail、任务参数JobDataMap、执行计划Trigger和Scheduler等,JobExecutionContext参数为实现任务执行逻辑提供了较高的灵活性 。

java 复制代码
public interface Job {  
    void execute(JobExecutionContext var1) throws JobExecutionException;  
}

2、JobDetail

JobDetail 表示一个 Job 的执行实例,通过 JobBuilder 可以创建一个JobDetail实例,每个JobDetail需要指定一个 Identity 标识,Identity 标识分为 name 和 group 两部分,需要保证调度上下文中 Identity 唯一。

java 复制代码
JobDetail jobDetail = JobBuilder.newJob(jobClass)  
    .withIdentity(name, group)
    // .storeDurably() 用来持久化,即执行该任务后,数据库不会删除该任务,下次定义Trigger时还可以使用
    .setJobData(jobDataMap)  
    .build();

3、Trigger

使用Trigger可以配置JobDetail的执行计划,通过使用 TriggerBuilder 可以创建一个 Trigger 实例,并通过 withIdentity 方法可以指定 JobDetail 实例,通过 withSchedule 方法配置调度策略。

java 复制代码
TriggerBuilder.newTrigger()  
    .withIdentity(jobKey.getName(), jobKey.getGroup())  
    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(seconds).withRepeatCount(1))  
    .build();

ScheduleBuilder用来配置调度策略,ScheduleBuilder具有如下四个子类

4、Scheduler

Scheduler 用来注册JobDetail、Trigger和监听器,可以管理任务的执行生命周期。

java 复制代码
/**
该接口具有较多方法,只列举几个较重要的。通过该接口还可以获取调度容器有哪些JobDetail和Trigger
*/
public interface Scheduler {
// 添加任务
Date scheduleJob(JobDetail var1, Trigger var2) throws SchedulerException;  

// 添加任务,对应的JobDetail已持久化
Date scheduleJob(Trigger var1) throws SchedulerException;

// 修改执行计划
Date rescheduleJob(TriggerKey var1, Trigger var2) throws SchedulerException;

// 删除任务
boolean deleteJob(JobKey var1) throws SchedulerException;

// 暂停任务
void pauseJob(JobKey var1) throws SchedulerException;

// 恢复任务
void resumeJob(JobKey var1) throws SchedulerException;

// 获取监听器,获取的ListenerManager可以注册监听器
ListenerManager getListenerManager() throws SchedulerException;
}

Scheduler 实现类如下图所示,默认使用 StdScheduler

5、监听器

监听器分为 JobListener、TriggerListener 和 SchedulerListener, JobListener 用来监听JobDetail, TriggerListener 用来监听Trigger, SchedulerListener 用来监听 Scheduler。通过使用 Scheduler.getListenerManager 方法获取的 ListenerManager 实例对象可以注册监听器。

java 复制代码
public interface JobListener {  
    String getName();  
    // 任务执行前处理
    void jobToBeExecuted(JobExecutionContext var1);  
    // 在JobDetail即将被执行,但又被TriggerListener否决时会调用该方法
    void jobExecutionVetoed(JobExecutionContext var1);  
    // 任务执行后处理
    void jobWasExecuted(JobExecutionContext var1, JobExecutionException var2);  
}
java 复制代码
public interface TriggerListener {  
    String getName();  
    // 调度器触发时处理
    void triggerFired(Trigger var1, JobExecutionContext var2);  
    // 在Trigger即将被执行,但又被Scheduler否决时会调用该方法
    boolean vetoJobExecution(Trigger var1, JobExecutionContext var2);  
    // 触发器错失触发时处理
    void triggerMisfired(Trigger var1);  
    // 触发器触发并且对应的JobDetail执行完后处理
    void triggerComplete(Trigger var1, JobExecutionContext var2, Trigger.CompletedExecutionInstruction var3);  
}
java 复制代码
public interface SchedulerListener {  
void jobScheduled(Trigger var1);  
  
void jobUnscheduled(TriggerKey var1);  
  
void triggerFinalized(Trigger var1);  
  
void triggerPaused(TriggerKey var1);  
  
void triggersPaused(String var1);  
  
void triggerResumed(TriggerKey var1);  
  
void triggersResumed(String var1);  
  
void jobAdded(JobDetail var1);  
  
void jobDeleted(JobKey var1);  
  
void jobPaused(JobKey var1);  
  
void jobsPaused(String var1);  
  
void jobResumed(JobKey var1);  
  
void jobsResumed(String var1);  
  
void schedulerError(String var1, SchedulerException var2);  
  
void schedulerInStandbyMode();  
  
void schedulerStarted();  
  
void schedulerStarting();  
  
void schedulerShutdown();  
  
void schedulerShuttingdown();  
  
void schedulingDataCleared();  
}

二、Quartz 简单使用

Spring boot 使用 Quartz 有两种任务存储方式,分别是内存和数据库,这里介绍基于数据库存储方式的使用

1、引入依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

在该依赖中,QuartzAutoConfiguration 提供了 Quartz 的自动配置,该类创建了一个Scheduler Bean,具体类为Stdcheduler,同时会自动扫描 JobDetail 和 Trigger Bean,并将这些 Bean 存放到数据库中。

2、添加 Quartz 配置

在 application.yml 文件中添加配置

yml 复制代码
spring:
  application:
    name: quartz-task
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/quartz_test?serverTimezone=Hongkong&characterEncoding=utf-8&useSSL=false
    username: 
    password: 
  quartz:
    job-store-type: jdbc
    jdbc:
      initialize-schema: always # 启动时初始化 Quartz 数据表方式

Quartz 数据库表文件可以查看 org.quartz-scheduler 包,路径为:quartz-x.x.x.jar/org/quartz/impl/jdbcjobstore ,在 SQL脚本中首先会删除表,再创建表。如果需要在项目启动时保留表中数据,可以让项目自己管理 quartz 的 SQL 脚本,不执行 Quartz 的数据库初始化。SQL文件如下图所示

3、定义任务 Job

java 复制代码
@Slf4j
public class HelloWorldJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        JobDetail jobDetail = jobExecutionContext.getJobDetail();
        JobKey key = jobDetail.getKey();
        log.info("HelloWorldJob begin. name: {}, group: {}", key.getName(), key.getGroup());
        Trigger trigger = jobExecutionContext.getTrigger();
        JobDataMap jobDataMap = trigger.getJobDataMap();
        Set<Map.Entry<String, Object>> entrySet = jobDataMap.entrySet();
        for (Map.Entry<String, Object> entry : entrySet) {
            log.info("trigger data: key = {}, value = {}.", entry.getKey(), entry.getValue());
        }
        log.info("HelloWorldJob end. name: {}, group: {}", key.getName(), key.getGroup());
    }
}

4、配置任务执行实例

执行任务有两种方式,一种是定义JobDetail和Trigger Bean,并将其注入到Spring容器中;另一种时,调用 Scheduler Bean提供的方法添加任务。

1、方式一

定义JobDetail和Trigger Bean

java 复制代码
@Configuration
public class QuartzConfig {

    @Bean
    public JobDetail helloWorldJobDetail() {
        JobKey jobKey = new JobKey("HelloWorld", "DefaultGroup");
        return JobBuilder.newJob(HelloWorldJob.class)
                .withIdentity(jobKey)
                .storeDurably()
                .build();
    }
    
    @Bean
    public Trigger helloWorldTrigger() {
        JobKey jobKey = new JobKey("HelloWorld", "DefaultGroup");
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        Date now = new Date();
        String nowAsString = sdf.format(now);
        return TriggerBuilder.newTrigger()
                    .forJob(jobKey)
                    .usingJobData("execute time", nowAsString)
                    .startNow()
                    // 隔3秒执行一次,总共执行 3+1 次
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).withRepeatCount(3))
                    .build();
    }
}

执行结果:

2、方式二

调用 Scheduler Bean 方法添加任务、暂停任务等

java 复制代码
@Component
public class SchedulerManager {
    @Autowired
    private Scheduler scheduler;
}

自动注入Scheduler后,可调用它的方法

java 复制代码
// 部分方法,添加任务、暂停任务、恢复任务
public interface Scheduler {

    List<JobExecutionContext> getCurrentlyExecutingJobs() throws ShedulerException;

    ListenerManager getListenerManager() throws SchedulerException;

    Date scheduleJob(JobDetail var1, Trigger var2) throws SchedulerException;

    Date scheduleJob(Trigger var1) throws SchedulerException;

    void scheduleJobs(Map<JobDetail, Set<? extends Trigger>> var1, boolean var2) throws SchedulerException;

    void scheduleJob(JobDetail var1, Set<? extends Trigger> var2, boolean var3) throws SchedulerException;

    boolean unscheduleJob(TriggerKey var1) throws SchedulerException;

    boolean unscheduleJobs(List<TriggerKey> var1) throws SchedulerException;
    // 修改执行计划
    Date rescheduleJob(TriggerKey var1, Trigger var2) throws SchedulerException;

    void addJob(JobDetail var1, boolean var2) throws SchedulerException;

    void addJob(JobDetail var1, boolean var2, boolean var3) throws SchedulerException;

    boolean deleteJob(JobKey var1) throws SchedulerException;

    boolean deleteJobs(List<JobKey> var1) throws SchedulerException;

    void triggerJob(JobKey var1) throws SchedulerException;

    void triggerJob(JobKey var1, JobDataMap var2) throws SchedulerException;

    void pauseJob(JobKey var1) throws SchedulerException;

    void pauseJobs(GroupMatcher<JobKey> var1) throws SchedulerException;

    void pauseTrigger(TriggerKey var1) throws SchedulerException;

    void pauseTriggers(GroupMatcher<TriggerKey> var1) throws SchedulerException;

    void resumeJob(JobKey var1) throws SchedulerException;

    void resumeJobs(GroupMatcher<JobKey> var1) throws SchedulerException;

    void resumeTrigger(TriggerKey var1) throws SchedulerException;

    void resumeTriggers(GroupMatcher<TriggerKey> var1) throws SchedulerException;

    void pauseAll() throws SchedulerException;

    void resumeAll() throws SchedulerException;

    List<String> getJobGroupNames() throws SchedulerException;
}

5、@DisallowConcurrentExecution和@PersistJobDataAfterExecution注释说明

  1. @DisallowConcurrentExecution:用在 Job 接口实现类上,用来保证不会同时执行该 Job 接口实现类执行实例
  2. @PersistJobDataAfterExecution:用在 Job 接口实现类,表示在正常执行完Job后, JobDataMap中的数据应该被改动,下次执行相同的job时,会接收到已经更新的数据

6、如何在 Job 执行逻辑中使用 Spring 容器 Bean

由于 Job 接口实现类不被 Spring 容器管理,无法直接通过自动注入注入Bean,需要定义一个静态方法获取 Spring Bean。

java 复制代码
@Component
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtil.applicationContext = applicationContext;
    }

    /**
     * 根据名称获取bean
     *
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName) {
        if (beanName == null) {
            return null;
        }
        return applicationContext.getBean(beanName);
    }

    public static <T> T getBean(Class<T> clazz) {
        if (clazz == null) {
            return null;
        }
        return applicationContext.getBean(clazz);
    }
}

三、总结

通过使用 Quartz 调度框架,可以灵活的管理任务。在 Quartz 中,具有如下几个关键概念:Job、JobDetail、Trigger、Scheduler 和 Listener。另外,管理、监控任务等操作,关键在于使用 Scheduler 方法。

相关推荐
cdut_suye3 分钟前
Linux工具使用指南:从apt管理、gcc编译到makefile构建与gdb调试
java·linux·运维·服务器·c++·人工智能·python
苹果醋315 分钟前
2020重新出发,MySql基础,MySql表数据操作
java·运维·spring boot·mysql·nginx
小蜗牛慢慢爬行17 分钟前
如何在 Spring Boot 微服务中设置和管理多个数据库
java·数据库·spring boot·后端·微服务·架构·hibernate
azhou的代码园20 分钟前
基于JAVA+SpringBoot+Vue的制造装备物联及生产管理ERP系统
java·spring boot·制造
wm10431 小时前
java web springboot
java·spring boot·后端
smile-yan1 小时前
Provides transitive vulnerable dependency maven 提示依赖存在漏洞问题的解决方法
java·maven
老马啸西风1 小时前
NLP 中文拼写检测纠正论文-01-介绍了SIGHAN 2015 包括任务描述,数据准备, 绩效指标和评估结果
java
Earnest~1 小时前
Maven极简安装&配置-241223
java·maven
皮蛋很白1 小时前
Maven 环境变量 MAVEN_HOME 和 M2_HOME 区别以及 IDEA 修改 Maven repository 路径全局
java·maven·intellij-idea
青年有志1 小时前
JavaWeb(一) | 基本概念(web服务器、Tomcat、HTTP、Maven)、Servlet 简介
java·web