Quartz

Quartz

  • 1.核心概念
    • 1.1 核心概念图
    • 1.2 demo
  • 2.Job
    • 2.1为什么设计成JobDetail+Job, 而不直接使用Job
    • 2.2 间隔执行时, 每次都会创建新的Job实例
    • 2.3 定时任务默认都是并发执行的,不会等待上一次任务执行完毕
      • 2.3.1 不允许并发执行
    • 2.4 在运行时, 通过JobDataMap向Job传递数据
      • 2.4.1 持久化JobDetail中的JobDataMap
  • 3.触发器
    • 3.1 优先级
    • 3.2 misfire(毕竟难得点, 一般用默认, 如果有特殊需求, 需要自己去研究)
      • 3.2.1 哪些情况会导致misfire
      • 3.2.2 有哪些策略
  • 4.SpringBoot整合Quartz
    • 4.1 数据库表准备
    • 4.2 Maven主要依赖
    • 4.3 quartz.properties
    • 4.4 初始化Scheduler, 并交给IOC容器管理
    • 4.5 job任务类
    • 4.6 实体类
    • 4.7 sevice
    • 4.8 controller

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 数据库表准备

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_
  • scheduler属性
    • org.quartz.scheduler.instanceName=DefaultQuartzScheduler: 设置Quartz调度器的实例名称, 在项目中通常只需要一个Quartz调度器实例, 一个调度器实例可以满足大多数情况下的需求
    • org.quartz.scheduler.instanceId=AUTO: 设置Quartz调度器的实例标识符, 当设置为Auto时, 会自动为调度器分配一个唯一的实例标识符, 以确保在多个调度器实例运行时它们彼此不冲突
  • jobDataMap属性
    • org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX: 持久化方式,默认存储在内存中,此处使用数据库方式, 通常需要配合其他相关的配置属性,比如数据库连接信息、事务管理器等,以便Quartz能够正确地初始化并使用作业存储。
    • org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate: 表示使用标准的JDBC驱动委托类。这个委托类适用于大多数常见的关系型数据库,比如MySQL、Oracle等, 配置这个属性时,需要确保选择的驱动委托类与所使用的数据库类型匹配,以便Quartz能够正确地生成和执行相应的SQL语句。
    • org.quartz.jobStore.useProperties=true: 设置jobDataMap存储类型为字符串, 默认为false. 设置为true可以提高作业数据的可读性和可操作性,但需要确保存储的对象是可序列化的。
    • org.quartz.jobStore.tablePrefix=QRTZ_: 表的前缀
    • org.quartz.jobStore.isClustered=false: 是否加入集群, 默认为false
    • org.quartz.jobStore.clusterCheckinInterval=5000: 集群环境下, 定期检查各个节点的时间间隔(毫秒)
    • org.quartz.jobStore.txIsolationLevelReadCommitted=true: 设置存储的事务隔离级别
  • ThreadPool属性
    • org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool: 指定使用的线程池实现类, SimpleThreadPool表示使用简单的线程池, 这个简单线程池实现会根据配置的线程数量创建一组固定大小的线程池,在需要时分配线程给触发器执行任务。
    • org.quartz.threadPool.threadCount=5: 调度器可以并发执行的任务数量,也就是同时执行的触发器任务数量。通过合理设置线程数量,可以控制应用的并发性能和资源利用率。
    • org.quartz.threadPool.threadPriority=5: 属性用于指定Quartz调度器线程池中线程的优先级。

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

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

@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 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);
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/490486.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python自动化测试环境搭建

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号&#xff1a;互联网杂货铺&#xff0c;回复1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 请事先自行安装好​​Pycharm​​​软件哦&#xff0c;我…

深度学习模型部署(十二)CUDA编程-绪

CUDA 运行时 API 与 CUDA 驱动 API 速度没有差别&#xff0c;实际中使用运行时 API 较多&#xff0c;运行时 API 是在驱动 API 上的一层封装。​ CUDA 是什么&#xff1f;​ CUDA(Compute Unified Device Architecture) 是 nvidia 推出的一个通用并行技术架构&#xff0c;用它…

基于冠豪猪优化器(CPO)的无人机路径规划

该优化算法是2024年新发表的一篇SCI一区top论文具有良好的实际应用和改进意义。一键运行main函数代码自动保存高质量图片 1、冠豪猪优化器 摘要&#xff1a;受冠豪猪(crest Porcupine, CP)的各种防御行为启发&#xff0c;提出了一种新的基于自然启发的元启发式算法——冠豪猪…

视觉轮速滤波融合1讲:理论推导

视觉轮速滤波融合理论推导 文章目录 视觉轮速滤波融合理论推导1 坐标系2 轮速计2.1 运动学模型2.2 外参 3 状态和协方差矩阵3.1 状态3.2 协方差矩阵 4 Wheel Propagation4.1 连续运动学4.2 离散积分4.2.1 状态均值递推4.2.2 协方差递推 5 Visual update5.1 视觉残差与雅可比5.2…

蓝桥杯2023年第十四届省赛真题-买瓜|DFS+剪枝

题目链接&#xff1a; 0买瓜 - 蓝桥云课 (lanqiao.cn) 蓝桥杯2023年第十四届省赛真题-买瓜 - C语言网 (dotcpp.com) &#xff08;蓝桥官网的数据要求会高一些&#xff09; 说明&#xff1a; 这道题可以分析出&#xff1a;对一个瓜有三种选择&#xff1a; 不拿&#xff0c…

Vue3基础笔记(2)事件

一.事件处理 1.内联事件处理器 <button v-on:click"count">count1</button> 直接将事件以表达式的方式书写~ 每次单击可以完成自增1的操作~ 2.方法事件处理器 <button click"addcount(啦啦啦~)">count2</button> 如上&…

每日必学Linux命令:mv命令

mv命令是move的缩写&#xff0c;可以用来移动文件或者将文件改名&#xff08;move (rename) files&#xff09;&#xff0c;是Linux系统下常用的命令&#xff0c;经常用来备份文件或者目录。 一&#xff0e;命令格式&#xff1a; mv [选项] 源文件或目录 目标文件或目录二&am…

Open WebUI大模型对话平台-适配Ollama

什么是Open WebUI Open WebUI是一种可扩展、功能丰富、用户友好的大模型对话平台&#xff0c;旨在完全离线运行。它支持各种LLM运行程序&#xff0c;包括与Ollama和Openai兼容的API。 功能 直观的界面:我们的聊天界面灵感来自ChatGPT&#xff0c;确保了用户友好的体验。响应…

轻松掌握C语言中的sqrt函数,快速计算平方根的魔法秘诀

C语言文章更新目录 C语言学习资源汇总&#xff0c;史上最全面总结&#xff0c;没有之一 C/C学习资源&#xff08;百度云盘链接&#xff09; 计算机二级资料&#xff08;过级专用&#xff09; C语言学习路线&#xff08;从入门到实战&#xff09; 编写C语言程序的7个步骤和编程…

第1章 实时3D渲染流水线

前言 本书所剖析的Unity 3D内置着色器代码版本是2017.2.0f3&#xff0c;读者可以从Unity 3D官网下载这些着色器代码。这些代码以名为builtin_shaders-2017.2.0f3.zip的压缩包的形式提供&#xff0c;解压缩后&#xff0c;内有4个目录和1个license.txt文件。 目录CGIncludes存放了…

苍穹外卖项目-01(开发流程,介绍,开发环境搭建,nginx反向代理,Swagger)

目录 一、软件开发整体介绍 1. 软件开发流程 1 第1阶段: 需求分析 2 第2阶段: 设计 3 第3阶段: 编码 4 第4阶段: 测试 5 第5阶段: 上线运维 2. 角色分工 3. 软件环境 1 开发环境(development) 2 测试环境(testing) 3 生产环境(production) 二、苍穹外卖项目介绍 …

Docker搭建LNMP环境实战(05):CentOS环境安装Docker-CE

前面几篇文章讲了那么多似乎和Docker无关的实战操作&#xff0c;本篇总算开始说到Docker了。 1、关于Docker 1.1、什么是Docker Docker概念就是大概了解一下就可以&#xff0c;还是引用一下百度百科吧&#xff1a; Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以…

SE注意力模块学习笔记《Squeeze-and-Excitation Networks》

Squeeze-and-Excitation Networks 摘要引言什么是全局平均池化&#xff1f; 相关工作Deep architectures Squeeze-and-Excitation Blocks3.1. Squeeze: Global Information Embedding3.2. Excitation: Adaptive Recalibration3.3. Exemplars: SE-Inception and SE-ResNet 5. Im…

百科词条编辑必备指南,让你轻松上手创建

1.注册账号&#xff1a;首先&#xff0c;你需要注册一个百科平台的账号。例如&#xff0c;对于百度百科&#xff0c;你需要有一个百度账号。 搜索词条&#xff1a;在百科全书平台上搜索您想要编辑的词条。如果词条已经存在&#xff0c;可以直接编辑&#xff1b;如果词条不存在&…

(已解决)vue3使用富文本出现样式乱码

我在copy代码到项目里面时候发现我的富文本乱码了 找了一圈不知道是哪里vue3不适配还是怎么&#xff0c;后来发现main.js还需要引入 import VueQuillEditor from vue-quill-editor // require styles 引入样式 import quill/dist/quill.core.css import quill/dist/quill.snow…

计算机组成原理(超详解!!) 第三节 运算器(浮点加减乘)

1.浮点加法、减法运算 操作过程 1.操作数检查 如果能够判断有一个操作数为0&#xff0c;则没必要再进行后续一系列操作&#xff0c;以节省运算时间。 2.完成浮点加减运算的操作 (1) 比较阶码大小并完成对阶 使二数阶码相同&#xff08;即小数点位置对齐&#xff09;…

力扣Lc21--- 389. 找不同(java版)-2024年3月26日

1.题目描述 2.知识点 &#xff08;1&#xff09;在这段代码中&#xff1a; // 统计字符串s中每个字符的出现次数for (int i 0; i < s.length(); i) {count[s.charAt(i) - a];}对于字符串s “abcd”&#xff1a; 当 i 0&#xff0c;s.charAt(i) ‘a’&#xff0c;ASCII…

牛客小白月赛89(A,B,C,D,E,F)

比赛链接 官方视频讲解&#xff08;个人觉得讲的还是不错的&#xff09; 这把BC偏难&#xff0c;差点就不想做了&#xff0c;对小白杀伤力比较大。后面的题还算正常点。 A 伊甸之花 思路&#xff1a; 发现如果这个序列中最大值不为 k k k&#xff0c;我们可以把序列所有数…

2024年道路运输企业主要负责人证模拟考试题库及道路运输企业主要负责人理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年道路运输企业主要负责人证模拟考试题库及道路运输企业主要负责人理论考试试题是由安全生产模拟考试一点通提供&#xff0c;道路运输企业主要负责人证模拟考试题库是根据道路运输企业主要负责人最新版教材&#…

数据结构进阶篇 之 【二叉树】详细概念讲解(带你认识何为二叉树及其性质)

有朋自远方来&#xff0c;必先苦其心志&#xff0c;劳其筋骨&#xff0c;饿其体肤&#xff0c;空乏其身&#xff0c;鞭数十&#xff0c;驱之别院 一、二叉树 1、二叉树的概念 1.1 二叉树中组分构成名词概念 1.2 二叉树的结构概念 1.3 特殊的二叉树 2、二叉树的存储结构 …