当前位置: 首页 > news >正文

【SpringBoot】调度和执行定时任务--Quartz(超详细)

Quartz 是一个功能强大的任务调度框架,广泛用于在 Java 应用程序中定时执行任务,同时它支持 Cron 表达式、持久化任务、集群等特性。以下是 Quartz 的详细使用教程,包括安装、基本概念、简单示例和高级功能。

1. 安装 Quartz

首先,在你的项目中添加 Quartz 依赖。对于 Maven 项目,可以在 pom.xml 中添加以下依赖:

<dependency><groupId>org.quartz-scheduler</groupId><artifactId>quartz</artifactId><version>2.3.2</version>
</dependency>

对于 Gradle 项目,可以在 build.gradle 中添加以下依赖:

implementation 'org.quartz-scheduler:quartz:2.3.2'

2. 基本概念

Quartz 的核心概念包括:

  • Scheduler:调度器,是 Quartz 的核心,负责管理和调度任务。
  • Job:任务,是实际执行的工作单元。需要实现 Job 接口。
  • JobDetail:定义任务的详细信息,包括任务的名称、组、以及任务的类。
  • Trigger:触发器,定义任务何时执行。常用的触发器包括 SimpleTrigger 和 CronTrigger。
  • JobStore:任务存储,定义任务的存储方式。常见的有 RAMJobStore(内存存储)和 JDBCJobStore(数据库存储)。

3. 简单示例

以下是一个简单的 Quartz 示例,展示如何创建和调度一个任务。

1. 定义 Job 类

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;public class HelloJob implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {System.out.println("Hello, Quartz! Current time: " + System.currentTimeMillis());}
}

2. 配置 Scheduler 和 Job

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;public class QuartzExample {public static void main(String[] args) throws SchedulerException {// 创建 Scheduler 实例Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// 定义一个 JobDetail 实例JobDetail job = JobBuilder.newJob(HelloJob.class).withIdentity("helloJob", "group1").build();// 创建一个触发器,每隔5秒执行一次Trigger trigger = TriggerBuilder.newTrigger().withIdentity("helloTrigger", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever()).build();// 调度任务scheduler.start();scheduler.scheduleJob(job, trigger);}
}

4. 高级功能

1. 使用 CronTrigger

CronTrigger 允许使用 Cron 表达式来定义复杂的调度规则。

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;public class CronTriggerExample {public static void main(String[] args) throws SchedulerException {Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();JobDetail job = JobBuilder.newJob(HelloJob.class).withIdentity("cronJob", "group1").build();// 使用 Cron 表达式创建触发器Trigger trigger = TriggerBuilder.newTrigger().withIdentity("cronTrigger", "group1").withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?")).build();scheduler.start();scheduler.scheduleJob(job, trigger);}
}

2. Cron 表达式

Cron 表达式用于定义任务调度的时间规则。它由6或7个字段组成,字段之间用空格分隔。以下是每个字段的含义:

┌───────────── 秒 (0 - 59)
│ ┌───────────── 分 (0 - 59)
│ │ ┌───────────── 小时 (0 - 23)
│ │ │ ┌───────────── 日 (1 - 31)
│ │ │ │ ┌───────────── 月 (1 - 12)
│ │ │ │ │ ┌───────────── 星期几 (0 - 7) (0 和 7 都是星期日)
│ │ │ │ │ │
│ │ │ │ │ │
* * * * * *

2.1 特殊字符

  • *: 表示任意值。
  • ?: 仅在日和星期字段中使用,表示不指定值。
  • -: 表示范围,例如 10-12 表示从10到12。
  • ,: 表示列表值,例如 1,2,3 表示1、2、3。
  • /: 表示增量,例如 0/15 表示从0开始每15分钟。
  • L: 表示最后,例如 L 在日字段表示月的最后一天。
  • W: 表示最近的工作日,例如 15W 表示最接近15号的工作日。
  • #: 表示第几个星期几,例如 2#1 表示第一个星期一。

2.2 示例

  • 0 0 12 * * ?: 每天中午12点执行。
  • 0 15 10 ? * *: 每天上午10:15执行。
  • 0 15 10 * * ?: 每天上午10:15执行。
  • 0 15 10 * * ? 2024: 2024年每天上午10:15执行。
  • 0 * 14 * * ?: 每天下午2点到2:59每分钟执行一次。
  • 0 0/5 14 * * ?: 每天下午2点到2:55每5分钟执行一次。
  • 0 0/5 14,18 * * ?: 每天下午2点到2:55每5分钟执行一次,以及每天下午6点到6:55每5分钟执行一次。
  • 0 0-5 14 * * ?: 每天下午2点到2:05每分钟执行一次。
  • 0 10,44 14 ? 3 WED: 每年三月的每个星期三下午2:10和2:44执行。

3. 使用 JobListener

JobListener 可以在任务执行的不同阶段进行拦截和处理。

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;public class MyJobListener implements JobListener {@Overridepublic String getName() {return "MyJobListener";}@Overridepublic void jobToBeExecuted(JobExecutionContext context) {//在任务即将被执行时调用,可以在任务执行前进行一些准备工作或记录日志。System.out.println("Job is about to be executed: " + context.getJobDetail().getKey());}@Overridepublic void jobExecutionVetoed(JobExecutionContext context) {//在任务执行被否决时调用,当某些条件满足时,可以阻止任务的执行,并在此方法中执行相应的处理逻辑。System.out.println("Job execution was vetoed: " + context.getJobDetail().getKey());}@Overridepublic void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {//在任务执行完成后调用,可以在任务执行后进行一些清理工作或记录日志。如果任务执行过程中抛出异常,jobException 将包含该异常信息。System.out.println("Job was executed: " + context.getJobDetail().getKey());if (jobException != null) {System.out.println("Job encountered an exception: " + jobException.getMessage());}}
}

在 Scheduler 中注册 JobListener:

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;public class JobListenerExample {public static void main(String[] args) throws SchedulerException {Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();//创建任务JobDetail job = JobBuilder.newJob(HelloJob.class).withIdentity("listenerJob", "group1").build();创建触发器Trigger trigger = TriggerBuilder.newTrigger().withIdentity("listenerTrigger", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever()).build();// 创建并注册 JobListenerMyJobListener listener = new MyJobListener();scheduler.getListenerManager().addJobListener(listener);scheduler.start();scheduler.scheduleJob(job, trigger);}
}

5. 持久化支持

Quartz 支持将任务数据持久化到数据库中,以便在系统重启后恢复任务状态。可以使用 JDBCJobStore 来实现这一点。

quartz.properties 文件中配置 JDBCJobStore:

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.dataSource = myDS
org.quartz.jobStore.tablePrefix = QRTZ_org.quartz.dataSource.myDS.driver = com.mysql.cj.jdbc.Driver
org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/quartz
org.quartz.dataSource.myDS.user = root
org.quartz.dataSource.myDS.password = password
org.quartz.dataSource.myDS.maxConnections = 5

6. 并发控制

在 Quartz 中,默认情况下,任务是并发执行的。如果需要确保同一个任务实例不被并发执行,可以实现 DisallowConcurrentExecution 接口。为了清楚地展示 DisallowConcurrentExecution 注解的作用,我们可以创建两个示例:一个使用 DisallowConcurrentExecution 注解,另一个不使用该注解。通过这两个示例,我们可以观察到任务在并发执行方面的差异。

示例 1:不使用 DisallowConcurrentExecution

在这个示例中,我们创建一个简单的任务类,它会模拟一个长时间运行的任务,并且不使用 DisallowConcurrentExecution 注解。

1. 定义 Job 类

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;public class ConcurrentJob implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {System.out.println("ConcurrentJob is executing at: " + System.currentTimeMillis());try {// 模拟长时间运行的任务Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("ConcurrentJob finished at: " + System.currentTimeMillis());}
}

2. 配置 Scheduler 和 Job

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;public class ConcurrentJobExample {public static void main(String[] args) throws SchedulerException {Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();JobDetail job = JobBuilder.newJob(ConcurrentJob.class).withIdentity("concurrentJob", "group1").build();// 创建一个触发器,每隔2秒执行一次Trigger trigger = TriggerBuilder.newTrigger().withIdentity("concurrentTrigger", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()).build();scheduler.start();scheduler.scheduleJob(job, trigger);}
}

示例 2:使用 DisallowConcurrentExecution

在这个示例中,我们创建一个类似的任务类,但这次使用 DisallowConcurrentExecution 注解,确保任务不会并发执行。

1. 定义 Job 类

import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;@DisallowConcurrentExecution
public class NonConcurrentJob implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {System.out.println("NonConcurrentJob is executing at: " + System.currentTimeMillis());try {// 模拟长时间运行的任务Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("NonConcurrentJob finished at: " + System.currentTimeMillis());}
}

2. 配置 Scheduler 和 Job

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;public class NonConcurrentJobExample {public static void main(String[] args) throws SchedulerException {Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();JobDetail job = JobBuilder.newJob(NonConcurrentJob.class).withIdentity("nonConcurrentJob", "group1").build();// 创建一个触发器,每隔2秒执行一次Trigger trigger = TriggerBuilder.newTrigger().withIdentity("nonConcurrentTrigger", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()).build();scheduler.start();scheduler.scheduleJob(job, trigger);}
}

运行示例

当你运行这两个示例时,你会看到以下行为:

不使用 DisallowConcurrentExecution 的输出

ConcurrentJob is executing at: 1633017600000
ConcurrentJob is executing at: 1633017602000
ConcurrentJob finished at: 1633017605000
ConcurrentJob is executing at: 1633017604000
ConcurrentJob finished at: 1633017607000
ConcurrentJob finished at: 1633017609000

可以看到,任务是并发执行的,多个任务实例同时运行。

使用 DisallowConcurrentExecution 的输出

NonConcurrentJob is executing at: 1633017600000
NonConcurrentJob finished at: 1633017605000
NonConcurrentJob is executing at: 1633017605000
NonConcurrentJob finished at: 1633017610000
NonConcurrentJob is executing at: 1633017610000
NonConcurrentJob finished at: 1633017615000

可以看到,任务是顺序执行的,新的任务实例只有在前一个实例完成后才会开始执行。

import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;@DisallowConcurrentExecution
public class NonConcurrentJob implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {System.out.println("Non-concurrent job is executing at: " + System.currentTimeMillis());}
}

通过这两个示例,我们可以清楚地看到 DisallowConcurrentExecution 注解的作用:它确保同一个任务实例不会并发执行,而是顺序执行。这在某些需要严格控制任务执行顺序的场景中非常有用。

7. 任务依赖管理

Quartz 本身并不直接支持任务依赖管理(即任务之间的依赖关系),但可以通过编程方式实现这一功能。通常的方法是使用多个 Job 和 Trigger,并在 Job 执行完成后手动调度下一个依赖的 Job。

1. 异步任务依赖管理

Quartz 本身并不直接支持任务依赖管理(即任务之间的依赖关系),但可以通过编程方式实现这一功能。通常的方法是使用多个 Job 和 Trigger,并在 Job 执行完成后手动调度下一个依赖的 Job。

以下是一个示例,展示如何在 Quartz 中实现任务依赖管理:

示例场景

假设有三个任务:

  1. 任务 A:完成后触发任务 B。
  2. 任务 B:完成后触发任务 C。
  3. 任务 C:最后执行。

实现步骤

  1. 定义三个 Job 类。
  2. 配置 Scheduler 和 Job。
  3. 在每个 Job 中执行完成后手动调度下一个依赖的 Job。

代码示例

1. 定义 Job 类
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;public class JobA implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {System.out.println("Job A executed at: " + System.currentTimeMillis());// 获取 Scheduler 实例Scheduler scheduler = context.getScheduler();try {// 创建 JobB 的 JobDetailJobDetail jobB = JobBuilder.newJob(JobB.class).withIdentity("jobB", "group1").build();// 创建立即触发的 TriggerTrigger triggerB = TriggerBuilder.newTrigger().withIdentity("triggerB", "group1").startNow().build();// 调度 JobBscheduler.scheduleJob(jobB, triggerB);} catch (SchedulerException e) {throw new JobExecutionException(e);}}
}public class JobB implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {System.out.println("Job B executed at: " + System.currentTimeMillis());// 获取 Scheduler 实例Scheduler scheduler = context.getScheduler();try {// 创建 JobC 的 JobDetailJobDetail jobC = JobBuilder.newJob(JobC.class).withIdentity("jobC", "group1").build();// 创建立即触发的 TriggerTrigger triggerC = TriggerBuilder.newTrigger().withIdentity("triggerC", "group1").startNow().build();// 调度 JobCscheduler.scheduleJob(jobC, triggerC);} catch (SchedulerException e) {throw new JobExecutionException(e);}}
}public class JobC implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {System.out.println("Job C executed at: " + System.currentTimeMillis());}
}
2. 配置 Scheduler 和 Job
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;public class QuartzDependencyExample {public static void main(String[] args) throws SchedulerException {// 创建 Scheduler 实例Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// 定义 JobAJobDetail jobA = JobBuilder.newJob(JobA.class).withIdentity("jobA", "group1").build();// 创建立即触发的 TriggerTrigger triggerA = TriggerBuilder.newTrigger().withIdentity("triggerA", "group1").startNow().build();// 调度 JobAscheduler.start();scheduler.scheduleJob(jobA, triggerA);}
}

解释

  1. JobA:在执行完成后,手动调度 JobB
  2. JobB:在执行完成后,手动调度 JobC
  3. JobC:最后执行,没有后续任务。

通过这种方式,可以实现任务之间的依赖关系。在每个任务执行完成后,手动调度下一个依赖的任务。这样,即使 Quartz 本身不直接支持任务依赖管理,也可以通过编程方式实现这一功能。

注意事项

  1. 异常处理:确保在调度下一个任务时正确处理异常,防止由于调度失败导致任务链中断。
  2. 并发控制:如果有多个任务链,需要确保并发控制,防止多个任务同时调度同一个任务。
  3. 持久化支持:如果任务链较长或需要持久化,确保使用持久化的 Scheduler 配置,以便在系统重启后恢复任务状态。

2. 同步任务依赖管理

在上面的示例中,任务之间的调度是异步的。每个 Job 在执行完成后,会立即调度下一个 Job,但不会等待下一个 Job 的执行完成。这种方式适用于大多数场景,但如果需要同步执行(即一个任务必须等待前一个任务执行完成后再执行),则需要一些额外的机制来确保任务的同步执行。

为了实现同步的任务依赖管理,可以使用以下方法:

  1. 使用 JobListener:监听每个任务的执行完成事件,并在事件触发时调度下一个任务。
  2. 使用共享的状态或信号量:在任务之间共享状态或使用信号量来确保任务按顺序执行。

以下是一个使用 JobListener 实现同步任务依赖管理的示例:

实现步骤

  1. 定义三个 Job 类。
  2. 实现 JobListener接口。
  3. 配置 Scheduler 和 Job。
  4. 当触发jobWasExecuted后调度下一个依赖的 Job。

示例代码

1. 定义 Job 类
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;public class JobA implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {System.out.println("Job A executed at: " + System.currentTimeMillis());}
}public class JobB implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {System.out.println("Job B executed at: " + System.currentTimeMillis());}
}public class JobC implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {System.out.println("Job C executed at: " + System.currentTimeMillis());}
}
2. 定义 JobListener
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
import org.quartz.SchedulerException;public class DependencyJobListener implements JobListener {private Scheduler scheduler;public DependencyJobListener(Scheduler scheduler) {this.scheduler = scheduler;}@Overridepublic String getName() {return "DependencyJobListener";}@Overridepublic void jobToBeExecuted(JobExecutionContext context) {// No action needed before job execution}@Overridepublic void jobExecutionVetoed(JobExecutionContext context) {// No action needed if job execution is vetoed}@Overridepublic void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {String jobName = context.getJobDetail().getKey().getName();try {if ("jobA".equals(jobName)) {// 调度 JobBJobDetail jobB = JobBuilder.newJob(JobB.class).withIdentity("jobB", "group1").build();Trigger triggerB = TriggerBuilder.newTrigger().withIdentity("triggerB", "group1").startNow().build();scheduler.scheduleJob(jobB, triggerB);} else if ("jobB".equals(jobName)) {// 调度 JobCJobDetail jobC = JobBuilder.newJob(JobC.class).withIdentity("jobC", "group1").build();Trigger triggerC = TriggerBuilder.newTrigger().withIdentity("triggerC", "group1").startNow().build();scheduler.scheduleJob(jobC, triggerC);}} catch (SchedulerException e) {e.printStackTrace();}}
}
3. 配置 Scheduler 和 Job
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;public class QuartzDependencyExample {public static void main(String[] args) throws SchedulerException {// 创建 Scheduler 实例Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// 定义 JobAJobDetail jobA = JobBuilder.newJob(JobA.class).withIdentity("jobA", "group1").build();// 创建立即触发的 TriggerTrigger triggerA = TriggerBuilder.newTrigger().withIdentity("triggerA", "group1").startNow().build();// 添加 JobListenerscheduler.getListenerManager().addJobListener(new DependencyJobListener(scheduler));// 调度 JobAscheduler.start();scheduler.scheduleJob(jobA, triggerA);}
}

解释

  1. JobA、JobB、JobC:分别定义三个独立的 Job 类。
  2. DependencyJobListener:实现 JobListener 接口,在每个 Job 执行完成后,根据当前执行的 Job 调度下一个 Job。
  3. Scheduler 配置:在 Scheduler 中注册 DependencyJobListener,并调度初始的 JobA。

注意事项

  1. 异常处理:确保在调度下一个任务时正确处理异常,防止由于调度失败导致任务链中断。
  2. 并发控制:如果有多个任务链,需要确保并发控制,防止多个任务同时调度同一个任务。
  3. 持久化支持:如果任务链较长或需要持久化,确保使用持久化的 Scheduler 配置,以便在系统重启后恢复任务状态。

3. 任务拦截

在 Quartz 中,可以通过实现 JobListener 来拦截任务的执行。JobListener 提供了几个方法,可以在任务执行前、执行后以及任务被否决时进行拦截和处理。

下面是一个详细的示例代码,展示如何使用 JobListener 来拦截任务的执行,并附有详细注释。

示例代码

1. 定义 Job 类
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;// 定义一个简单的 Job 类
public class SampleJob implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {System.out.println("SampleJob is executing at: " + System.currentTimeMillis());}
}
2. 实现 JobListener
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;// 实现 JobListener 接口
public class MyJobListener implements JobListener {@Overridepublic String getName() {// 返回监听器的名称return "MyJobListener";}@Overridepublic void jobToBeExecuted(JobExecutionContext context) {// 在任务即将执行时调用System.out.println("Job is about to be executed: " + context.getJobDetail().getKey());}@Overridepublic void jobExecutionVetoed(JobExecutionContext context) {// 在任务被否决时调用System.out.println("Job execution was vetoed: " + context.getJobDetail().getKey());}@Overridepublic void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {// 在任务执行完成后调用System.out.println("Job was executed: " + context.getJobDetail().getKey());if (jobException != null) {System.out.println("Job encountered an exception: " + jobException.getMessage());}}
}
3. 配置 Scheduler 和 Job
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;public class QuartzJobListenerExample {public static void main(String[] args) throws SchedulerException {// 创建 Scheduler 实例Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// 定义一个 JobDetail 实例JobDetail job = JobBuilder.newJob(SampleJob.class).withIdentity("sampleJob", "group1").build();// 创建一个触发器Trigger trigger = TriggerBuilder.newTrigger().withIdentity("sampleTrigger", "group1").startNow().build();// 创建并注册 JobListenerMyJobListener listener = new MyJobListener();scheduler.getListenerManager().addJobListener(listener);// 调度任务scheduler.start();scheduler.scheduleJob(job, trigger);}
}

解释

  1. SampleJob:定义一个简单的 Job 类,包含一个 execute 方法,打印当前时间。
  2. MyJobListener:实现 JobListener 接口,提供四个方法:
    • getName:返回监听器的名称。
    • jobToBeExecuted:在任务即将执行时调用,打印任务的 Key。
    • jobExecutionVetoed:在任务被否决时调用,打印任务的 Key。
    • jobWasExecuted:在任务执行完成后调用,打印任务的 Key 和异常信息(如果有)。
  3. QuartzJobListenerExample:主类,配置 Scheduler 和 Job:
    • 创建 Scheduler 实例。
    • 定义一个 JobDetail 实例。
    • 创建一个触发器。
    • 创建并注册 JobListener 实例。
    • 调度任务并启动 Scheduler。

注意事项

  1. 异常处理:在 jobWasExecuted 方法中处理任务执行中的异常,确保异常信息被记录或处理。
  2. 并发控制:如果有多个任务并发执行,需要确保监听器的实现是线程安全的。
  3. 持久化支持:在需要持久化任务状态时,确保 Scheduler 配置支持持久化。

相关文章:

【SpringBoot】调度和执行定时任务--Quartz(超详细)

Quartz 是一个功能强大的任务调度框架&#xff0c;广泛用于在 Java 应用程序中定时执行任务&#xff0c;同时它支持 Cron 表达式、持久化任务、集群等特性。以下是 Quartz 的详细使用教程&#xff0c;包括安装、基本概念、简单示例和高级功能。 1. 安装 Quartz 首先&#xff…...

低代码开发平台系统架构概述

概述 织信低代码开发平台&#xff08;产品全称&#xff1a;织信Informat&#xff09;是一款集成了应用设计、运行与管理的综合性平台。它提供了丰富的功能模块&#xff0c;帮助用户快速构建、部署和维护应用程序。织信低代码平台通过集成丰富的功能模块&#xff0c;为用户提供…...

源码编译llama.cpp 、ggml 后端启用自定义BLAS加速

源码编译llama.cpp 、ggml 后端启用自定义BLAS加速 我在llama.cpp 官网上提交了我的解决方案&#xff1a;How to setup OpenBlas on windows? #625 GGML 官网 https://github.com/ggerganov/ggml/issues/959 windows on arm 编译 llama.cpp 、ggml 后端启用自定义BLAS加速 …...

glb数据格式

glb数据格式 glb 文件格式只包含一个glb 文件&#xff0c;文件按照二进制存储&#xff0c;占空间小 浏览 浏览glb工具的很多&#xff0c;ccs&#xff0c;3D查看器等都可以&#xff0c;不安装软件的话用下面网页加载就可以&#xff0c;免费 glTF Viewer (donmccurdy.com) glb…...

手语识别系统源码分享

手语识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …...

Oracle 数据库部署与实施

文章目录 1. macOS 上部署 Oracle 数据库通过 Docker 在 macOS 上部署 2. Linux 上部署 Oracle 数据库直接在 Linux 上部署通过 Docker 在 Linux 上部署 3. Windows 上部署 Oracle 数据库4. 使用 Docker 部署 Oracle 数据库前提条件拉取 Oracle 数据库 Docker 镜像运行 Oracle …...

【Python】 ast.literal_eval 与 eval

一、背景 我在在编写管理后台的过程中&#xff0c;遇到一个小问题&#xff0c;是关于用户名的存储和解码。用户名以base64编码的形式存储在 MySQL 数据库中&#xff0c;并且还保留了b这样的形式&#xff0c;具体为什么要这样存我也不知道,可能是因为有些特殊字符无法直接存储。…...

Java 入门指南:JVM(Java虚拟机)垃圾回收机制 —— 新一代垃圾回收器 ZGC 收集器

文章目录 垃圾回收机制垃圾收集器垃圾收集器分类ZGC 收集器ZGC 的性能优势复制算法指针染色读屏障 ZGC 的工作过程Stop-The-World 暂停阶段并发阶段 垃圾回收机制 垃圾回收&#xff08;Garbage Collection&#xff0c;GC&#xff09;&#xff0c;顾名思义就是释放垃圾占用的空…...

基于 K8S kubernetes 的常见日志收集方案

目录 1、日志对我们来说到底重不重要&#xff1f; 2、常见的日志收集方案 2.1 EFK 2.2 ELK Stack 2.3 ELKfilebeat 2.4 其他方案 2、elasticsearch组件介绍 3、filebeat组件介绍 3.1 filebeat和beat关系 3.2 filebeat是什么&#xff1f; 3.3 Filebeat工作原理 3.4 …...

Unity3D 小案例 像素贪吃蛇 02 蛇的觅食

Unity3D 小案例 像素贪吃蛇 第二期 蛇的觅食 像素贪吃蛇 食物生成 在场景中创建一个 2D 正方形&#xff0c;调整颜色&#xff0c;添加 Tag 并修改为 Food。 然后拖拽到 Assets 文件夹中变成预制体。 创建食物管理器 FoodManager.cs&#xff0c;添加单例&#xff0c;可以设置…...

【sgCreateCallAPIFunction】自定义小工具:敏捷开发→调用接口方法代码生成工具

<template><div :class"$options.name" class"sgDevTool"><sgHead /><div class"sg-container"><div class"sg-start"><div style"margin-bottom: 10px">调用接口方法定义列表</div…...

京东商品详情的 API 探秘与应用

在当今数字化的商业世界中&#xff0c;获取准确而详细的商品信息对于开发者、商家以及消费者都具有至关重要的意义。京东作为国内领先的电商平台之一&#xff0c;提供了丰富的商品资源和强大的 API 接口&#xff0c;让我们能够轻松获取京东商品的详情信息。本文将带你深入了解如…...

功能测试干了三年,快要废了。。。

8年前刚进入到IT行业&#xff0c;到现在学习软件测试的人越来越多&#xff0c;所以在这我想结合自己的一些看法给大家提一些建议。 最近聊到软件测试的行业内卷&#xff0c;越来越多的转行和大学生进入测试行业&#xff0c;导致软件测试已经饱和了&#xff0c;想要获得更好的待…...

【C++】多态的认识和理解

个人主页 文章目录 ⭐一、多态的概念&#x1f384;二、多态的定义及实现1.多态的构成2.实现多态的条件3.虚函数的概念4.虚函数的重写和覆盖5.析构函数的重写6.协变7.override和 final关键字8.重载、重写/覆盖、隐藏这三者的区别 &#x1f3e0;三、纯虚函数和抽象类的关系&#…...

linux-安全管理-用户认证

Linux 安全管理&#xff1a;用户认证 一、概述 用户认证是 Linux 安全管理的核心部分&#xff0c;确保系统能够识别并验证合法用户&#xff0c;同时阻止未经授权的访问。Linux 提供了多种用户认证机制&#xff0c;包括用户名和密码认证、基于密钥的认证、双因素认证&#xff…...

webpack5 构建优化方案看这篇就够了!【Node.js进阶】

无论在面试还是内部晋升&#xff0c;webpack 构建优化方案 一直都是非常重要的部分。 webpack5构建加持 一、项目完成目标二、搭建项目1. 安装koa、koa/router &#xff08;如果已经配置可路过&#xff09;2. 创建入口文件3. 安装构建依赖4. 在项目根目录添加 .babelrc 文件5. …...

esp32-C2 对接火山引擎实现智能语音(一)

目录 一、火山引擎大模型简介 1)火山引擎网址: 2)首先需要先注册火山引擎账号 3)语音识别——即语音转为文本 一句话识别 流式语音识别 录音文件识别标准版 录音文件识别极速版 4)语音合成——文本转音频 一、火山引擎大模型简介 火山引擎的智能语音技术,基于业界先…...

【MySQL-初级】mysql基础操作(账户、数据库、表的增删查改)

概述 数据备份与恢复 数据库备份&#xff1a;在cmd下 root用户&#xff1a;sudo mysqldump -u root -p Test > Test.sql普通用户&#xff1a;mysqldump -u zzz -p db_name > db_name.sql 数据库恢复 先创建一个空的数据库在cmd下&#xff1a;sudo mysql -u root -p d…...

centos bash脚本一键运行安装go环境

复制到install_go.sh直接bash install_go.sh运行就完了 echo ----------安装go环境 wget https://go.dev/dl/go1.21.13.linux-amd64.tar.gz tar -zxvf go1.21.13.linux-amd64.tar.gzmkdir /srv cp -r go /srv/echo "PATH$PATH:/srv/go/bin ">> ~/.bashrc echo…...

vue2制作高复用页面

记录一下页面搭建记录&#xff0c;利用vue2组件化开发的思想。这个页面适合于大部分信息管理系统~。模板固定&#xff0c;每次使用&#xff0c;直接修改表单表格参数&#xff0c;api接口等。 以上图页面为例&#xff0c;一个基础数据信息页面可以分为&#xff0c;分类&#xff…...

Feed流系统重构:架构篇

重构对我而言&#xff0c;最大的乐趣在于解决问题。我曾参与一个C#彩票算奖系统的重构&#xff0c;那时系统常因超时引发用户投诉。接手任务时&#xff0c;我既激动又紧张&#xff0c;连续两天几乎废寝忘食地编码。结果令人振奋&#xff0c;算奖时间从一小时大幅缩短至十分钟。…...

Android 后台服务之Persistent 属性

在 Android 开发中,有时我们需要后台服务持续运行,以保持应用的某些功能。例如,音乐播放器需要在后台播放音乐,或者健康应用需要持续跟踪用户的运动数据。后台服务是 Android 中的一种组件,它不与用户界面交互,能够在后台执行长时间运行的任务。由于 Android 系统的资源管…...

STM32+ESP01连接到机智云

机智云,全球领先的智能硬件软件自助开发及物联网(iot)云服务平台。机智云平台为开发者提供了自助式智能硬件开发工具与开放的云端服务。通过傻瓜化的自助工具、完善的SDK与API服务能力最大限度降低了物联网硬件开发的技术门槛&#xff0c;降低开发者的研发成本&#xff0c;提升…...

电脑实时监控软件有哪些?七个电脑屏幕监控软件任你选择

电脑实时监控软件种类繁多&#xff0c;每款软件都有其独特的功能和适用场景。 以下是七个备受推荐的电脑屏幕监控软件&#xff0c;供您选择&#xff1a; 1.安企神&#xff1a; 功能&#xff1a;它是一款国内领先的企业级电脑监控解决方案&#xff0c; 提供实时屏幕监控、 文…...

信奥学习规划(CSP-J/S)

CSP-J组学习路线规划 CSP-S组学习规划...

【Linux取经之路】编译器gcc/g++的使用 调试器gdb的使用

目录 背景知识 编译器gcc/g的安装 编译器gcc/g的使用 调试器gdb的使用 cgdb 条件断点 背景知识 子曰&#xff1a;“温故而知新”。在谈gcc/g的使用之前&#xff0c;我们先来复习编译的4个阶段&#xff0c;也算是为下面的内容做一些铺垫&#xff0c;请看思维导图。 编译…...

自动化流程机器人(RPA)

自动化流程机器人&#xff08;RPA&#xff09;正逐渐成为企业提高效率和降低成本的强有力工具。 一、RPA的概念 自动化流程机器人&#xff08;Robotic Process Automation&#xff0c;简称RPA&#xff09;是一种利用软件机器人&#xff08;Robot&#xff09;模拟和执行复杂任务…...

Unity persistentDataPath使用案例

Unity persistentDataPath使用案例 一、Application.persistentDataPath 1、概念 persistentDataPath&#xff1a;此属性用于返回一个持久化数据存储目录的路径&#xff0c;可以在此路径下存储一些持久化的数据文件&#xff1b;是一个可读写的目录&#xff1b;此文件夹在Edi…...

Android 测试手册

1. 介绍 Android 测试是确保应用程序质量的重要步骤。它包括不同类型的测试&#xff0c;用于验证应用程序的功能、性能、安全性和用户体验。这个手册将指导你了解和实施 Android 测试的主要方法和工具。 2. 测试类型 2.1 单元测试 目的&#xff1a;验证单个组件&#xff08…...

各大平台统遭入侵??区块链市场遭攻击损失近3亿!

今年&#xff0c;全球发生多起骇人听闻的勒索入侵软件攻击事件&#xff0c;黑客组织利用各种手段和技术&#xff0c;不断试图突破网络安全防线&#xff0c;窃取敏感信息、破坏系统运行&#xff0c;甚至进行勒索和敲诈&#xff0c;使得网络安全问题日益凸显其重要性和紧迫性。 S…...