Quartz

1.核心概念

  • Scheduler(任务调度器)
  • Trigger(触发器)
  • JobDetail(任务详情)
  • Job(具体任务)

1.Scheduler
任务调度器, 通过触发器(Trigger)任务详情(JobDetail)来调度、暂停、删除任务, 调度器可以看作一个独立运行的容器, 通过将TriggerJobDetail注册到Scheduler 中, 两者在Scheduler中拥有各自的组和名称

组和名称是Scheduler查询某一对象的依据, Trigger的组和名称必须唯一, JobDetail的组和名称也必须唯一(但可以跟Trigger相同, 因为两者是不同类型)

Scheduler是一个接口

2.Trigger
触发器, 描述Job执行的时间触发规则, 有两个子类: SimpleTriggerCronTrigger,

SimpleTrigger适合仅调度一次、以固定时间间隔周期执行的调度
CronTrigger可以通过Cron表达式定义出各种复杂时间规则的调度方案, 适合更灵活、更复杂的任务调度需求

3.JobDetail
任务详情, 包括了任务的唯一标识以及具体要执行的任务, 可以通过JobDataMap往任务中传递数据

4.Job
具体任务, 是一个接口, 只定义一个方法execute()方法包含了执行任务的具体方法

1.1 核心概念图

  • Job: 要做什么事情
  • JobDetail: 封装这个任务, 设置组和名称
  • Trigger: 什么时候去做
  • Scheduler: 什么时候去做什么事情

请添加图片描述
一个Scheduler可以注册多个JobDetail和Trigger

1.2 demo

  • 任务
public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("执行任务");
    }
}
  • 初始化任务详情和触发器, 并装载到调度器中, 让调度器去执行
public static void main(String[] args) {
   // 任务详情
    JobDetail JobDetail = JobBuilder.newJob(MyJob.class)
            .withIdentity("job1", "group1")
            .build();

    // 触发器
    Trigger trigger = TriggerBuilder.newTrigger()
            .withIdentity("trigger1", "group1")
            // 执行计划, 以1秒的间隔执行, 周期性执行
            .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
            .startNow()
            .build();

    try {
        // 调度器
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.scheduleJob(JobDetail, trigger);  // 装载任务和触发器
        scheduler.start();  // 开启定时任务
    } catch (SchedulerException e) {
        e.printStackTrace();
    }
}

注意点: Scheduler每次执行, 都会根据jobDetail创建一个新的job对象, 这样可以规避并发访问的问题(job是多例的, jobDetail也是多例的)

Quartz定时任务默认都是并发执行的, 不会等待上一个任务执行完毕, 只要时间间隔到了, 就会执行. 如果定时任务执行时间太久, 会长时间占用资源, 导致其他任务阻塞

2.Job

2.1为什么设计成JobDetail+Job, 而不直接使用Job

  • 分离任务定义和任务执行逻辑
    • JobDetail负责定义任务的元数据,比如任务的名称、分组、描述等信息
    • Job则负责实际执行任务的逻辑。
    • 这样的分离使得任务的定义和执行可以独立地管理和修改,增加了系统的灵活性和可维护性。
  • 规避并发访问的问题, 如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题, 通过使用JobDetail + Job 方式, 每次访问, 都会根据JobDetail创建一个新的Job实例

2.2 间隔执行时, 每次都会创建新的Job实例

public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("[job]=" + this.hashCode());
        System.out.println("[jobDetail]=" + context.getJobDetail().hashCode());
        System.out.println("[trigger]=" + context.getTrigger().hashCode());
    }
}

每次的job实例都不是同一个

在这里插入图片描述

为什么这么设计?

  • 状态隔离
    即使上一个任务执行中可能存在的状态残留, 也不会影响到下一次任务执行, 这样可以确保任务执行的可靠性和一致性
  • 线程安全
    通过每次创建新的job实例, 避免在多线程环境下出现资源共享问题.
  • 避免资源泄漏
    通过每次创建新的Job实例,可以确保任务执行完成后,相关的资源能够被正确释放,避免了潜在的资源泄漏问题。

虽然每次创建新的Job实例会增加一定的资源消耗,但这种设计能够保证任务执行的可靠性和一致性,是一种合理的权衡。同时,Quartz也提供了相关的扩展机制,允许开发人员自定义Job实例的创建和生命周期管理策略,以满足特定的需求。

2.3 定时任务默认都是并发执行的,不会等待上一次任务执行完毕

Quartz的调度器(Scheduler)是多线程的, 并且可以同时执行多个作业. 这种并发执行的特性使得Quartz能够高效地处理大量的定时任务

小实验: 在trigger中设置间隔时间为1秒, 同时设置Job的执行时间为3秒

public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("execute:" + new Date());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

每次任务执行, 并没有等上一次执行结束才开始, 这说明了每次执行定时任务都是并发的, 定时任务之间相互隔离

在这里插入图片描述

2.3.1 不允许并发执行

@DisallowConcurrentExecution注解的作用是确保同一个作业(job)类的多个实例不会同时执行.

当一个作业实例正在执行时, 下一个调度的任务将会等待前一个任务执行完成后再执行

  • 防止并发执行: 标记了@DisallowConcurrentExecution的作业类不会被并发执行, 如果一个作业实例正在执行, 另一个
  • 避免资源竞争: 适用于那些需要独占资源或者对共享资源有依赖的作业. 通过防止同一作业类的多个实例同时运行, 可以避免资源竞争和数据不一致的问题
  • 确保任务的顺序执行: 对于一些需要按顺序执行的任务,通过使用@DisallowConcurrentExecution注解可以确保它们按照预期的顺序执行,而不会出现并发执行导致的问题
@DisallowConcurrentExecution
public class MyJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("execute:" + new Date());
        System.out.println("job=" + this.hashCode());

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

在这里插入图片描述

2.4 在运行时, 通过JobDataMap向Job传递数据

JobDataMap可以帮助我们在作业(job)运行时传递数据. 它允许你在作业执行过程中向Job实例传递任意参数类型的数据

JobDataMap本质是一个Map. 我们将需要传递的数据包装成键值对, put到JobDataMap中即可

  • 步骤一: 创建JobDetail, put键值对, 然后在初始化JobDetail或者Trigger时作为参数传入
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("name", "zhangsan");
jobDataMap.put("age", 15);
jobDataMap.put("money", 128.2);

// 任务详情
JobDetail JobDetail = JobBuilder.newJob(MyJob.class)
        .withIdentity("job1", "group1")
        .usingJobData(jobDataMap)
        .build();
  • 步骤二: 在job中获取JobDataMap并访问数据
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    JobDataMap jobData = jobExecutionContext.getJobDetail().getJobDataMap();
    String name = jobData.getString("name");
    int age = jobData.getInt("age");
    double money = jobData.getDouble("money");
    System.out.println(name + age + money);

    // 从trigger中获取传递的参数
    // JobDataMap triggerData = jobExecutionContext.getTrigger().getJobDataMap();

    // 将JobDetail和Trigger
    // JobDataMap mergedJobDataMap = jobExecutionContext.getMergedJobDataMap();
}

PS: 可以使用getMergedJobDataMap将jobData和triggerData合并在一起, 但会出现相同key被覆盖的情况

2.4.1 持久化JobDetail中的JobDataMap

@PersistJobDataAfterExecution是Quartz提供的一个注解,用于标记作业类(Job class), 当一个作业类被标记为@PersistJobDataAfterExecution时, Quartz将在每次作业执行完成后,将作业实例的JobDataMap中的数据持久化到调度存储中. 这意味着作业实例的状态数据将会在执行完成后被保存下来,并在下一次执行时恢复。

这个注解对于需要跟踪作业状态或者持久化作业的状态数据非常有用。例如,如果一个作业在执行过程中累加了一个计数器的值,通过使用@PersistJobDataAfterExecution注解,你可以确保这个计数器的值在每次作业执行完成后都会被保存下来,而不会丢失。

案例: 在job中累加一个计数器的值
因为Job、JobDetail都是每次执行定时任务时新建的, 所以JobDataMap也不是同一个对象. 所以我们如果想让JobDataMap在一个定时任务中被共享(job、jobDetail), 需要添加@PersistJobDataAfterExecution注解

@DisallowConcurrentExecution
@PersistJobDataAfterExecution
public class MyJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
    	// 获取jobDataMap
        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
		
		// 从中获取参数
        int count = jobDataMap.getInt("count");
        System.out.println("count:" + count);

		// 执行作业逻辑, 累加计数器
		count++;
        
        // 将更新后的计数器值放回JobDataMap中
        jobDataMap.put("count", count);
    }
}

注意: 持久化只针对于JobDetail中的JobDataMap(对于Tigger中的JobDataMap无效)

3.触发器

3.1 优先级

Trigger的优先级主要用于决定当多个Trigger同时触发时调度器的执行顺序

当调度器中有多个Trigger准备就绪,可以被调度执行时,调度器会先选择具有最高优先级的Trigger来执行。如果有多个Trigger具有相同的最高优先级,则调度器会根据调度器的调度策略来确定执行顺序。

需要注意的是,Quartz中Trigger的优先级仅影响多个Trigger同时准备就绪时的执行顺序,并不会影响单个Trigger的执行。优先级较高的Trigger并不会强制中断正在执行的Trigger,而只是影响下一个被选中执行的Trigger。

因此,在Quartz中,通过设置Trigger的优先级,可以在一定程度上控制Trigger的执行顺序,使得具有更高优先级的Trigger更有可能首先被执行。

简单来说: 优先级影响多个同时触发的Trigger的执行顺序, 而不会影响它们的执行时间

Trigger trigger1 = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .withPriority(5)
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).repeatForever())
                .build();

// 定义Trigger2,优先级为10
Trigger trigger2 = TriggerBuilder.newTrigger()
        .withIdentity("trigger2", "group1")
        .withPriority(10)
        .startNow()
        .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).repeatForever())
        .build();

3.2 misfire(毕竟难得点, 一般用默认, 如果有特殊需求, 需要自己去研究)

misfire指触发器错过触发时间后, Quartz如何处理错过的触发事件

3.2.1 哪些情况会导致misfire

  • 当job达到触发时间时, 所有线程都在作业, 没有可用线程
  • 在job需要触发的时间点, scheduler停止了(可能是意外停止)
  • job使用了@DisallowConcurrentExecution注解, job不能并发执行, 当达到下一个job执行点的时候, 上一个任务还没有完成
  • job指定了过去了开始执行时, 例如当前时间为8点(am), 指定时间为7点(am)

3.2.2 有哪些策略

默认策略是MISFIRE_INSTRUCTION_SMART_POLICY智能策略。这个策略会根据触发器的类型和配置来自动选择最合适的处理方式。

  • 对于SimpleTrigger,如果错过了触发时间,Quartz会尽快触发任务执行,但不会丢失任何触发次数。
  • 对于CronTrigger,如果错过了触发时间,Quartz会将触发器安排到下一个合适的触发时间,并保留当前的重复计数。

Quartz misfire详解
全网最好的一篇讲解Quartz的MisFire机制

4.SpringBoot整合Quartz

Quartz存储任务信息有两种方式,使用内存或者使用数据库来存储.

如果使用内存存储调度数据, 如果应用程序意外终止或者重启, 内存中的调度数据会丢失, 导致调度器失去对作业(job)和触发器的追踪, 从而导致任务丢失或不正确执行

通过将Quartz与Mysql集成, 可以将调度数据存储到数据库中, 从而实现数据的持久化.

  • SpringBoot版本: 2.0.9.RELEASE
  • Mysql版本: 8.0.26(因为我本地mysql是8.0版本)

4.1 数据库表准备

一共是11张表, 用来持久化定时任务的信息
springboot整合quartz(集群环境)

4.2 Maven主要依赖

  • SpringBoot整合quartz的启动器
  • mysql连接驱动
  • druid连接池(可选)
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

<dependency>
	<groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
	<version>8.0.26</version>
</dependency>

<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.78</version>
</dependency>

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>

这里使用 druid 作为数据库连接池,Quartz 默认使用 c3p0

4.3 quartz.properties

默认情况下, Quartz会加载classpath下的quartz.properties 作为配置文件, 如果找不到,则会使用 quartz 框架自己 jar 包下 org/quartz 底下的 quartz.properties 文件

#主要分为scheduler、threadPool、jobStore、dataSource等部分


org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.instanceName=DefaultQuartzScheduler
#如果您希望Quartz Scheduler通过RMI作为服务器导出本身,则将“rmi.export”标志设置为true
#在同一个配置文件中为'org.quartz.scheduler.rmi.export'和'org.quartz.scheduler.rmi.proxy'指定一个'true'值是没有意义的,如果你这样做'export'选项将被忽略
org.quartz.scheduler.rmi.export=false
#如果要连接(使用)远程服务的调度程序,则将“org.quartz.scheduler.rmi.proxy”标志设置为true。您还必须指定RMI注册表进程的主机和端口 - 通常是“localhost”端口1099
org.quartz.scheduler.rmi.proxy=false
org.quartz.scheduler.wrapJobExecutionInUserTransaction=false


#实例化ThreadPool时,使用的线程类为SimpleThreadPool
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#threadCount和threadPriority将以setter的形式注入ThreadPool实例
#并发个数  如果你只有几个工作每天触发几次 那么1个线程就可以,如果你有成千上万的工作,每分钟都有很多工作 那么久需要50-100之间.
#只有1到100之间的数字是非常实用的
org.quartz.threadPool.threadCount=5
#优先级 默认值为5
org.quartz.threadPool.threadPriority=5
#可以是“true”或“false”,默认为false
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true


#在被认为“misfired”(失火)之前,调度程序将“tolerate(容忍)”一个Triggers(触发器)将其下一个启动时间通过的毫秒数。默认值(如果您在配置中未输入此属性)为60000(60秒)
org.quartz.jobStore.misfireThreshold=5000
# 默认存储在内存中,RAMJobStore快速轻便,但是当进程终止时,所有调度信息都会丢失
#org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore

#持久化方式,默认存储在内存中,此处使用数据库方式
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
#您需要为JobStore选择一个DriverDelegate才能使用。DriverDelegate负责执行特定数据库可能需要的任何JDBC工作
# StdJDBCDelegate是一个使用“vanilla”JDBC代码(和SQL语句)来执行其工作的委托,用于完全符合JDBC的驱动程序
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#可以将“org.quartz.jobStore.useProperties”配置参数设置为“true”(默认为false),以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,
#因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题
org.quartz.jobStore.useProperties=true
#表前缀
org.quartz.jobStore.tablePrefix=QRTZ_

关于配置详细解释: Quartz系统参数配置详解
官网: 详细配置

4.4 初始化Scheduler, 并交给IOC容器管理

我们可以选择在quartz.properties设置与数据库的连接信息, 此处我们使用了application.yml中配置的数据源

@Configuration
public class QuartzConfig implements SchedulerFactoryBeanCustomizer {

    @Autowired
    private DataSource dataSource;

    @Override
    public void customize(SchedulerFactoryBean schedulerFactoryBean) {
        schedulerFactoryBean.setStartupDelay(2);
        schedulerFactoryBean.setAutoStartup(true);
        schedulerFactoryBean.setOverwriteExistingJobs(true);
    }

    @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());
        schedulerFactoryBean.setDataSource(dataSource);
        return schedulerFactoryBean;
    }

    /*
     * 通过SchedulerFactoryBean获取Scheduler的实例
     */
    @Bean
    public Scheduler scheduler() throws IOException {
        return schedulerFactoryBean().getScheduler();
    }

}

4.5 配置数据源

server:
  port: 8080
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/quartz
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource  # 指定使用 Druid 数据源
      # Druid 连接池配置
    druid:
      initial-size: 5
      min-idle: 5
      max-active: 20
      max-wait: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      pool-prepared-statements: true
      max-open-prepared-statements: 20

4.6 job任务类

@DisallowConcurrentExecution
@PersistJobDataAfterExecution
public class JobOne extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("TimeEventJob正在执行..." + LocalDateTime.now());
        // 执行10秒
        try {
            Thread.sleep(9000);
            System.out.println("TimeEventJob执行完毕..." + LocalDateTime.now());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

4.6 实体类

public class JobInfo {
    /**
     * 任务名称
     */
    private String jobName;
    /**
     * 任务组
     */
    private String jobGroup;
    /**
     * 触发器名称
     */
    private String triggerName;
    /**
     * 触发器组
     */
    private String triggerGroup;
    /**
     * cron表达式
     */
    private String cron;
    /**
     * 类名
     */
    private String className;
    /**
     * 状态
     */
    private String status;
    /**
     * 下一次执行时间
     */
    private String nextTime;
    /**
     * 上一次执行时间
     */
    private String prevTime;
    /**
     * 配置信息(data)
     */
    private String config;
	......
}

4.7 sevice

public class JobServicer {

    @Resource
    private Scheduler scheduler;

    /**
     * 添加任务
     */
    @SuppressWarnings("unchecked")
    public void addJob(JobInfo jobInfo) throws SchedulerException, ClassNotFoundException {
        Objects.requireNonNull(jobInfo, "任务信息不能为空");

        // 生成job key
        JobKey jobKey = JobKey.jobKey(jobInfo.getJobName(), jobInfo.getJobGroup());
        // 当前任务不存在才进行添加
        if (!scheduler.checkExists(jobKey)) {
            Class<Job> jobClass = (Class<Job>)Class.forName(jobInfo.getClassName());
            // 任务明细
            JobDetail jobDetail = JobBuilder
                    .newJob(jobClass)
                    .withIdentity(jobKey)
                    .withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup())
                    .withDescription(jobInfo.getJobName())
                    .build();
            // 配置信息
            jobDetail.getJobDataMap().put("config", jobInfo.getConfig());
            // 定义触发器
            TriggerKey triggerKey = TriggerKey.triggerKey(jobInfo.getTriggerName(), jobInfo.getTriggerGroup());
            // 设置任务的错过机制
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(triggerKey)
                    .withSchedule(CronScheduleBuilder.cronSchedule(jobInfo.getCron()).withMisfireHandlingInstructionDoNothing())
                    .build();
            scheduler.scheduleJob(jobDetail, trigger);
        } else {
            throw new SchedulerException(jobInfo.getJobName() + "任务已存在,无需重复添加");
        }
    }

    /**
     * 任务暂停
     */
    public void pauseJob(String jobGroup, String jobName) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
        if (scheduler.checkExists(jobKey)) {
            scheduler.pauseJob(jobKey);
        }
    }

    /**
     * 继续任务
     */
    public void continueJob(String jobGroup, String jobName) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
        if (scheduler.checkExists(jobKey)) {
            scheduler.resumeJob(jobKey);
        }
    }

    /**
     * 删除任务
     */
    public boolean deleteJob(String jobGroup, String jobName) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
        if (scheduler.checkExists(jobKey)) {
            // 这里还需要先删除trigger相关
            //TriggerKey triggerKey = TriggerKey.triggerKey(jobInfo.getTriggerName(), jobInfo.getTriggerGroup());
            //scheduler.getTrigger()
            //scheduler.rescheduleJob()
            return scheduler.deleteJob(jobKey);
        }
        return false;
    }

    /**
     * 获取任务信息
     */
    public JobInfo getJobInfo(String jobGroup, String jobName) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
        if (!scheduler.checkExists(jobKey)) {
            return null;
        }
        List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
        if (Objects.isNull(triggers)) {
            throw new SchedulerException("未获取到触发器信息");
        }
        TriggerKey triggerKey = triggers.get(0).getKey();
        Trigger.TriggerState triggerState = scheduler.getTriggerState(triggerKey);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);

        JobInfo jobInfo = new JobInfo();
        jobInfo.setJobName(jobGroup);
        jobInfo.setJobGroup(jobName);
        jobInfo.setTriggerName(triggerKey.getName());
        jobInfo.setTriggerGroup(triggerKey.getGroup());
        jobInfo.setClassName(jobDetail.getJobClass().getName());
        jobInfo.setStatus(triggerState.toString());

        if (Objects.nonNull(jobDetail.getJobDataMap())) {
            jobInfo.setConfig(JSONObject.toJSONString(jobDetail.getJobDataMap()));
        }

        CronTrigger theTrigger = (CronTrigger) triggers.get(0);
        jobInfo.setCron(theTrigger.getCronExpression());
        return jobInfo;
    }

}

4.8 controller

package com.itheima.controller;

import com.itheima.handler.JobHandler;
import com.itheima.pojo.JobInfo;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@RestController
@RequestMapping("/job")
public class QuartzController {

    @Resource
    private JobServicer jobHandler;
    @Resource
    private Scheduler scheduler;

    /**
     * 查询所有的任务
     */
    @RequestMapping("/all")
    public List<JobInfo> list() throws SchedulerException {
        List<JobInfo> jobInfos = new ArrayList<>();
        List<String> triggerGroupNames = scheduler.getTriggerGroupNames();
        for (String triggerGroupName : triggerGroupNames) {
            Set<TriggerKey> triggerKeySet = scheduler
                    .getTriggerKeys(GroupMatcher.triggerGroupEquals(triggerGroupName));
            for (TriggerKey triggerKey : triggerKeySet) {
                Trigger trigger = scheduler.getTrigger(triggerKey);
                JobKey jobKey = trigger.getJobKey();
                JobInfo jobInfo = jobHandler.getJobInfo(jobKey.getGroup(), jobKey.getName());
                jobInfos.add(jobInfo);
            }
        }
        return jobInfos;
    }

    /**
     * 添加任务
     */
    @PostMapping("/add")
    public JobInfo addJob(@RequestBody JobInfo jobInfo) throws SchedulerException, ClassNotFoundException {
        jobHandler.addJob(jobInfo);
        return jobInfo;
    }

    /**
     * 暂停任务
     */
    @RequestMapping("/pause")
    public void pauseJob(@RequestParam("jobGroup") String jobGroup, @RequestParam("jobName") String jobName)
            throws SchedulerException {
        jobHandler.pauseJob(jobGroup, jobName);
    }

    /**
     * 继续任务
     */
    @RequestMapping("/continue")
    public void continueJob(@RequestParam("jobGroup") String jobGroup, @RequestParam("jobName") String jobName)
            throws SchedulerException {
        jobHandler.continueJob(jobGroup, jobName);
    }

    /**
     * 删除任务
     */
    @RequestMapping("/delete")
    public boolean deleteJob(@RequestParam("jobGroup") String jobGroup, @RequestParam("jobName") String jobName)
            throws SchedulerException {
        return jobHandler.deleteJob(jobGroup, jobName);
    }

}
### Quartz 调度框架使用指南 #### 一、初步认识Quartz 为了快速掌握Quartz议先从官方文档的Introduction部分入手,这里会介绍Quartz的核心概念及其组成要素[^1]。 #### 二、实践操作 接着应该浏览Tutorials & Examples章节,在实际案例指导下完成基本设置与编程工作。这有助于加深对理论的理解并积累实践经验。 对于具体的实现细节,下面给出一段简单的代码示例来展示如何创一个基于Quartz的任务调度程序: ```java import org.quartz.*; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; public class SimpleExample { public void run() throws SchedulerException { // 创调度器实例 StdSchedulerFactory factory = new StdSchedulerFactory(); Scheduler scheduler = factory.getScheduler(); // 定义job详情, 并关联到MyJob类 JobDetail job = newJob(MyJob.class).withIdentity("myJob", "group1").build(); // 构触发器trigger使其每十秒执行一次 Trigger trigger = newTrigger().withIdentity("myTrigger", "group1") .startNow() .withSchedule(simpleSchedule().withIntervalInSeconds(10).repeatForever()) .build(); // 把job和trigger注册至scheduler内 scheduler.scheduleJob(job, trigger); // 启动调度器 scheduler.start(); try { Thread.sleep(60 * 1000); } catch (Exception e) {} // 关闭调度器 scheduler.shutdown(true); } } ``` 这段代码展示了怎样定义一个新的任务(`MyJob`)并通过`SimpleTrigger`设定其每隔十秒钟重复执行一次的方式加入到了调度队列当中[^4]。 #### 三、深入探究 当遇到特定难题时,则可参照Configuration Reference同API Documentation获取更详尽的帮助材料;同时也可以探索更多高级特性比如持久化存储机制(JobStore),它负责保存运行期间产生的各类元数据如Trigger、Scheduler、JobDetail等信息[^5]。 另外值得注意的是,通过`JobExecutionContext`对象可以在执行过程中获得当前上下文环境以及有关该次调用的具体参数[^3]。 #### 四、持续学习 最后推荐关注相关资源和支持渠道以便于长期跟进项目发展动态和技术交流活动。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值