二八佳人体似酥,腰间仗剑斩愚夫。虽然不见人头落,暗里教君骨髓枯。
上一章简单介绍了 SpringBoot定时任务(三十三) ,如果没有看过,请观看上一章
关于 Quartz, 可以看老蝴蝶之前的文章: https://blog.csdn.net/yjltx1234csdn/category_9953970.html
本章节参考了文章: 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 文件
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OgqFt3Bc-1672825073638)(images/image-20230104164042059.png)]](https://img-blog.csdnimg.cn/466162b8b89443e5b3ad77b965e2441f.png)
业务代码实现定时任务
创建定时任务类, 继承 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
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RZRgx1OQ-1672825073639)(images/image-20230104171631765.png)]](https://img-blog.csdnimg.cn/7b5fa9c3eb184f3095b546f5720bf87f.png)
删除任务 /delete
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l676JYtD-1672825073639)(images/image-20230104171831389.png)]](https://img-blog.csdnimg.cn/cae45bf0adf549149043b4018620774a.png)
再次查询,没有数据了。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HC6cyV6f-1672825073640)(images/image-20230104171901615.png)]](https://img-blog.csdnimg.cn/4fecb871c12f46efa47a8cb39dbba135.png)
创建定时任务 createJob
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9aWFveDf-1672825073640)(images/image-20230104172206212.png)]](https://img-blog.csdnimg.cn/789ad19273df4a39afffd9db77b23043.png)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VRDswWWM-1672825073640)(images/image-20230104172235795.png)]](https://img-blog.csdnimg.cn/7f3b3bd67182467187a541d1ffa45153.png)
暂时定时任务 /pauseJob
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jxgzTWYW-1672825073641)(images/image-20230104172401038.png)]](https://img-blog.csdnimg.cn/00bf54e096484f73a739a4ac7e56468d.png)
不继续打印了
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xlvsGQ4t-1672825073641)(images/image-20230104172418070.png)]](https://img-blog.csdnimg.cn/9e7365a128b449199e78d8ee8de31ee8.png)
手动运行一次定时任务 runOnce
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7A7usW3o-1672825073641)(images/image-20230104172554759.png)]](https://img-blog.csdnimg.cn/693e34fd4f684aa6b4ae3da9350da938.png)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HjG6VjSf-1672825073641)(images/image-20230104172536994.png)]](https://img-blog.csdnimg.cn/a5789a97ea20403799d59cc59c7622a3.png)
只跑一次
重启定时任务 /resume
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9QijFBKZ-1672825073641)(images/image-20230104172724978.png)]](https://img-blog.csdnimg.cn/8fe4962c405442e3a68c25a863026b02.png)

更新定时任务 /update
修改 cron 表达式和 参数的值
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cr7z10yz-1672825073642)(images/image-20230104173421680.png)]](https://img-blog.csdnimg.cn/cb37894c909b4a079d81414659117c16.png)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0yw7XX10-1672825073642)(images/image-20230104173410719.png)]](https://img-blog.csdnimg.cn/bb6d4dba1d844c8493e289fd86bdad3e.png)
本章节的代码放置在 github 上:
https://github.com/yuejianli/springboot/tree/develop/SpringBoot_QuartzJob
谢谢您的观看,如果喜欢,请关注我,再次感谢 !!!




![P1047 [NOIP2005 普及组] 校门外的树](https://img-blog.csdnimg.cn/cfc61a2309ef4f40af94b1f7d216e8ee.png)













