Quartz学习笔记2

Posted by Young Sheep on July 20, 2023

之前学习Quartz完成了基础的定时任务配置,详见Quartz学习笔记 本来以为这种简单的定时任务配置就能满足项目需求,后来发现项目实际需求还需要动态的调整定时任务的触发条件,显然之前的基础配置无法满足这一点,但如同我在那篇笔记中最后说的那样,Quartz更强大的就是它的Scheduler管理,因此现在不学也得学了。

如果是使用其Scheduler管理功能,就不能简单的通过Quartz配置类在启动时配置项目文件了,而是应该通过编写一个QuartzManage类,定义管理定时任务的方法,然后可以编写一个初始化定时任务的类,在项目启动时调用QuartzManage类的创建任务方法实现初始化定时任务,之后需要动态调整定时任务时同样也是通过调用QuartzManage类的方法实现。

编写定时任务实现类

定时任务实现类包含了任务的业务逻辑,通过实现Job类的execute()方法,而在execute()方法中定义任务的业务逻辑,这样在触发任务时就会调用该类的execute()方法。 我直接实现的是QuartzJobBean,它是Job的抽象类,通过其executeInternal()方法实现业务逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Slf4j
@Component
public class MyJob extends QuartzJobBean {
   
    @Override
    protected void executeInternal(JobExecutionContext context){
        try {
            // 需要执行任务详细业务代码 。。。
            
            // 此处catch一下,为了在执行失败后,不让quartz再执行一次业务逻辑,导致业务数据错误,
            // 正常应该是不应该将异常抛到此处的,但是由于我的项目需要,只能在这里进行捕捉了,这里是由于Quartz的失败重试机制,容易导致数据异常。
        } catch (Exception e) {
            log.error("定时任务执行失败,跳过此次任务执行。");
        }
    }
}

编写QuartzManage类

创建定时任务的流程:

  • 创建JobDetail:通过JobBuilder的newJob()方法创建JobDetail,需要传入定时任务实现类的Class。
  • 创建CronTrigger:通过CronScheduleBuilder构造一个cron表达式的builder,再通过TriggerBuilder创建该cron的CronTrigger类。当然也可以创建其他类型的触发器,由于cron触发器就满足我的业务需求了,就没有后续研究。
  • 把定时任务加入Scheduler:通过Scheduler的scheduleJob()方法把JobDetail和Trigger加入Quartz的Scheduler中。

更新定时任务cron的流程

创建任务时通过TriggerBuilder创建CronTrigger类可以通过withIdentity()方法设置触发器的key,之后便可以通过这个key找到触发器然后实现更新触发器。

  • 获取TriggerKey:通过TriggerKey的triggerKey()方法找到TriggerKey。
  • 创建新的CronTrigger:和创建任务时一样创建新的CronTrigger类。
  • 更新Job的触发器:通过Scheduler的rescheduleJob()方法,根据TriggerKey更新其触发器为新的CronTrigger。

删除定时任务的流程

创建任务时通过JobBuilder的newJob()方法创建JobDetail时可以通过withIdentity()方法设置Job的key,之后便可以通过这个key找到这个Job然后实现删除Job。

  • 获取JobKey:通过JobKey的jobKey()方法找到JobKey。
  • 删除Job:通过Scheduler的deleteJob()方法,根据JobKey找到定时任务并删除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@Component
public class QuartzManage {
    /**
     * @Description: 创建Job
     * @Author YoungSheep
     * @Date 2023/7/19 18:13
     * @param scheduler scheduler
     * @param jobClass 实现Job类的Class
     * @param cron cron表达式
     * @param jobName job名称,用于区分不同的job
     */
    public void createScheduleJob(Scheduler scheduler, Class<? extends Job> jobClass, String cron, String jobName) throws SchedulerException {
        //获取到定时任务的执行类  必须是类的绝对路径名称
        //定时任务类需要是job类的具体实现 QuartzJobBean是job的抽象类。
        // 构建定时任务信息
        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName).build();
        // 设置定时任务执行方式
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
        // 构建触发器trigger
        CronTrigger trigger = TriggerBuilder.newTrigger().startNow().withIdentity(jobName).withSchedule(scheduleBuilder).build();
        scheduler.scheduleJob(jobDetail, trigger);
    }

    /**
     * @Description: 更新Job的cron表达式
     * @Author YoungSheep
     * @Date 2023/7/19 18:16
     * @param scheduler scheduler
     * @param cron 新的cron表达式
     * @param jobName job名称
     */
    public void updateScheduleJob(Scheduler scheduler, String cron, String jobName) throws SchedulerException {

        //获取到对应任务的触发器
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
        //设置定时任务执行方式
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
        //重新构建任务的触发器trigger
        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        if (trigger == null){
            return;
        }
        trigger = TriggerBuilder.newTrigger().startNow().withIdentity(jobName).withSchedule(scheduleBuilder).build();
        //重置对应的job
        scheduler.rescheduleJob(triggerKey, trigger);
    }

    /**
     * @Description: 删除job
     * @Author YoungSheep
     * @Date 2023/7/19 18:16
     * @param scheduler scheduler
     * @param jobName job名称
     */
    public void deleteScheduleJob(Scheduler scheduler, String jobName) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(jobName);
        scheduler.deleteJob(jobKey);

    }

}

编写项目启动时的初始化任务类

通过上面两种配置其实就已经可以实现在项目中动态配置定时任务了,即通过在Service层调用QuartzManage方法实现定时任务的动态配置。但这样并没有实现定时任务的持久化,当项目重新启动时之前的配置的定时任务就失效了,因此需要在每次动态配置定时任务时将定时任务的信息存放在数据库中,同时在项目启动时扫描数据库中的定时任务重新创建,这样便能实现简单的持久化。

其实Quartz这样优秀的框架自然会自带持久化配置,但由于我的业务中定时任务比较简单,因此便可以这样简单的实现持久化,而不用复杂的配置。

而项目启动时加载数据库中的定时任务可以通过SpringBoot的 CommandLineRunner类实现:实现CommandLineRunner的类会在其加入Spring容器时调用其run()方法,因此我们只需要编写一个定时任务的初始化类实现CommandLineRunner,并在run()方法中配置数据库中的定时任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@AllArgsConstructor
@Slf4j
@Component
public class ScheduleJobInitListener implements CommandLineRunner {
    private final QuartzManage quartzManage;
	//自动注入项目启动时Quartz创建的Scheduler
    private final Scheduler scheduler;
    
    @Override
    public void run(String... args) throws Exception {
		//初始化定时任务的业务逻辑
        //可以从数据库中获取定时任务信息并通过quartzManage重新配置
			
			
    }
}