关于定时任务

  • 概念:其实就是字面意思,设置定时器,当到达指定时间时自动完成某项任务
  • 使用场景:年度报告、各种统计报告、某个同步任务等

相关名词

  • 工作(Job):用于定义具体执行的工作
  • 工作明细(JobDetail):用于描述定时工作相关的信息
  • 触发器(Trigger):用于描述触发工作的规则,通常使用cron表达式定义调度规则
  • 调度器(Scheduler):描述了工作明细与触发器的对应关系

java 定时任务调度的实现方式

Timer、ScheduledExecutor、Spring Scheduler、开源工具包 JCronTab、开源工具包 Quartz

1.Timer

  • 特点是:简单易用。
  • 但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务;能实现简单的定时任务,稍微复杂点(或要求高一些)的定时任务却不好实现。

Java原生实现

TimerTask是一个抽象类,new它的对象要实现方法,而它里面的run方法,应该能想到的是多线程也有个run,而定时任务也确实是这个run,到时间了以后就去开一个异步线程来执行任务。(TimerTask实现了Runnable接口)

package com.zjl.timer;

import java.util.Timer;
import java.util.TimerTask;

/**
 * @author: zjl
 * @datetime: 2024/4/1
 * @desc:
 */
public class TaskTest {
    public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask task = new TimerTask(){
            @Override
            public void run(){
                System.out.println("task...");
            }
        };
        //传入要执行的任务,0即从现在开始,1000即每隔1秒执行一次
        timer.schedule(task, 0, 1000);
    }
}

2.ScheduledExecutor

鉴于 Timer 的缺陷,Java 5 推出了基于线程池设计的ScheduledExecutor。

  • 每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发执行的,相互之间不会受到干扰。
  • 需要注意的是,只有当任务的执行时间到来时,ScheduedExecutor 才会真正启动一个线程,其余时间 ScheduledExecutor 都是在轮询任务的状态。
  • 虽然用 ScheduledExecutor 和 Calendar 能够实现复杂任务调度,但实现起来还是比较麻烦,对开发还是不够友善。

3.Spring Scheduler

Spring 对任务调度的实现支持,可以指定任务的执行时间,但对任务队列和线程池的管控较弱;一般集成于项目中,小任务很方便。

4.开源工具包 JCronTab

JCronTab 则是一款完全按照 crontab 语法编写的 java 任务调度工具。

  • 可指定任务的执行时间;
  • 提供完全按照 Unix 的 UNIX-POSIX crontab 的格式来规定时间;
  • 支持多种任务调度的持久化方法,包括普通文件、数据库以及 XML 文件进行持久化;
  • JCronTab 内置了发邮件功能,可以将任务执行结果方便地发送给需要被通知的人;
  • 设计和部署是高性能并可扩展。

5.开源工具包 Quartz

  • 具有强大的调度功能,很容易与 Spring 集成,形成灵活可配置的调度功能;
  • 调度环境的持久化机制:可以保存并恢复调度现场,即使系统因为故障关闭,任务调度现场的数据并不会丢失;timer 没有这些特点;
  • 灵活的应用方式:可以灵活的定义触发器调度的时间表,并可以对触发器与任务进行关联映射;
  • 分布式与集群能力

Quartz

Quartz是 OpenSymphony 开源组织在 Job scheduling 领域又一个开源项目,完全由 Java 开发,可以用来执行定时任务,类似于 java.util.Timer。但是相较于 Timer,Quartz 增加了很多功能:

Quartz的相关概念

  • Scheduler:调度器,进行任务调度;quartz的大脑。
  • Job:业务job,亦可称业务组件;定时任务的具体执行业务需要实现此接口,调度器会调用此接口的execute方法完成我们的定时业务。
  • JobDetail:用来定义业务Job的实例,我们可以称之为quartz job,很多时候我们谈到的job指的是JobDetail。
  • Trigger:触发器,用来定义一个指定的Job何时被执行。
  • JobBuilder:Job构建器,用来定义或创建JobDetail的实例;JobDetail限定了只能是Job的实例。
  • TriggerBuilder:触发器构建器,用来定义或创建触发器的实例。

特点

  1. 可扩展性: Quartz框架具有良好的可扩展性,允许用户通过自定义插件来扩展其功能。
  2. 灵活性: Quartz提供了多种调度选项,包括固定间隔、滑动间隔、一次性任务等。用户可以根据需求轻松配置和定制调度任务。
  3. 可靠性: Quartz框架在任务调度中具有高可靠性。它支持重试和恢复机制,以确保在应用程序或框架故障时任务能够可靠地执行。
  4. 可配置性: Quartz允许用户根据应用程序的需求进行配置。用户可以自定义调度器的线程池、任务调度器、触发器等组件。
  5. 持久化: Quartz框架支持任务持久化。用户可以将任务状态存储在数据库或其他存储系统中,以便在应用程序重启时恢复任务的状态。

Quartz的一些使用场景

  1. 后台任务处理:Quartz可以用于在后台运行任务,例如数据备份,文件上传,发送电子邮件或任何其他需要定期执行的任务。
  2. 在特定时间点运行任务:如果你需要在特定时间(如每天的某个时间或每周的特定日期)运行任务,Quartz可以满足这个需求。
  3. 任务重复执行:如果你需要定期重复执行某个任务,例如每小时,每天,每周或每月,Quartz可以提供这种功能。
  4. 任务优先级和调度:Quartz允许你设置任务的优先级,并且可以基于优先级调度任务。这意味着,如果两个任务同时可用,那么优先级较高的任务会优先执行。
  5. 集群和分布式调度:Quartz支持集群和分布式环境,这意味着你可以在多个服务器或工作节点上调度和执行任务。
  6. 任务失败重试:Quartz允许你配置在任务失败时的重试策略,这对于处理可能会偶尔失败的任务非常有用。
  7. 任务依赖关系:Quartz允许任务之间建立依赖关系,这使得任务调度更加灵活和可配置。

Quartz框架中MisFire的理解

Quartz框架是Java生态中的一套非常优秀的定时任务框架,使用Quartz框架时,需要注意Quartz强烈依赖主机时间。主机时间的任何飘动,可能导致Quartz定时任务的非预期执行或不执行。任何定时框架,都可能存在MisFire的现象。

MisFire

周期性任务A需要在某个规定的时间执行,但是由于某种原因导致任务A未执行,称为MisFire。

Misfire主要定义了五种常量:

  1. TASK_PARAMS:用于存放和获取定时任务参数的KEY值。
  2. MISFIRE_DEFAULT(默认):等价于MISFIRE_FIRE_AND_PROCEED。
  3. MISFIRE_IGNORE_MISFIRES(立即执行):错过多少次,初次执行的时候,所有未触发的都会一次性立即执行,然后按照原规律进行正常后续调度。
  4. MISFIRE_FIRE_AND_PROCEED(执行一次):无论错过多少次,初次运行的时候,立即执行第一个错过的并丢弃其他的,后续的执行仍按照原规律进行执行。
  5. MISFIRE_DO_NOTHING(放弃执行):无论错过多少次,均忽略,后续的执行仍按照原规律进行执行。

Quartz常常由于下列几种情况导致任务的MisFire:

  1. Quartz的线程数较少,周期性任务较多且执行时间较长,导致某个时间点需要执行周期性任务A未被CPU调度到,导致MisFire。
  2. 将本地时间往后调整,直接跨过了周期性任务A,周期性任务A也被MisFire。
  3. 设置过去的时间为开始执行时间。例如当前时间是9:00:00,指定开始时间为7:00:00

如何设定任务的MisFire和MisFire后的补偿机制非常重要,对于改点官方文档描述较为模糊,网上一大批直接翻译官方文档的博客更是说的云里雾里。

分析

如何判定一个任务是MissFire

  • Quartz判断一个任务是MisFire,提供了一个配置项:org.quartz.jobStore.misfireThreshold,缺省是60000ms(即60秒)。只有任务执行时间延后超过这个配置项阀值,任务才被确认为MisFire状态,随后执行约定的补偿机制。
  • 也就是说,任务执行时间延后未超过misfireThreshold阀值,则会立即执行该任务。

判定一个任务为MissFire后,Quartz的补偿机制区别

补偿机制只有在任务确认为MisFire状态后,才会被执行。补偿机制比较容易理解,主要以CronTrigger机制为例。

withMisfireHandlingInstructionDoNothing

不触发立即执行,等待下次Cron触发频率到达时刻开始按照Cron频率依次执行。

withMisfireHandlingInstructionIgnoreMisfires

以错过的第一个频率时间立刻开始执行,重做错过的所有频率周期后,当下一次触发频率发生时间大于当前时间后,再按照正常的Cron频率依次执行

withMisfireHandlingInstructionFireAndProceed(默认)

以当前时间为触发频率立刻触发一次执行,然后按照Cron频率依次执行

案例:三种执行策略详解:

假设有个任务从9点开始,每隔一个小时就会执行。

  • 立即执行(withMisfireHandlingInstructionIgnoreMisfires):所有misfire的任务会马上执行,如果9点misfire了,在10:15系统恢复之后,9点,10点的misfire会马上执行,然后执行下一个周期的任务。
  • 执行一次(WithMisfireHandlingInstructionFireAndProceed):会合并部分的misfire,正常执行下一个周期的任务,假设9,10的任务都misfire了,系统在10:15分起来了。只会执行9点的misfire,10点的misfire被丢弃,下次正点执行任务。
  • 放弃执行(withMisfireHandlingInstructionDoNothing):所有的misfire不管,执行下一个周期的任务。

springboot整合Quartz

1.引入依赖

SpringBoot版本:2.7.16

				<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <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>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter-test</artifactId>
            <version>3.0.3</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>

        <!--常用工具类 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

2.ScheduleConstants常量类

package com.zjl.constant;

/**
 * @author: zjl
 * @datetime: 2024/4/1
 * @desc: Misfire定义的五种常量
 */
public class ScheduleConstants {
    //参数
    public static final String TASK_PARAMS = "PARAMS";
    //默认
    public static final String MISFIRE_DEFAULT = "0";
    //忽略错过的执行,按新 Cron 继续运行。
    public static final String MISFIRE_IGNORE_MISFIRES = "1";
    //补偿错过的执行,然后继续运行。
    public static final String MISFIRE_FIRE_AND_PROCEED = "2";
    //错过的执行不做任何处理,等待下一次 Cron 触发。
    public static final String MISFIRE_DO_NOTHING = "3";
}

3.SysJob实体类

这边我只列举了一些必须会用到的字段,其他字段可以根据实际需求来进行扩展。

package com.zjl.domain;

import lombok.Data;
import com.zjl.constant.ScheduleConstants;

/**
 * @author: zjl
 * @datetime: 2024/4/1
 * @desc:
 */
@Data
public class SysJob {
    //定时任务ID
    private String jobId;
    //定时任务名称
    private String jobName;
    //定时任务组
    private String jobGroup;
    //目标bean名
    private String beanTarget;
    //目标bean的方法名
    private String beanMethodTarget;
    //执行表达式
    private String cronExpression;
    //是否并发 0:代表允许并发执行 1:代表不允许并发执行
    private String concurrent;
    //计划策略
    private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT;
}

4.SpringUtils工具类

  1. 实现BeanFactoryPostProcessor接口,重写postProcessBeanFactory()方法,在所有Bean初始化之前就会被执行,因此可以在此处获取到最主要的BeanFactory。
  2. 通过beanFactory对象提供的API实现获取 Bean功能
package com.zjl.utils;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

/**
 * @author: zjl
 * @datetime: 2024/4/1
 * @desc:
 */
@Component
public final class SpringUtils implements BeanFactoryPostProcessor {
    //Spring应用上下文环境
    private static ConfigurableListableBeanFactory beanFactory;
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        SpringUtils.beanFactory = beanFactory;
    }
    /**
     * 获取对象
     * @param name
     * @return Object 一个以所给名字注册的bean的实例
     * @throws BeansException
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException {
        return (T) beanFactory.getBean(name);
    }
}

5.核心配置类

先简单梳理一些每个实体类的大致作用,以及它们之间的联系和执行逻辑。

实体类类作用
JobExecuteUtils任务执行的工具类,通过反射调用目标Bean的方法。
AbstractQuartzJob抽象QuartzJob类,实现了execute方法,在执行任务前后做了trycatch。
QuartzJobExecution任务执行类,继承AbstractQuartzJob,在doExecute方法中调用JobExecuteUtils、executeMethod执行目标方法。
QuartzDisallowConcurrentExecution禁止并发执行的任务执行类,也继承自AbstractQuartzJob,执行逻辑同QuartzJobExecution。
ScheduleUtils任务调度的工具类,根据SysJob创建JobDetail和CronTrigger,调度任务。
SysJobService任务管理业务接口,定义了初始化、增加、修改、删除任务等方法。
SysJobServiceImpl任务管理业务接口实现类。

执行逻辑:

  1. 初始化时,SysJobServiceImpl从数据库加载定时任务,调度到调度器。
  2. 调度器根据CronTrigger触发时间启动JobDetail。
  3. JobDetail执行时调用对应Job类的execute方法。
  4. AbstractQuartzJob实现了execute方法,在其中调用子类doExecute。
  5. 子类QuartzJobExecution的doExecute通过JobExecuteUtils反射执行目标方法。
  6. 这样就完成了定时任务的执行。

5.1 JobExecuteUtils执行定时任务方法类

  1. 我们通过jobExecutionContext.getMergedJobDataMap().get(TASK_PARAMS);这个方法去获取咱们的SysJob参数信息。
  2. 默认获取的是Object类型,所以我们需要通过BeanUtils.copyProperties(param,sysJob);这个方法进行实体类的转换和拷贝,注意参数顺序别写反了。
  3. 然后我们通过SpringUtils封装好的getBean方法,通过拷贝好的实体类中的参数去动态进行获取被Spring托管的Bean。
  4. 获取到Bean之后通过bean.getClass().getMethod(sysJob.getBeanMethodTarget());这个代码去获取Bean中需要执行的方法。
  5. 最后通过method.invoke(bean);进行执行Bean中对应的方法内容。
package com.zjl.utils;

import com.zjl.domain.SysJob;
import org.quartz.JobExecutionContext;
import org.springframework.beans.BeanUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import static com.zjl.constant.ScheduleConstants.TASK_PARAMS;

/**
 * @author: zjl
 * @datetime: 2024/4/1
 * @desc:
 */
public class JobExecuteUtils {
    /**
     * 获取bean并执行对应的方法
     * @param jobExecutionContext
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public static void executeMethod(JobExecutionContext jobExecutionContext) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Object param = jobExecutionContext.getMergedJobDataMap().get(TASK_PARAMS);
        SysJob sysJob = new SysJob();
        BeanUtils.copyProperties(param,sysJob);
        Object bean = SpringUtils.getBean(sysJob.getBeanTarget());
        Method method = bean.getClass().getMethod(sysJob.getBeanMethodTarget());
        method.invoke(bean);
    }
}

5.2 AbstractQuartzJob抽象类

定义了一个抽象类并实现了Job接口,同时重写execute方法。这个方法是定时任务执行的核心方法,其次在这个抽象类当中我们还定义了一个doExecute方法,主要就是为了写我们定时任务的执行逻辑。

package com.zjl.domain;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.lang.reflect.InvocationTargetException;

/**
 * @author: zjl
 * @datetime: 2024/4/1
 * @desc: 抽象类 Quartz调用
 */
public abstract class AbstractQuartzJob implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        try {
            doExecute(jobExecutionContext);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    protected abstract void doExecute(JobExecutionContext jobExecutionContext) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException;
}

5.3 QuartzJobExecution/QuartzDisallowConcurrentExecution开启/禁止并发执行任务类

继承上述的抽象类,通过@DisallowConcurrentExecution这个注解来禁止咱们的定时任务的并发执行,如果不加则是默认允许并发执行,最后我们重写了doExecute方法去执行我们定时任务的处理逻辑。
例如有个定时任务每隔5秒执行一次,不过这个任务做完可能需要20秒。

允许并发执行:

在这处理的20秒钟,肯定需要处理下一个任务了,如果设置了允许并发执行,不会等到上一个任务执行完毕才会执行下一个任务,只要每隔5s就会执行下一个任务。

package com.zjl.domain;

import org.quartz.JobExecutionContext;
import java.lang.reflect.InvocationTargetException;
import static com.zjl.utils.JobExecuteUtils.executeMethod;

/**
 * @author: zjl
 * @datetime: 2024/4/1
 * @desc:
 */
public class QuartzJobExecution extends AbstractQuartzJob {
    @Override
    protected void doExecute(JobExecutionContext jobExecutionContext) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        executeMethod(jobExecutionContext);
    }
}

允许并发执行体现在任务一中

不允许并发执行(@DisallowConcurrentExecution):

在这处理的20秒钟,肯定需要处理下一个任务了,如果设置了不允许并发执行,会必须等到上一个任务执行完毕才会执行下一个任务

package com.zjl.domain;

import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import java.lang.reflect.InvocationTargetException;
import static com.zjl.utils.JobExecuteUtils.executeMethod;

/**
 * @author: zjl
 * @datetime: 2024/4/1
 * @desc: 定时任务处理(禁止并发执行)
 */
@DisallowConcurrentExecution
public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob
{
    @Override
    protected void doExecute(JobExecutionContext jobExecutionContext) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        executeMethod(jobExecutionContext);
    }
}

不允许并发执行体现在任务二中

5.4 ScheduleUtils定时任务工具类

一共封装三个方法

getQuartzJobClass

这个方法是主要通过SysJob实体类当中会通过动态参数获取任务类并决定当前任务是否并发执行(0代表并发执行,1代表禁止并发执行)

handleCronScheduleMisfirePolicy

这个方法主要设置待执行任务的执行策略。

createScheduleJob

这个方法是最主要的方法,它的逻辑总共7个步骤如下:

  1. 得到任务类(是否并发执行)
  2. 构建job信息
  3. 构件表达式调度构建器
  4. 配置执行策略
  5. 按新的cronExpression表达式构建一个新的trigger
  6. 放入参数,运行时的方法可以获取
  7. 执行调度任务
代码
package com.zjl.utils;

import com.zjl.constant.ScheduleConstants;
import com.zjl.domain.QuartzDisallowConcurrentExecution;
import com.zjl.domain.QuartzJobExecution;
import com.zjl.domain.SysJob;
import org.quartz.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationTargetException;

import static com.zjl.constant.ScheduleConstants.TASK_PARAMS;

/**
 * @author: zjl
 * @datetime: 2024/4/1
 * @desc: 定时任务工具类
 */
@Component
public class ScheduleUtils {
    /**
     * 得到quartz任务类
     * @param sysJob 执行计划
     * @return 具体执行任务类
     */
    private static Class<? extends Job> getQuartzJobClass(SysJob sysJob) {
        boolean isConcurrent = "0".equals(sysJob.getConcurrent());
        return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
    }
    /**
     * 创建定时任务
     */
    public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //获取任务类型
        Class<? extends Job> jobClass = getQuartzJobClass(job);
        // 构建job信息
        String cornExpression = job.getCronExpression();
        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(job.getJobId(),job.getJobGroup()).build();
        // 表达式调度构建器
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cornExpression);
        //配置执行策略
        cronScheduleBuilder = handleCronScheduleMisfirePolicy(job,cronScheduleBuilder);
        // 按新的cronExpression表达式构建一个新的trigger
        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(job.getJobId(),job.getJobGroup())
                .withSchedule(cronScheduleBuilder).build();
        // 放入参数,运行时的方法可以获取
        jobDetail.getJobDataMap().put(TASK_PARAMS, job);
        // 执行调度任务
        scheduler.scheduleJob(jobDetail, trigger);
    }
    /**
     * 设置定时任务策略
     */
    public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb) {
        switch (job.getMisfirePolicy()) {
            case ScheduleConstants.MISFIRE_DEFAULT:
                return cb;
            case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
                return cb.withMisfireHandlingInstructionIgnoreMisfires();
            case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
                return cb.withMisfireHandlingInstructionFireAndProceed();
            case ScheduleConstants.MISFIRE_DO_NOTHING:
                return cb.withMisfireHandlingInstructionDoNothing();
            default:
                throw new RuntimeException("策略异常");
        }
    }
}

5.5 SysJobService定时任务调度信息接口

撰写定时任务调度的接口,主要分为初始化所有任务、新增任务,立即执行任务、更新任务、暂停任务,恢复任务和删除任务,代码如下,这个不多说,主要详细讲解一下它的实现类。

package com.zjl.service;
import org.quartz.SchedulerException;
import java.lang.reflect.InvocationTargetException;
import com.zjl.domain.SysJob;
/**
 * @author: zjl
 * @datetime: 2024/4/1
 * @desc: 定时任务调度信息
 */
public interface SysJobService {
    /**
     * 项目启动时,初始化定时器
     */
    void init() throws SchedulerException, NoSuchMethodException, InvocationTargetException, IllegalAccessException;
    /**
     * 新增任务
     * @param job 调度信息
     * @return 结果
     */
    public int insertJob(SysJob job) throws SchedulerException, InvocationTargetException, NoSuchMethodException, IllegalAccessException;
    /**
     * 立即运行任务
     * @param job 调度信息
     * @return 结果
     */
    public void run(SysJob job) throws SchedulerException;
    /**
     * 更新任务
     * @param job 调度信息
     * @return 结果
     */
    public int updateJob(SysJob job) throws SchedulerException, InvocationTargetException, NoSuchMethodException, IllegalAccessException;
    /**
     * 暂停任务
     * @param job 调度信息
     * @return 结果
     */
    public int pauseJob(SysJob job) throws SchedulerException;
    /**
     * 恢复任务
     * @param job 调度信息
     * @return 结果
     */
    public int resumeJob(SysJob job) throws SchedulerException;
    /**
     * 删除任务后,所对应的trigger也将被删除
     * @param job 调度信息
     * @return 结果
     */
    public int deleteJob(SysJob job) throws SchedulerException;
}

5.6 SysJobServiceImpl定时任务调度信息接口实现类

  1. 首先initTaskList这个方法我这边做了偷懒,直接写了一个List集合在项目中,正确的配置应该是从数据库去查询一共有哪些定时任务需要程序去执行的,以及对应的配置信息应该如何去执行。
  2. 其次在init方法中,@PostConstruct这个注解会在项目启动的时候帮我们进行初始化,其次我调用了clear方法进行先清空定时任务,使用循环获取到需要所有定时任务集合,通过createScheduleJob方法去创建。
package com.zjl.service;
import com.zjl.utils.ScheduleUtils;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import com.zjl.domain.SysJob;

import static com.zjl.constant.ScheduleConstants.MISFIRE_IGNORE_MISFIRES;

/**
 * @author: zjl
 * @datetime: 2024/4/1
 * @desc:
 */
@Service
public class SysJobServiceImpl implements SysJobService {

    @Resource
    private Scheduler scheduler;

    /**
     * 模拟从数据库获取数据
     * @return
     */
    public List<SysJob> initTaskList(){
        List<SysJob> list = new ArrayList<>();
        SysJob job = new SysJob();
        job.setJobId(UUID.randomUUID().toString());
        job.setJobGroup("system");
        job.setConcurrent("0");
        job.setCronExpression("0/5 * * * * ?");
        job.setBeanTarget("task1");
        job.setBeanMethodTarget("handle");
        list.add(job);
        job = new SysJob();
        job.setJobId(UUID.randomUUID().toString());
        job.setJobGroup("system");
        job.setConcurrent("1");
        job.setCronExpression("0/50 * * * * ?");
        job.setBeanTarget("task2");
        job.setBeanMethodTarget("handle");
        job.setMisfirePolicy(MISFIRE_IGNORE_MISFIRES);
        list.add(job);
        return list;
    }

    /**
     * 初始化定时任务
     * @throws SchedulerException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    @PostConstruct
    @Override
    public void init() throws SchedulerException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        scheduler.clear();
        List<SysJob> list = initTaskList();
        for (int i = 0 ; i < list.size() ; i ++){
            SysJob job = list.get(i);
            ScheduleUtils.createScheduleJob(scheduler, job);
        }
    }

    @Override
    public int insertJob(SysJob job) throws SchedulerException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        ScheduleUtils.createScheduleJob(scheduler, job);
        return 1;
    }

    @Override
    public void run(SysJob job) throws SchedulerException {
        scheduler.triggerJob(JobKey.jobKey(job.getJobId(),job.getJobGroup()));
    }

    @Override
    public int updateJob(SysJob job) throws SchedulerException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        // 判断是否存在
        JobKey jobKey = JobKey.jobKey(job.getJobId(),job.getJobGroup());
        if (scheduler.checkExists(jobKey)) {
            scheduler.deleteJob(jobKey);
        }
        ScheduleUtils.createScheduleJob(scheduler, job);
        return 1;
    }

    @Override
    public int pauseJob(SysJob job) throws SchedulerException {
        scheduler.pauseJob(JobKey.jobKey(job.getJobId(),job.getJobGroup()));
        return 1;
    }

    @Override
    public int resumeJob(SysJob job) throws SchedulerException {
        scheduler.resumeJob(JobKey.jobKey(job.getJobId(),job.getJobGroup()));
        return 1;
    }

    @Override
    public int deleteJob(SysJob job) throws SchedulerException {
        scheduler.deleteJob(JobKey.jobKey(job.getJobId(),job.getJobGroup()));
        return 1;
    }
}

测试一下吧

任务一:

package com.zjl.task;

import org.springframework.stereotype.Component;
import java.util.Date;

@Component("task1")
public class Task1 {
    public void handle() throws InterruptedException {
        String dateStr = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date());
        System.out.println("task1"+ dateStr +"开始");
        Thread.sleep(10000);
        System.out.println("task1"+ dateStr +"结束");
    }
}

任务二:

package com.zjl.task;

import org.springframework.stereotype.Component;
import java.util.Date;

@Component("task2")
public class Task2 {
    public void handle(){
        String dateStr = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date());
        System.out.println("task2"+ dateStr +"开始");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("task2"+ dateStr +"结束");
    }
}

可以分别采用并发或者非并发方式测试,反正参数都可以动态进行配置

更多推荐