【RuoYi_02】:定时任务的实现

发布于:2024-05-04 ⋅ 阅读:(24) ⋅ 点赞:(0)

本文主要包含以下内容:

  • springboot 项目整合 quartz
  • 若以定时任务的功能实现

若以定时任务是采用 quartz 实现的,涉及的库表有 quartz.sql 中的表以及 sys_job 表,前者是 springboot 项目整合 quartz 所需要的持久化必备的,后者是自己定义的 job任务。

springboot 整合 quartz

quartz中的基本概念

job:表示一个任务、要执行的具体内容。通常由我们去实现 job 接口或者继承 QuartzJobBean类。

JobDetail:表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,定义了job的名称、所属组等基本信息,另外 JobDetail 还包含了这个任务调度的方案和策略。

JobDataMap

向作业实例传递参数和数据:通过将数据放入 JobDataMap,可以将数据传递给job实例。

在触发器中设置作业相关的数据:您还可以在 Trigger 中设置 JobDataMap,这样在触发器触发时,可以将数据传递给作业实例。这使得可以在触发作业之前对作业实例进行配置或准备

Trigger:触发器,触发任务的时刻

Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。他们之间的关系可以用下图来表示:

示例代码:

第一步:创建 springboot 项目并引入依赖。

<!-- 实现对 Quartz 的自动化配置 -->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-quartz -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
    <version>2.7.11</version>
</dependency>

第二步:创建任务,这里以实现job接口为例。

@Slf4j
public class QuartzJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        String jobMsg = (String) context.getJobDetail().getJobDataMap().get("msg");
        String triggerMsg = (String) context.getTrigger().getJobDataMap().get("msg");
        log.info("jobMsg:{},triggerMsg:{}", jobMsg, triggerMsg);
    }
}

Job接口中,需要我们实现 execute() 方法,这个方法就是我们执行业务逻辑的地方。

第三步:创建 JobDetail 并配置调度器和触发器

@Configuration
@Slf4j
public class QuartzConfig {
​
    private static final String ID = "QUARTZ";
​
    @Bean
    public JobDetail jobDetail1() {
        return JobBuilder.newJob(QuartzJob.class)
                .withIdentity(ID + " 01")
                .usingJobData("msg", "Hello Quartz -- jobDetail")
                .storeDurably()
                .build();
    }
​
    @Bean
    public Trigger trigger1() {
        // 简单的调度计划的构造器
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(10) // 频率
                .repeatForever(); // 次数
​
        return TriggerBuilder.newTrigger()
                .forJob(jobDetail1())
                .withIdentity(ID + " 01Trigger")
                .usingJobData("msg", "Hello Quartz -- trigger")
                .withSchedule(scheduleBuilder)
                .build();
    }
}
​

不需要进行其他额外的配置,直接启动项目,即可得到如下的运行日志:

这就是 SpringBoot 中整合 Quzrtz 的简单示例。

若以定时任务实现

若以在项目中也引入的 quartz 来实现定时任务,分析一下在若以中是如何使用的。首先是在数据库中执行quartz.sql中的语句。

数据库表分析:在若以系统中,表 sys_job 用来持久化系统中任务。sys_job_log 用来记录定时任务的执行。

sys_job 分析:

private Long jobId;
/** 任务名称 */
private String jobName;
/** 任务组名 */
private String jobGroup;
/** 调用目标字符串 */
private String invokeTarget;
/** cron执行表达式 */
private String cronExpression;
/** cron计划策略 */
private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT;
/** 是否并发执行(0允许 1禁止) */
private String concurrent;
/** 任务状态(0正常 1暂停) */
private String status;

这里面要额外注意的字段为invokeTargetcronExpression,前者通过反射机制找到对应的执行方法,后者则是任务的调度方式。

在工具类 JobInvokeUtil 中,通过如下代码执行:

public static void invokeMethod(SysJob sysJob) throws Exception
{
    String invokeTarget = sysJob.getInvokeTarget();
    String beanName = getBeanName(invokeTarget);
    String methodName = getMethodName(invokeTarget);
    List<Object[]> methodParams = getMethodParams(invokeTarget);
​
    if (!isValidClassName(beanName))
    {
        // 通过bean名称获取bean      
        Object bean = SpringUtils.getBean(beanName);
        invokeMethod(bean, methodName, methodParams);
    }
    else
    {
        // 通过类全限定名称的方式获取bean
        Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();
        invokeMethod(bean, methodName, methodParams);
    }
}
​
private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
            throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
            InvocationTargetException
    {
        if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0)
        {
            Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams));
            method.invoke(bean, getMethodParamsValue(methodParams));
        }
        else
        {
            Method method = bean.getClass().getMethod(methodName);
            method.invoke(bean);
        }
    }

若以中是通过抽象类AbstractQuartzJob实现接口Job接口来定义的。通过子类来实现doExecute()方法。

AbstractQuartzJob 类中,execute() 方法实现如下:

@Override
public void execute(JobExecutionContext context) throws JobExecutionException
{
    SysJob sysJob = new SysJob();
    BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
    try
    {
        before(context, sysJob);
        if (sysJob != null)
        {
            doExecute(context, sysJob);
        }
        after(context, sysJob, null);
    }
    catch (Exception e)
    {
        log.error("任务执行异常  - :", e);
        after(context, sysJob, e);
    }
}

子类中实现 doExecute() 方法:

@DisallowConcurrentExecution
public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob
{
    @Override
    protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception
    {
        System.out.println("禁止并发执行");
        JobInvokeUtil.invokeMethod(sysJob);
    }
}
public class QuartzJobExecution extends AbstractQuartzJob
{
    @Override
    protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception
    {
        System.out.println("任务执行:" + sysJob.getJobName() + ",任务分组:" + sysJob.getJobGroup());
        JobInvokeUtil.invokeMethod(sysJob);
    }
}

@DisallowConcurrentExecution注解是quartz提供的,标识任务是否可以并发执行。在这里完成了第一步 Job 接口的实现。接下来就是 JobDetail 以及调度器和触发器相关的绑定。

com.ruoyi.quartz.service.impl.SysJobServiceImpl接口中,定义了如下代码:

@PostConstruct
public void init() throws SchedulerException, TaskException
{
    scheduler.clear();
    List<SysJob> jobList = jobMapper.selectJobAll();
    for (SysJob job : jobList)
    {
        ScheduleUtils.createScheduleJob(scheduler, job);
    }
}

在项目启动时,会执行此方法:

  • 首先查询 sys_job 表中的所有任务,
  • 循环查出来的结果,执行 ScheduleUtils.createScheduleJob(scheduler, job); 方法
/**
 * 创建定时任务
 */
public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException
{
    Class<? extends Job> jobClass = getQuartzJobClass(job);
    // 构建job信息
    Long jobId = job.getJobId();
    String jobGroup = job.getJobGroup();
    // 创建jobDetail
    JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();
​
    // 表达式调度构建器---调度器
    CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
    cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);
​
    // 按新的cronExpression表达式构建一个新的trigger---触发器
    CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
            .withSchedule(cronScheduleBuilder).build();
​
    // 放入参数,运行时的方法可以获取
    jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
​
    // 判断是否存在
    if (scheduler.checkExists(getJobKey(jobId, jobGroup)))
    {
        // 防止创建时存在数据问题 先移除,然后在执行创建操作
        scheduler.deleteJob(getJobKey(jobId, jobGroup));
    }
​
    // 判断任务是否过期
    if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression())))
    {
        // 执行调度任务
        scheduler.scheduleJob(jobDetail, trigger);
    }
​
    // 暂停任务
    if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue()))
    {
        scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
    }
}
​

以上便是若以中是如何实现定时任务的逻辑,除此之外,在新增和删除以及更新相关sys_job记录时,都有对应的操作。具体的请参看源码。


网站公告

今日签到

点亮在社区的每一天
去签到