二八佳人体似酥,腰间仗剑斩愚夫。虽然不见人头落,暗里教君骨髓枯。

上一章简单介绍了 SpringBoot定时任务(三十三) ,如果没有看过,​​请观看上一章​​


本章节参考了文章: ​​springboot整合quartz实现定时任务的动态修改,启动,暂停等操作​

在这个大佬的基础上,进行了扩充。

SpringBoot 整合 Quartz

pom.xml 添加依赖

<!--添加 spring-boot-starter-quartz的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- json 工具 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>

application.yml 文件配置

# 引入 数据库的相关配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.100.252:3306/springboot?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true
username: root
password: abc123
#配置 quartz
quartz:
#初始化数据库脚本路径,默认使用classpath:org/quartz/impl/jdbcjobstore/tables_@@platform@@.sql路径下的脚本
#注意,放置的是 spring.quartz 下面
#持久化到数据库方式
job-store-type: jdbc
# 初始化Quartz表结构,项目第一次启动配置程always,然后改成never 否则已生成的job会被初始化掉
initialize-schema: never
properties:
org:
quartz:
scheduler:
instanceName: MyScheduler
instanceId: AUTO
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
tablePrefix: QRTZ_
isClustered: true
clusterCheckinInterval: 10000
useProperties: false
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 10
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true

需要先初始化表结构。

在这个 jar 包上, quartz-2.3.2.jar org.quartz.impl.jdbcjobstore.tables.mysql_innodb.sql

在相应的库上面 执行该 sql 文件

SpringBoot整合Quartz实现动态定时任务(三十四)_整合Quartz

业务代码实现定时任务

创建定时任务类, 继承 QuartzJobBean

创建定时任务类: MyQuartzJob

//继承 抽象类  QuartzJobBean
public class MyQuartzJob extends QuartzJobBean {
@Autowired
private UserService userService;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {

System.out.println(" 执行定时任务: " + context.getJobDetail().getKey());
// 获取参数
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
User user = new User();
if (!ObjectUtils.isEmpty(jobDataMap)) {
user.setName(jobDataMap.getString("name"));
user.setAge(jobDataMap.getInt("age"));
user.setSex(jobDataMap.getString("sex"));
user.setDescription(jobDataMap.getString("description"));
}
userService.addUser(user);
}
}

配置 JobDetail 和 Trigger

@Configuration
public class QuartzConfig {
@Bean
public JobDetail myJobDetail(){
JobDetail jobDetail = JobBuilder.newJob(MyQuartzJob.class)
.withIdentity("myQuartzJob","system")
//JobDataMap可以给任务execute传递参数
.usingJobData("name","两个蝴蝶飞")
.usingJobData("age",26)
.storeDurably()
.build();
return jobDetail;
}
@Bean
public Trigger myTrigger(){
Trigger trigger = TriggerBuilder.newTrigger()
.forJob(myJobDetail())
.withIdentity("myTrigger1","myTriggerGroup1")
.usingJobData("job_trigger_param","job_trigger_param1")
.startNow()
//.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
// 固化的 cron 表达式
.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?"))
.build();
return trigger;
}
}

执行程序

启动类上不需要添加 @EnableScheduling 注解

@SpringBootApplication
@Log4j2
public class JobApplication {
public static void main(String[] args) {
SpringApplication.run(JobApplication.class,args);
log.info("运行定时任务启动成功");
}
}

如果有任务的话,服务器停止之后还会执行那个时候的任务,后面会变正常,每隔5s 执行一次, 任务参数也可以获取到。

2023-01-04 16:47:24.015  INFO 23132 --- [  restartedMain] top.yueshushu.learn.JobApplication       : 运行定时任务启动成功
2023-01-04 16:47:34.025 INFO 23132 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore : ClusterManager: detected 1 failed or restarted instances.
2023-01-04 16:47:34.025 INFO 23132 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore : ClusterManager: Scanning for instance "Yuejl1672822021469"'s failed in-progress jobs.
2023-01-04 16:47:34.100 INFO 23132 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore : ClusterManager: ......Freed 1 acquired trigger(s).
执行定时任务: system.myQuartzJob
2023-01-04 16:47:34.342 INFO 23132 --- [eduler_Worker-1] t.y.learn.service.UserServiceImpl : 执行添加员工的操作,User(id=null, name=两个蝴蝶飞, sex=null, age=26, description=null)
执行定时任务: system.myQuartzJob
2023-01-04 16:47:34.618 INFO 23132 --- [eduler_Worker-2] t.y.learn.service.UserServiceImpl : 执行添加员工的操作,User(id=null, name=两个蝴蝶飞, sex=null, age=26, description=null)
执行定时任务: system.myQuartzJob
2023-01-04 16:47:34.833 INFO 23132 --- [eduler_Worker-3] t.y.learn.service.UserServiceImpl : 执行添加员工的操作,User(id=null, name=两个蝴蝶飞, sex=null, age=26, description=null)
执行定时任务: system.myQuartzJob
2023-01-04 16:47:35.112 INFO 23132 --- [eduler_Worker-4] t.y.learn.service.UserServiceImpl : 执行添加员工的操作,User(id=null, name=两个蝴蝶飞, sex=null, age=26, description=null)
执行定时任务: system.myQuartzJob
2023-01-04 16:47:40.130 INFO 23132 --- [eduler_Worker-5] t.y.learn.service.UserServiceImpl : 执行添加员工的操作,User(id=null, name=两个蝴蝶飞, sex=null, age=26, description=null)
执行定时任务: system.myQuartzJob
2023-01-04 16:47:45.164 INFO 23132 --- [eduler_Worker-6] t.y.learn.service.UserServiceImpl : 执行添加员工的操作,User(id=null, name=两个蝴蝶飞, sex=null, age=26, description=null)
执行定时任务: system.myQuartzJob
2023-01-04 16:47:50.110 INFO 23132 --- [eduler_Worker-7] t.y.learn.service.UserServiceImpl : 执行添加员工的操作,User(id=null, name=两个蝴蝶飞, sex=null, age=26, description=null)

动态实现定时任务调度

上面的方式是写死在程序当中的,也存在修改不方便的情况。

实体接收数据 QuartzBean

@Data
public class QuartzBean implements Serializable {
/** 任务id */
private String id;

/** 任务名称 */
private String jobName;

/** 任务执行类 */
private String jobClass;

/** 组名 */
private String groupName;

/** 任务 参数信息 */
private String jobParam;

/** 任务状态 启动还是暂停*/
private Integer status;

/** 任务运行时间表达式 */
private String cronExpression;
}

工具类 QuartzUtils

/**
* 任务工具类
*
* @author yuejianli
* @date 2023-01-04
*/
public class QuartzUtils {
/**
获取所有的定时任务
* @throws Exception
*/
public static List<QuartzBean> getAllJob(Scheduler scheduler){
GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
List<QuartzBean> jobList = new ArrayList();
try {
Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
for (JobKey jobKey : jobKeys) {
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
for (Trigger trigger : triggers) {
QuartzBean job = new QuartzBean();
job.setJobName(jobKey.getName());
job.setGroupName(jobKey.getGroup());
Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
job.setStatus(Trigger.TriggerState.NORMAL.equals(triggerState)?1:0);
if (trigger instanceof CronTrigger) {
CronTrigger cronTrigger = (CronTrigger) trigger;
String cronExpression = cronTrigger.getCronExpression();
job.setCronExpression(cronExpression);
}
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
JobDataMap jobDataMap = jobDetail.getJobDataMap();
String[] keys = jobDataMap.getKeys();
if (keys != null && keys.length > 0){
Map<String,String> paramMap = new HashMap<>(keys.length,1.0f);
for (String key: keys) {
paramMap.put(key, jobDataMap.get(key).toString());
}
String paramStr = JSON.toJSONString(paramMap);
job.setJobParam(paramStr);
}
Class<? extends Job> jobClass = jobDetail.getJobClass();
job.setJobClass(jobClass.getName());
jobList.add(job);
}
}

} catch (SchedulerException e) {
e.printStackTrace();
}
return jobList;
}

/**
* 创建定时任务 定时任务创建之后默认启动状态
* @param scheduler 调度器
* @param quartzBean 定时任务信息类
* @throws Exception
*/
public static void createScheduleJob(Scheduler scheduler, QuartzBean quartzBean){
try {
//获取到定时任务的执行类 必须是类的绝对路径名称
//定时任务类需要是job类的具体实现 QuartzJobBean是job的抽象类。
Class<? extends Job> jobClass = (Class<? extends Job>) Class.forName(quartzBean.getJobClass());
// 构建定时任务信息
JobBuilder jobBuilder = JobBuilder.newJob(jobClass).withIdentity(quartzBean.getJobName(), quartzBean.getGroupName());

// 设置参数
Map<String,String> paramHashMap = JSON.parseObject(quartzBean.getJobParam(), HashMap.class);
if (!ObjectUtils.isEmpty(paramHashMap)){
paramHashMap.forEach(
(param,paramValue)->{
jobBuilder.usingJobData(param,paramValue);
}
);
}
JobDetail jobDetail = jobBuilder
.storeDurably()
.build();
// 设置定时任务执行方式
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzBean.getCronExpression());
// 构建触发器trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(quartzBean.getJobName()).withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail, trigger);
} catch (ClassNotFoundException e) {
System.out.println("定时任务类路径出错:请输入类的绝对路径");
} catch (SchedulerException e) {
System.out.println("创建定时任务出错:"+e.getMessage());
}
}

/**
* 根据任务名称暂停定时任务
* @param scheduler 调度器
* @param jobKeyName 定时任务名称
* @throws SchedulerException
*/
public static void pauseScheduleJob(Scheduler scheduler, String jobKeyName){
String[] jobNameGroupArr = jobKeyName.split("\\.");
JobKey jobKey = JobKey.jobKey(jobNameGroupArr[1],jobNameGroupArr[0]);
try {
scheduler.pauseJob(jobKey);
} catch (SchedulerException e) {
System.out.println("暂停定时任务出错:"+e.getMessage());
}
}

/**
* 根据任务名称恢复定时任务
* @param scheduler 调度器
* @param jobKeyName 定时任务名称
* @throws SchedulerException
*/
public static void resumeScheduleJob(Scheduler scheduler, String jobKeyName) {
String[] jobNameGroupArr = jobKeyName.split("\\.");
JobKey jobKey = JobKey.jobKey(jobNameGroupArr[1],jobNameGroupArr[0]);
try {
scheduler.resumeJob(jobKey);
} catch (SchedulerException e) {
System.out.println("启动定时任务出错:"+e.getMessage());
}
}

/**
* 根据任务名称立即运行一次定时任务
* @param scheduler 调度器
* @param jobKeyName 定时任务名称
* @throws SchedulerException
*/
public static void runOnce(Scheduler scheduler, String jobKeyName){
String[] jobNameGroupArr = jobKeyName.split("\\.");
JobKey jobKey = JobKey.jobKey(jobNameGroupArr[1],jobNameGroupArr[0]);
try {
scheduler.triggerJob(jobKey);
} catch (SchedulerException e) {
System.out.println("运行定时任务出错:"+e.getMessage());
}
}

/**
* 更新定时任务
* @param scheduler 调度器
* @param quartzBean 定时任务信息类
* @throws SchedulerException
*/
public static void updateScheduleJob(Scheduler scheduler, QuartzBean quartzBean) {
deleteScheduleJob(scheduler,quartzBean.getGroupName()+"."+quartzBean.getJobName());
createScheduleJob(scheduler,quartzBean);
}

/**
* 根据定时任务名称从调度器当中删除定时任务
* @param scheduler 调度器
* @param jobKeyName 定时任务名称
* @throws SchedulerException
*/
public static void deleteScheduleJob(Scheduler scheduler, String jobKeyName) {
String[] jobNameGroupArr = jobKeyName.split("\\.");
JobKey jobKey = JobKey.jobKey(jobNameGroupArr[1],jobNameGroupArr[0]);
try {
if (ObjectUtils.isEmpty(jobKey)){
return ;
}
scheduler.deleteJob(jobKey);
} catch (SchedulerException e) {
System.out.println("删除定时任务出错:"+e.getMessage());
}
}
}

编写定时任务

这个与具体的业务有关, 需要提前创建好。

如 这儿老蝴蝶定义两个任务, 一个有参数,一个无参数。

无参数任务:

/**
* 任务, 是 空参数
*
* @author yuejianli
* @date 2023-01-04
*/
@Slf4j
public class MyTask1 extends QuartzJobBean {

@Autowired
private UserService userService;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
userService.addUser(null);
log.info(" 空参数任务 :" + context.getJobDetail().getKey());
}
}

有参数任务:

@Slf4j
public class MyTask2 extends QuartzJobBean {

@Autowired
private UserService userService;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
log.info(" 有参数任务 :" + context.getJobDetail().getKey());
if (ObjectUtils.isEmpty(jobDataMap)) {
userService.addUser(null);
return ;
}
User user = new User();
user.setName(jobDataMap.getString("name"));
user.setAge(jobDataMap.getInt("age"));
user.setSex(jobDataMap.getString("sex"));
user.setDescription(jobDataMap.getString("description"));
userService.addUser(user);
}
}

控制实现类

/**
* 定时任务 controller
*/
@RestController
@RequestMapping("/quartz/")
public class ScheduledController {
/**
注入任务调度
*/
@Resource
private Scheduler scheduler;

@RequestMapping("/getAll")
public List<QuartzBean> getAll() {
try {
return QuartzUtils.getAllJob(scheduler);
} catch (Exception e) {
return null;
}
}

@RequestMapping("/createJob")
public String createJob(@RequestBody QuartzBean quartzBean) {
try {
QuartzUtils.createScheduleJob(scheduler,quartzBean);
} catch (Exception e) {
return "创建失败";
}
return "创建成功";
}

@RequestMapping("/pauseJob")
public String pauseJob(String jobName) {
try {
QuartzUtils.pauseScheduleJob (scheduler,jobName);
} catch (Exception e) {
return "暂停失败";
}
return "暂停成功";
}

@RequestMapping("/runOnce")
@ResponseBody
public String runOnce(String jobName) {
try {
QuartzUtils.runOnce (scheduler,jobName);
} catch (Exception e) {
return "运行一次失败";
}
return "运行一次成功";
}

@RequestMapping("/resume")
@ResponseBody
public String resume(String jobName) {
try {

QuartzUtils.resumeScheduleJob(scheduler,jobName);
} catch (Exception e) {
return "重新开启失败";
}
return "重新开启成功";
}

@RequestMapping("/delete")
@ResponseBody
public String delete(String jobName) {
try {

QuartzUtils.deleteScheduleJob(scheduler,jobName);
} catch (Exception e) {
return "重新开启失败";
}
return "重新开启成功";
}

@RequestMapping("/update")
@ResponseBody
public String update(@RequestBody QuartzBean quartzBean) {
try {
QuartzUtils.updateScheduleJob(scheduler,quartzBean);
} catch (Exception e) {
return "更新失败";
}
return "更新成功";
}
}

测试运行

获取所有的任务 /getAll

SpringBoot整合Quartz实现动态定时任务(三十四)_整合Quartz_02

删除任务 /delete

SpringBoot整合Quartz实现动态定时任务(三十四)_spring boot_03

再次查询,没有数据了。

SpringBoot整合Quartz实现动态定时任务(三十四)_Quartz动态定时任务_04

创建定时任务 createJob

SpringBoot整合Quartz实现动态定时任务(三十四)_定时任务_05

SpringBoot整合Quartz实现动态定时任务(三十四)_整合Quartz_06

暂时定时任务 /pauseJob

SpringBoot整合Quartz实现动态定时任务(三十四)_User_07

不继续打印了

SpringBoot整合Quartz实现动态定时任务(三十四)_spring boot_08

手动运行一次定时任务 runOnce

SpringBoot整合Quartz实现动态定时任务(三十四)_定时任务_09

SpringBoot整合Quartz实现动态定时任务(三十四)_User_10

只跑一次

重启定时任务 /resume

SpringBoot整合Quartz实现动态定时任务(三十四)_整合Quartz_11

SpringBoot整合Quartz实现动态定时任务(三十四)_User_12

更新定时任务 /update

修改 cron 表达式和 参数的值

SpringBoot整合Quartz实现动态定时任务(三十四)_定时任务_13

SpringBoot整合Quartz实现动态定时任务(三十四)_整合Quartz_14