记一次ruoyi中使用Quartz实现定时任务

一、首先了解一下Quartz

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。

二、Quartz的三大核心组件

调度器:Scheduler。
****任务:JobDetail。
**触发器:**Trigger,包括 SimpleTrigger 和 CronTrigger。
(1)**Job(任务):**是一个接口,有一个方法 void execute(JobExecutionContext context) ,可以通过实现该接口来定义需要执行的任务(具体的逻辑代码)。

JobDetail:Quartz每次执行Job时,都重新创建一个Job实例,会接收一个Job实现类,以便运行的时候通过newInstance()的反射调用机制去实例化Job。JobDetail是用来描述Job实现类以及相关静态信息,比如任务在scheduler中的组名等信息。

(2)Trigger(触发器):描述触发Job执行的时间触发规则实现类SimpleTrigger和CronTrigger可以通过crom表达式定义出各种复杂的调度方案。

Calendar:是一些日历特定时间的集合。一个Trigger可以和多个 calendar关联,比如每周一早上10:00执行任务,法定假日不执行,则可以通过calendar进行定点排除。

(3)Scheduler(调度器):代表一个Quartz的独立运行容器。Trigger和JobDetail可以注册到Scheduler中。Scheduler可以将Trigger绑定到某一JobDetail上,这样当Trigger被触发时,对应的Job就会执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。
在这里插入图片描述

三、简单实现

引入依赖:

<!-- SpringBoot 整合 Quartz 定时任务 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
    <version>2.3.5.RELEASE</version>
</dependency>
package com.pjb.job;
 
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.quartz.QuartzJobBean;
 
import java.text.SimpleDateFormat;
import java.util.Date;
 
/**
 * 同步用户信息Job
 * @author pan_junbiao
 **/
public class SyncUserJob extends QuartzJobBean
{
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext)
    {
        //获取JobDetail中传递的参数
        String userName = (String) jobExecutionContext.getJobDetail().getJobDataMap().get("userName");
        String blogUrl = (String) jobExecutionContext.getJobDetail().getJobDataMap().get("blogUrl");
        String blogRemark = (String) jobExecutionContext.getJobDetail().getJobDataMap().get("blogRemark");
 
        //获取当前时间
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
        //打印信息
        System.out.println("用户名称:" + userName);
        System.out.println("博客地址:" + blogUrl);
        System.out.println("博客信息:" + blogRemark);
        System.out.println("当前时间:" + dateFormat.format(date));
        System.out.println("----------------------------------------");
    }
}
package com.pjb.config;
 
import com.pjb.job.SyncUserJob;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
/**
 * Quartz定时任务配置类
 * @author pan_junbiao
 **/
@Configuration
public class QuartzConfig
{
    private static String JOB_GROUP_NAME = "PJB_JOBGROUP_NAME";
    private static String TRIGGER_GROUP_NAME = "PJB_TRIGGERGROUP_NAME";
 
    /**
     * 定时任务1:
     * 同步用户信息Job(任务详情)
     */
    @Bean
    public JobDetail syncUserJobDetail()
    {
        JobDetail jobDetail = JobBuilder.newJob(SyncUserJob.class)
                .withIdentity("syncUserJobDetail",JOB_GROUP_NAME)
                .usingJobData("userName", "pan_junbiao的博客") //设置参数(键值对)
                .usingJobData("blogUrl","https://blog.csdn.net/pan_junbiao")
                .usingJobData("blogRemark","您好,欢迎访问 pan_junbiao的博客")
                .storeDurably() //即使没有Trigger关联时,也不需要删除该JobDetail
                .build();
        return jobDetail;
    }
 
    /**
     * 定时任务1:
     * 同步用户信息Job(触发器)
     */
    @Bean
    public Trigger syncUserJobTrigger()
    {
        //每隔5秒执行一次
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
 
        //创建触发器
        Trigger trigger = TriggerBuilder.newTrigger()
                .forJob(syncUserJobDetail())//关联上述的JobDetail
                .withIdentity("syncUserJobTrigger",TRIGGER_GROUP_NAME)//给Trigger起个名字
                .withSchedule(cronScheduleBuilder)
                .build();
        return trigger;
    }
}

执行结果:
在这里插入图片描述

四、在若依中实现

在这里插入图片描述
在这里插入图片描述
对应后台代码:
controller:

 /**
     * 新增保存调度
     */
    @Log(title = "定时任务", businessType = BusinessType.INSERT)
    @RequiresPermissions("monitor:job:add")
    @PostMapping("/add")
    @ResponseBody
    public AjaxResult addSave(@Validated SysJob job) throws SchedulerException, TaskException
    {
        if (!CronUtils.isValid(job.getCronExpression()))
        {
            return error("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确");
        }
        else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用");
        }
        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS }))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用");
        }
        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用");
        }
        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规");
        }
        else if (!ScheduleUtils.whiteList(job.getInvokeTarget()))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
        }
        job.setCreateBy(getLoginName());
        return toAjax(jobService.insertJob(job));
    }

对应实现;

  /**
     * 新增任务
     * 
     * @param job 调度信息 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertJob(SysJob job) throws SchedulerException, TaskException
    {
        job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
        int rows = jobMapper.insertJob(job);
        if (rows > 0)
        {
            ScheduleUtils.createScheduleJob(scheduler, job);
        }
        return rows;
    }

具体看这个方法:
ScheduleUtils.createScheduleJob(scheduler, job);
在这里插入图片描述
具体进入getQuartzJobClass方法:
在这里插入图片描述
这里分了两个类,一个是可以异步执行,另一个是不可以异步执行(也就是同一个job对象,不能同时进行,需要等待,一般不会这么用);
其实两个类基本一样,都是继承了AbstractQuartzJob类(这个类实现了Job,指向具体干什么,也就是实现Job的doExecute()方法),不同之处就是禁止并发使用了@DisallowConcurrentExecution注解;

在这里插入图片描述
这个invokeMethod()方法不说了,就是用反射,执行我们指定的类、方法;

整体流程就是这么简单,具体的暂停、激活,使用scheduler类中的方法,结合我们定义的jobKey去操作,类似于下面:
scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));

五、程序启动时加载数据库中的定时任务

就是使用@PostConstruct,在springboot启动后立刻执行方法:

 /**
     * 项目启动时,初始化定时器 
     * 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据)
     */
    @PostConstruct
    public void init() throws SchedulerException, TaskException
    {
        scheduler.clear();
        List<SysJob> jobList = jobMapper.selectJobAll();
        for (SysJob job : jobList)
        {
            ScheduleUtils.createScheduleJob(scheduler, job);
        }
    }

引入别人的图片:
在这里插入图片描述

### Ruoyi 框架 SnailJob 定时任务使用方法 #### 一、简介 SnailJob 是 RuoYi 框架中的一个定时任务调度组件,用于管理和执行周期性的业务逻辑。通过该模块可以方便地创建、管理以及监控各种类型的后台作业。 #### 二、环境准备 为了能够顺利集成并使用 SnailJob,在项目启动前需确认以下几点: - 已经成功搭建好基于 Spring Boot 的开发环境; - 配置好了 Nacos 或其他注册中心服务作为微服务体系的基础架构支持[^4]; 对于想要替换默认数据库的同学来说,则需要注意数据源 URL 的正确性等问题。 #### 三、基本配置 在 application.yml 文件内添加如下配置项以开启 Quartz 调度器的支持: ```yaml spring: quartz: job-store-type: jdbc # 设置存储方式为jdbc持久化 wait-time-in-millis-to-delete-orphaned-executions-on-startup: 30_000L ``` 接着定义具体的 Job 类实现 `org.quartz.Job` 接口,并重写 execute 方法完成自定义的任务处理逻辑。 #### 四、编写定时任务类 下面给出一段简单的例子展示如何在一个名为 MyTask.java 的文件里声明一个新的定时任务: ```java import org.quartz.DisallowConcurrentExecution; import org.quartz.PersistJobDataAfterExecution; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; @DisallowConcurrentExecution // 不允许并发执行同一个实例 @PersistJobDataAfterExecution // 执行完毕后保存状态到数据库中 public class MyTask implements org.quartz.Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("This is my custom task."); try{ Thread.sleep(5 * 1000); // Simulate a long-running operation. }catch (InterruptedException e){ throw new RuntimeException(e); } } } ``` 注意:如果希望某个特定的定时任务具有事务特性的话,请谨慎考虑是否真的有必要这样做,因为这可能会带来意想不到的问题[^1]。 #### 五、注册与触发条件设定 最后一步就是告诉系统何时何地应该运行这些已经编写的 Jobs 。可以通过修改 ruoyi-job 下 resources 目录下的 scheduler.properties 来达到目的 : ```properties # cron 表达式样例,每分钟执行一次 my.task.cron=0 */1 * ? * ``` 以上即完成了整个流程介绍,现在就可以尝试部署应用程序观察效果啦!
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值