当前位置: 首页 > 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…...

Android Wi-Fi 连接失败日志分析

1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分&#xff1a; 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析&#xff1a; CTR…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别

UnsatisfiedLinkError 在对接硬件设备中&#xff0c;我们会遇到使用 java 调用 dll文件 的情况&#xff0c;此时大概率出现UnsatisfiedLinkError链接错误&#xff0c;原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用&#xff0c;结果 dll 未实现 JNI 协…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

【JVM】- 内存结构

引言 JVM&#xff1a;Java Virtual Machine 定义&#xff1a;Java虚拟机&#xff0c;Java二进制字节码的运行环境好处&#xff1a; 一次编写&#xff0c;到处运行自动内存管理&#xff0c;垃圾回收的功能数组下标越界检查&#xff08;会抛异常&#xff0c;不会覆盖到其他代码…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明

AI 领域的快速发展正在催生一个新时代&#xff0c;智能代理&#xff08;agents&#xff09;不再是孤立的个体&#xff0c;而是能够像一个数字团队一样协作。然而&#xff0c;当前 AI 生态系统的碎片化阻碍了这一愿景的实现&#xff0c;导致了“AI 巴别塔问题”——不同代理之间…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA

浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求&#xff0c;本次涉及的主要是收费汇聚交换机的配置&#xff0c;浪潮网络设备在高速项目很少&#xff0c;通…...

springboot整合VUE之在线教育管理系统简介

可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生&#xff0c;小白用户&#xff0c;想学习知识的 有点基础&#xff0c;想要通过项…...

C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...