Quartz和@Scheduled对比
@Scheduled适合简单固定定时任务,不能动态新增,不能暂停/恢复定时任务,不能动态删除定时任务
下面是一个简单的SpringBoot+Quartz示例
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>demo</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Web 开发 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- AOP,用于实现自定义注解功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 测试依赖,Spring Boot 3.x 用这个 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Quartz依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
写个JOB
package com.example.demo.job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.time.LocalDateTime;
//具体要干什么
@DisallowConcurrentExecution//用于 例如任务要执行20分钟 但是5分钟执行一次 防止重复
public class HelloJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
System.out.println("Quartz 定时任务执行:" + LocalDateTime.now());
}
}
配置 JobDetail 和 Trigger
package com.example.demo.config;
import com.example.demo.job.HelloJob;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration//配置 JobDetail 和 Trigger
public class QuartzConfig {
/**
* helloJobDetail:定义一个任务,任务类是 HelloJob
* helloJobTrigger:定义触发器,每 5 秒执行一次 helloJob
* @return
*/
@Bean
public JobDetail helloJobDetail() {
return JobBuilder.newJob(HelloJob.class)
.withIdentity("helloJob")
.storeDurably()
.build();
}
@Bean
public Trigger helloJobTrigger() {
return TriggerBuilder.newTrigger()
.forJob(helloJobDetail())
.withIdentity("helloJobTrigger")
//有多种写法
.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))
.build();
}
}
上面代码就是固定5秒执行一次System.out.println("Quartz 定时任务执行:" + LocalDateTime.now());
和简单定时任务没有什么区别
如果希望JOB中注入Service
@Service
public class OrderService {
public void syncOrder() {
System.out.println("同步订单:" + LocalDateTime.now());
}
}
定时执行Service中的任务
package com.example.demo.job;
import com.example.demo.service.OrderService;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.time.LocalDateTime;
//具体要干什么
public class HelloJob extends QuartzJobBean {
private final OrderService orderService;
public HelloJob(OrderService orderService) {
this.orderService = orderService;
}
@Override
protected void executeInternal(JobExecutionContext context) {
orderService.syncOrder();
}
}
注意Quartz要引入spring-boot-starter-jdbc
和application.yaml
这样会自动创建相关表 当然如果你不引入spring-boot-starter-jdbc也没事 就是相关quartz的表不会自动创建 和Mybatis不起冲突 不配置application.yaml也没关系 默认内存中

spring:
application:
name: demo
datasource:
url: jdbc:mysql://127.0.0.1:3306/demo_quartz?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: xxxxxxxxx
driver-class-name: com.mysql.cj.jdbc.Driver
quartz:
job-store-type: jdbc
jdbc:
initialize-schema: always
<!-- Quartz依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
上面代码和简单定时任务没什么两样 反而简单定时任务更加方便
Quartz 真正强的地方是:
运行时动态新增任务
运行时修改 cron
运行时暂停任务
运行时恢复任务
运行时删除任务
任务信息可以存数据库
后台页面可以管理任务
Quartz理解 就是说任务代码是写死的 可以动态增删查 启动 暂停 恢复 但是不能改()
可以动态改"调度规则"
不能直接动态改"Java 代码逻辑"
Quartz 默认不是动态改代码。
Quartz 是动态管理任务调度。 当然可以实现动态改代码 但是不建议
示例:
一个喊话功能
{
"jobName": "testJob",
"jobGroup": "default",
"cron": "0/5 * * * * ?",
"message": "hello quartz"
}
写一个通用JOB
message 是创建任务时动态传进来的
package com.example.demo.job;
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.time.LocalDateTime;
public class DynamicJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) {
String message = context.getMergedJobDataMap().getString("message");
System.out.println("动态 Quartz 任务执行");
System.out.println("时间:" + LocalDateTime.now());
System.out.println("参数:" + message);
}
}
写一个通用 Job
这个 Job 不写死任务名,也不写死执行时间。
package com.example.demo.job;
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.time.LocalDateTime;
public class DynamicJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) {
String message = context.getMergedJobDataMap().getString("message");
System.out.println("动态 Quartz 任务执行");
System.out.println("时间:" + LocalDateTime.now());
System.out.println("参数:" + message);
}
}
这里重点是:
context.getMergedJobDataMap().getString("message")
这个 message 是创建任务时动态传进来的。
写请求 DTO
package com.example.demo.dto;
public class QuartzJobDTO {
private String jobName;
private String jobGroup;
private String cron;
private String message;
public String getJobName() {
return jobName;
}
public void setJobName(String jobName) {
this.jobName = jobName;
}
public String getJobGroup() {
return jobGroup;
}
public void setJobGroup(String jobGroup) {
this.jobGroup = jobGroup;
}
public String getCron() {
return cron;
}
public void setCron(String cron) {
this.cron = cron;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
写 QuartzService
核心就是这个类。
package com.example.demo.service;
import com.example.demo.dto.QuartzJobDTO;
import com.example.demo.job.DynamicJob;
import org.quartz.*;
import org.springframework.stereotype.Service;
@Service
public class QuartzService {
private final Scheduler scheduler;
public QuartzService(Scheduler scheduler) {
this.scheduler = scheduler;
}
/**
* 新增定时任务
*/
public void addJob(QuartzJobDTO dto) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(dto.getJobName(), dto.getJobGroup());
if (scheduler.checkExists(jobKey)) {
throw new RuntimeException("任务已存在:" + dto.getJobName());
}
JobDetail jobDetail = JobBuilder.newJob(DynamicJob.class)
.withIdentity(jobKey)
.usingJobData("message", dto.getMessage())
.storeDurably()
.build();
TriggerKey triggerKey = TriggerKey.triggerKey(dto.getJobName() + "Trigger", dto.getJobGroup());
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.forJob(jobDetail)
.withSchedule(CronScheduleBuilder.cronSchedule(dto.getCron()))
.build();
scheduler.scheduleJob(jobDetail, trigger);
}
/**
* 暂停任务
*/
public void pauseJob(String jobName, String jobGroup) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
scheduler.pauseJob(jobKey);
}
/**
* 恢复任务
*/
public void resumeJob(String jobName, String jobGroup) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
scheduler.resumeJob(jobKey);
}
/**
* 删除任务
*/
public void deleteJob(String jobName, String jobGroup) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
scheduler.deleteJob(jobKey);
}
/**
* 修改 cron 表达式
*/
public void updateCron(String jobName, String jobGroup, String newCron) throws SchedulerException {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName + "Trigger", jobGroup);
CronTrigger newTrigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.withSchedule(CronScheduleBuilder.cronSchedule(newCron))
.build();
scheduler.rescheduleJob(triggerKey, newTrigger);
}
/**
* 立即执行一次
*/
public void runOnce(String jobName, String jobGroup) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
scheduler.triggerJob(jobKey);
}
}
重点看这几个方法:
scheduler.scheduleJob(jobDetail, trigger); // 新增任务
scheduler.pauseJob(jobKey); // 暂停任务
scheduler.resumeJob(jobKey); // 恢复任务
scheduler.deleteJob(jobKey); // 删除任务
scheduler.rescheduleJob(triggerKey, trigger);// 修改执行时间
scheduler.triggerJob(jobKey); // 立即执行一次
这些就是 Quartz 真正有用的地方。
写 Controller
package com.example.demo.controller;
import com.example.demo.dto.QuartzJobDTO;
import com.example.demo.service.QuartzService;
import org.quartz.SchedulerException;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/quartz")
public class QuartzController {
private final QuartzService quartzService;
public QuartzController(QuartzService quartzService) {
this.quartzService = quartzService;
}
@PostMapping("/add")
public String add(@RequestBody QuartzJobDTO dto) throws SchedulerException {
quartzService.addJob(dto);
return "新增任务成功";
}
@PostMapping("/pause")
public String pause(String jobName, String jobGroup) throws SchedulerException {
quartzService.pauseJob(jobName, jobGroup);
return "暂停任务成功";
}
@PostMapping("/resume")
public String resume(String jobName, String jobGroup) throws SchedulerException {
quartzService.resumeJob(jobName, jobGroup);
return "恢复任务成功";
}
@PostMapping("/delete")
public String delete(String jobName, String jobGroup) throws SchedulerException {
quartzService.deleteJob(jobName, jobGroup);
return "删除任务成功";
}
@PostMapping("/updateCron")
public String updateCron(String jobName, String jobGroup, String cron) throws SchedulerException {
quartzService.updateCron(jobName, jobGroup, cron);
return "修改 cron 成功";
}
@PostMapping("/runOnce")
public String runOnce(String jobName, String jobGroup) throws SchedulerException {
quartzService.runOnce(jobName, jobGroup);
return "立即执行成功";
}
}
测试新增任务
用 Postman 请求:
POST http://localhost:8080/quartz/add
Content-Type: application/json
请求体:
{
"jobName": "testJob",
"jobGroup": "default",
"cron": "0/5 * * * * ?",
"message": "hello quartz"
}
控制台每 5 秒输出:
动态 Quartz 任务执行
时间:2026-06-04T...
参数:hello quartz
暂停任务
POST http://localhost:8080/quartz/pause?jobName=testJob&jobGroup=default
任务停止执行。
恢复任务
POST http://localhost:8080/quartz/resume?jobName=testJob&jobGroup=default
任务继续执行。
修改执行时间
比如改成每 10 秒执行一次:
POST http://localhost:8080/quartz/updateCron?jobName=testJob&jobGroup=default&cron=0/10 * * * * ?
删除任务
POST http://localhost:8080/quartz/delete?jobName=testJob&jobGroup=default
任务被删除。
这才是 Quartz 和 @Scheduled 的区别
@Scheduled 是这样:
@Scheduled(cron = "0/5 * * * * ?")
public void test() {
System.out.println("执行任务");
}
问题是:
cron 写死在代码里
任务写死在代码里
不能方便地从数据库加载
不能方便地后台新增
不能方便地后台暂停
不能方便地后台删除
Quartz 是这样:
任务名、cron、状态、参数可以放数据库
Spring Boot 启动时从数据库读取任务
然后动态注册到 Scheduler
后台页面可以动态管理任务
真实项目一般怎么设计
真实项目一般会建一张表:
CREATE TABLE sys_job (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
job_name VARCHAR(100),
job_group VARCHAR(100),
job_class VARCHAR(255),
cron_expression VARCHAR(100),
params VARCHAR(1000),
status TINYINT,
remark VARCHAR(500)
);
数据示例:
id: 1
job_name: syncOrderJob
job_group: default
job_class: com.example.demo.job.SyncOrderJob
cron_expression: 0 */5 * * * ?
params: {"type":"order"}
status: 1
后台页面展示:
任务名称 cron 状态 操作
同步订单 0 */5 * * * ? 启用 暂停 / 修改 / 删除 / 执行一次
清理日志 0 0 2 * * ? 启用 暂停 / 修改 / 删除 / 执行一次
然后后端根据数据库内容调用:
scheduler.scheduleJob(...)
scheduler.pauseJob(...)
scheduler.resumeJob(...)
scheduler.deleteJob(...)
scheduler.rescheduleJob(...)