springboot集成quartz框架,实现定时任务
后端创建一个类和方法,前端直接输入类.方法名,然后定义执行时间以及是否启用
quartz三个重要组成部分:
调度器:Scheduler
实现任务的管理;
任务:JobDetail
(通过反射)创建具体的任务实例;
触发器:Trigger
根据调度时间执行具体任务
1. 添加依赖
确保在你的pom.xml
文件中包含Quartz依赖:
<dependencies>
<!-- Spring Boot Starter for Quartz -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
</dependencies>
2. 创建一个通用的任务执行器
创建一个通用的任务执行器,用于通过反射机制执行指定类的指定方法:
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
public class DynamicJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
String className = context.getJobDetail().getJobDataMap().getString("className");
String methodName = context.getJobDetail().getJobDataMap().getString("methodName");
try {
Class<?> clazz = Class.forName(className);
Object instance = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getMethod(methodName);
method.invoke(instance);
} catch (Exception e) {
throw new JobExecutionException(e);
}
}
}
3. 创建任务调度服务
创建一个服务,用于添加、删除和管理动态任务,Trigger实现根据具体的实例名+调度时间(jobName+cronExpression),执行任务:
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class SchedulerService {
@Autowired
private Scheduler scheduler;
public void scheduleJob(String jobName, String className, String methodName, String cronExpression) throws SchedulerException {
JobDetail jobDetail = JobBuilder.newJob(DynamicJob.class)
.withIdentity(jobName)
.usingJobData("className", className)
.usingJobData("methodName", methodName)
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(jobName + "Trigger")
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.build();
scheduler.scheduleJob(jobDetail, trigger);
}
public void deleteJob(String jobName) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName);
scheduler.deleteJob(jobKey);
}
}
4. 创建控制器
创建一个控制器,用于接收前端请求,并调用调度服务的方法:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/scheduler")
public class SchedulerController {
@Autowired
private SchedulerService schedulerService;
@PostMapping("/schedule")
public String scheduleJob(@RequestParam String jobName,
@RequestParam String className,
@RequestParam String methodName,
@RequestParam String cronExpression) {
try {
schedulerService.scheduleJob(jobName, className, methodName, cronExpression);
return "Job scheduled successfully";
} catch (Exception e) {
return "Failed to schedule job: " + e.getMessage();
}
}
@DeleteMapping("/delete")
public String deleteJob(@RequestParam String jobName) {
try {
schedulerService.deleteJob(jobName);
return "Job deleted successfully";
} catch (Exception e) {
return "Failed to delete job: " + e.getMessage();
}
}
}
5. 前端(未测试)
前端使用vue,提供一个表单,用户输入jobName
、className
、methodName
以及cronExpression
,并通过HTTP请求将这些数据发送到后端API
注:cronExpression
是Quartz Cron触发器使用的一种特殊格式的字符串,用于定义任务的调度时间。Quartz的Cron表达式由6个或7个字段组成,每个字段代表一个时间单位,具体如下:[秒] [分] [小时] [日] [月] [星期] [年 (可选)];要每天下午六点执行一次任务,Quartz Cron表达式为:0 0 18 * * ?
创建一个Vue组件,提供一个表单,让用户输入调度任务的信息:
<template>
<div>
<h2>Schedule a Job</h2>
<form @submit.prevent="scheduleJob">
<div>
<label for="jobName">Job Name:</label>
<input type="text" v-model="jobName" required />
</div>
<div>
<label for="className">Class Name:</label>
<input type="text" v-model="className" required />
</div>
<div>
<label for="methodName">Method Name:</label>
<input type="text" v-model="methodName" required />
</div>
<div>
<label for="cronExpression">Cron Expression:</label>
<input type="text" v-model="cronExpression" required />
</div>
<button type="submit">Schedule Job</button>
</form>
<h2>Delete a Job</h2>
<form @submit.prevent="deleteJob">
<div>
<label for="jobName">Job Name:</label>
<input type="text" v-model="jobNameToDelete" required />
</div>
<button type="submit">Delete Job</button>
</form>
</div>
</template>
<script>
export default {
data() {
return {
jobName: '',
className: '',
methodName: '',
cronExpression: '',
jobNameToDelete: ''
};
},
methods: {
async scheduleJob() {
try {
const response = await fetch('/scheduler/schedule', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
jobName: this.jobName,
className: this.className,
methodName: this.methodName,
cronExpression: this.cronExpression,
})
});
const result = await response.text();
alert(result);
} catch (error) {
alert('Failed to schedule job: ' + error.message);
}
},
async deleteJob() {
try {
const response = await fetch('/scheduler/delete', {
method: 'DELETE',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
jobName: this.jobNameToDelete,
})
});
const result = await response.text();
alert(result);
} catch (error) {
alert('Failed to delete job: ' + error.message);
}
}
}
};
</script>
<style scoped>
form {
margin-bottom: 20px;
}
div {
margin-bottom: 10px;
}
label {
margin-right: 10px;
}
button {
margin-top: 10px;
}
</style>
6. 示例任务类
自定义任务类,示例:
package com.****.****.****.utils;
import org.springframework.stereotype.Component;
@Component
public class TestSchedulerMethod {
public void testMethodScheduler(){
System.out.println("测试定时方法"+System.currentTimeMillis());
}
}
7.使用postMan测试
类名要用全路径名;cronExpression表达式:0 * * * * ? 每分钟执行一次
注意事项
- 确保所有任务类都在Spring容器中管理(例如,使用
@Component
注解,即,6. 示例任务类,类名com...****.utils.TestSchedulerMethod,方法名:testMethodScheduler)。 - 确保Quartz的Scheduler在Spring Boot启动时自动启动,可以在
application.properties
文件中配置:
spring.quartz.job-store-type=memory
spring.quartz.properties.org.quartz.scheduler.instanceName=MyScheduler
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
8.扩展—持久化
上面示例,仅能将方法存入内存中,项目重启以后就失效了;
持久化提示:
创建一个表,调用scheduler的调度方法scheduler.scheduleJob(jobDetail,trigger);后,将用到的 jobName,className,methodName,cronExpression 信息存入表中,并创建一个init()方法,添加上@PostConstruct注解:用于在依赖注入完成后执行,将需要的定时方法重新加载
@Service
public class SchedulerService {
@Autowired
private Scheduler scheduler;
public void scheduleJob(String jobName, String className, String methodName, String cronExpression) throws SchedulerException {
JobDetail jobDetail = JobBuilder.newJob(DynamicJob.class)
.withIdentity(jobName)
.usingJobData("className", className)
.usingJobData("methodName", methodName)
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(jobName + "Trigger")
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.build();
scheduler.scheduleJob(jobDetail, trigger);
// 将定时任务保存到数据库里
ScheduledTask task = new ScheduledTask();
task.setJobName(jobName);
task.setClassName(className);
task.setMethodName(methodName);
task.setCronExpression(cronExpression);
scheduledTaskRepository.save(task);
}
public void deleteJob(String jobName) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName);
scheduler.deleteJob(jobKey);
// 删除数据库里的定时任务
ScheduledTask task = scheduledTaskRepository.findByJobName(jobName);
if (task != null) {
scheduledTaskRepository.delete(task);
}
}
@PostConstruct
public void init() throws SchedulerException {
// 加载数据库里所有的定时任务
List<ScheduledTask> tasks = scheduledTaskRepository.findAll();
for (ScheduledTask task : tasks) {
scheduleJob(task.getJobName(), task.getClassName(), task.getMethodName(), task.getCronExpression());
}
}
}
9.扩展—暂停和恢复(配合前端实现手动启停)
@RestController
@RequestMapping("/scheduler")
public class SchedulerController {
@Autowired
private SchedulerService schedulerService;
// 其他已有方法...
@PutMapping
public String toggleJob(@RequestParam String jobName, @RequestParam boolean status) {
try {
if (status) {
schedulerService.resumeJob(jobName);
} else {
schedulerService.pauseJob(jobName);
}
return "operation successfully";
} catch (Exception e) {
return "Failed to operation: " + e.getMessage();
}
}
}
@Service
public class SchedulerService {
@Autowired
private Scheduler scheduler;
// 其他已有方法...
// 若是实现了持久化pauseJob(String jobName)、resumeJob(String jobName)还需要配合持久化使用,表中增加字段持久化定时任务的执行状态
public void pauseJob(String jobName) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName);
scheduler.pauseJob(jobKey);
}
public void resumeJob(String jobName) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName);
scheduler.resumeJob(jobKey);
}
}