Quartz整合SpringBoot实现非持久化多任务运行

news2024/11/15 12:29:56

简介

java后端入门新手,对知识内容理解较浅,如文章内容有误,请各位大佬多多指点。本篇文章适用于对quartz有一定了解的入门新手,且并没有采用quartz官方的持久化方式,是在结合工作需求的基础上完成的quartz任务调度的任务添加与修改,以及对时间表达式的修改等功能

任务调度框架Quartz基本概念如下:
Job/JobDetail: 任务
任务指的是你要做的事,也是任务调度的核心,比如我想要每隔一个小时喝一杯水,那么任务就是“喝水”
Trigger: 触发器
触发器指的是任务触发的时间,也就是任务在什么时间开始执行。例如上一句喝水的例子,“每隔一个小时”,这就是一个触发器,触发器具有两种定时类型,一种简单定时,一种使用Cron时间表达式进行复杂定时。

简单使用

一.传统SpringBoot配置使用

这个方法是使用了SpringBoot与Quartz的整合方式,并将任务和触发器用Bean组件的方式进行自动配置。

1.导入依赖

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

2.新建任务类

在SpringBoot2.x之前,使用Quartz建立任务类需要实现Job接口,重写excute()方法,而在SpringBoot中,一般是继承QuartzJobBean类,重写executeInternal() 方法,方法内就是任务具体执行的内容

@Slf4j
public class MyJob extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        log.info("MyJob开始运行啦!");

    }
}

3.新建配置类

需要创建一个Job,关联对应的实体类,每个Job和Trigger都可以设置分组,Job/Trigger有组group和名字name的联合唯一标识,在一个触发器中不允许有两个相同标识的Job/Trigger

  • Job和trigger都可以进行分组,相对应的任务和触发器可以分到同一组
  • 可以使用Trigger的group和name来获取对应的触发器
@Configuration
public class QuartzConfig {
    
    @Bean
    public JobDetail jobDetail(){

        return JobBuilder.newJob(MyJob.class)//加载任务实体(必须)
                .withIdentity("myJob","group1")//设置分组
                .storeDurably(true)//true会使任务在没有对应触发器的情况下一直存在
                .build();
    }

    @Bean
    public Trigger trigger(){
        //设置Cron时间表达式,此处也可用SimpleScheduleBuilder
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/30 0/1 * * * ?");
        //建立触发器
        return TriggerBuilder.newTrigger()
                .forJob(jobDetail())//与Job进行绑定
                .withIdentity("trigger","group1")//分组
                .withSchedule(cronScheduleBuilder)//绑定触发时间
                .build();//构建
    }
    
}

程序启动时会自动加载@Configration配置文件,以及被@Bean标记的方法,至此,任务调度启动。
程序启动时会实现以下内容,这是代表着Quartz的内容存储在内存中,没有进行持久化,并显示了线程数等信息

  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

二.复杂多任务调度

Gitee源码地址:quartz任务调度

1.简介

多任务调度自然不可能是并发,而是采用了多线程异步并行的方法来进行多个任务的调度。Quartz有自带的线程池,默认的核心线程数是10个,也就是说,最多支持10个任务的同时调度;当十个任务要进行切换时,线程池会释放空余的线程,并从第一个线程开始,循环往复。不过不建议在没有配置线程池的情况下运行10个任务,可能会造成线程堵塞的情况。

2.非默认持久化

本篇文章的多任务方法并没有使用默认的持久化方式,而是采用了自建表的方式来存储任务信息。如需要使用默认的持久化方式,请参考Quartz官方文档建表说明;如想简单使用自建表,主要需要如下内容:

3.创建数据表

每个任务类都继承于QuartzJobBean类,并重写对应的方法
总体需求:完成多个任务的定时调度,并且能够在数据表中添加任务和修改任务执行周期


任务类的创建需要与数据表中的数据互相对应,并自行配置任务类的结果
数据表内容如下:


其中实体类路径一定要存在对应的实体类,在表中添加任务之前一定要确保你想要添加的Job存在于你的程序中。

4.程序结构

程序结构如下:


其中,核心代码为控制层的两个类

(1)实体类:Job.java

import lombok.Data;

@Data
public class Job {

    private int id;   //id自增
    private String group;   //分组
    private String name;    //任务名
    private String className;  //任务类路径
    private String cron;  //时间表达式

}

2.任务类

MyJob/MyJob2
注意分开写。在数据库添加任务之前,记得建立对应的任务类。任务类中的executeInternal()方法中就是你的任务要执行的操作,这里以日志代替,进行了省略。

@Slf4j
public class MyJob extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        log.info("MyJob开始运行啦!");
		
    }
}


@Slf4j
public class MyJob2 extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        log.info("MyJob2开始运行啦!");
    }
}

3.核心类

QuartzJob.java
这个类是任务调度的核心类,主要包括四个方法,代码中会详细的介绍方法的作用

@Slf4j
@Component
public class QuartzJob {

    @Resource
    Scheduler scheduler;

    //这里是获取数据库表的数据Service
    @Resource
    JobService jobService;

    public void start() throws SchedulerException {
        scheduler.start();
    }

    @Scheduled(cron = "0 0/1 * * * ?")
    public void loadJob() throws Exception {
        //数据库遍历数据
        List<Job> list = jobService.findAllJob();

        for (Job job : list){

            //获取一个要修改的触发器的资料,身份,key
            TriggerKey triggerKey = new TriggerKey(job.getName(),job.getGroup());

            //根据key获取要更改的具体的CronTrigger触发器
            CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);

            //判定,如果没有对应的触发器,就建立任务和触发器
            if (null == cronTrigger){
                createJob(scheduler,job);
            }else {
                //如果已经有对应的触发器,那么就已存在任务,程序转入触发器Cron监测方法
                updateJob(job,job.getCron());
            }
        }
    }

    public void createJob(Scheduler scheduler,Job job) throws SchedulerException {

        Class<org.quartz.Job> clazz;
        try {
            //这就是调度器要执行的类
            clazz = (Class<org.quartz.Job>) Class.forName(job.getClassName());
        } catch (ClassNotFoundException e1) {
            throw new RuntimeException(e1);
        }

        //批量创建任务
        JobDetail jobDetail = JobBuilder.newJob(clazz)
                .withIdentity(job.getName(),job.getGroup())
                .build();
        //获取当前Cron时间
        String cron = job.getCron();
        //创建表达式,构建触发器
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron).withMisfireHandlingInstructionDoNothing();
		//创建触发器
        CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                .forJob(jobDetail)
                .withSchedule(cronScheduleBuilder)
                .withIdentity(job.getName(),job.getGroup())
                .build();
        //调度器整合任务与对应的触发器
        scheduler.scheduleJob(jobDetail,cronTrigger);
        log.info("当前job创建成功:{}",job.getName());

    }

    /**
     * 1.此方法会对触发器进行更新,主要更新Cron表达式
     * 2.此方法会判定当前触发器的时间较上一分钟是否存在修改
     * 3.只有判定存在修改时,才会对表达式进行修改
     *
     * @param job   任务实体类
     * @param time  修改后的Cron表达式
     * @throws Exception
     */
    public void updateJob(Job job,String time) throws Exception{

        //创建一个要修改的触发器的资料,身份
        TriggerKey triggerKey = new TriggerKey(job.getName(),job.getGroup());

        //获取要更改的具体的CronTrigger触发器
        CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        //获取当前时间
        String oldTime = cronTrigger.getCronExpression();
        if (!oldTime.equals(time)){
            //用修改后的时间更新触发器
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(time).withMisfireHandlingInstructionDoNothing();
            CronTrigger cronTrigger1 = TriggerBuilder.newTrigger()
                    .withIdentity(job.getName(),job.getGroup())
                    .withSchedule(cronScheduleBuilder)
                    .build();
            //调度器整合新的触发器
            scheduler.rescheduleJob(triggerKey,cronTrigger1);
            log.info("监听到修改,任务“{}”发生修改,修改前执行时间为:{} ,修改后执行时间为: {}",job.getName(),oldTime,time);
        }

    }

}

这里按照从底到外的顺序来说明,createJob/updateJob -> loadJob ->start
createJob(): 这里遍历任务,创建任务JobDetail以及对应的触发器Trigger
updateJob(): 这个方法用于更新触发器,当对时间表达式进行了修改时,用来更新对应的任务类的触发器。
loadJob (): 这个方法用于执行任务的创建和触发器的修改,方法执行时会开始检查对应的name和group,来查找是否建立了这个任务及其触发器。所以在数据表中添加任务时会发现队友对应的触发器,就会去新建任务以及触发器。如果已经存在,就会转入触发器的监听方法,只有当修改了时间表达式后,才会修改触发器。这个方法加入@Scheduled注解,让它能够在固定的时间运行一次,这里设置的是一分钟。
start(): 是任务调度的开启方法,里面启动了调度器,之所以单独分开写,是因为要确保调度器只启动一次,并且一直保持稳定运行


QuartzStart.java

@Slf4j
@Component
public class QuartzStart implements ApplicationListener<ApplicationEvent> {

    private static boolean loaded = false;

    @Resource
    QuartzJob quartzJob;

    //这个方法会在容器加载时执行一次
    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        if(applicationEvent instanceof ContextRefreshedEvent){
            if(!loaded){//避免多次执行
                loaded = true;
                //定时任务启动
                try {
                    
                    //第一遍加载全部任务
                    quartzJob.loadJob();
                    //全部任务都开始执行
                    quartzJob.start();
                    System.out.println("任务已经启动...啦啦啦啦啦啦啦");

                } catch (SchedulerException se) {
                    se.printStackTrace();
                } catch (Exception exception) {
                    exception.printStackTrace();
                }
            }

        }
    }
}

这个类的方法只会在服务器启动时加载执行一次,目的是加载第一遍任务以及启动任务调度

5.执行结果

刚开始会创建所有任务,并按照任务表中的Cron表达式执行,初始都为10s执行一次


在修改表达式后,任务二改为15秒执行一次


这里要进一步说明的是,在时间修改监听,也就是@Scheduled注解的方法,时间如果设为整点(每分钟执行),并且与任务的执行重合(每十秒,第六次为一个整分钟),由于修改触发器与任务类的执行是分开的两个线程,所以当时间重合时,即使已经修改了时间,但是任务类还是会按照修改前的时间执行一次,第二次会按照新的时间执行,为了避免这样的情况,建议合理安排时间。
本篇文章中对任务与时间的修改在数据库中完成,也可结合前端,封装为接口,动态修改数据库中的数据,从而实现对定时任务的控制
本文完。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/708124.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【交换排序】手撕八大排序之快速排序和冒泡排序(超级详细)

目录 &#x1f341;一.快速排序 &#x1f340;Ⅰ.Hoare法 &#x1f347;Ⅱ.挖坑法 &#x1f34b;1.递归版本 &#x1f34a;2.关于时间复杂度 &#x1f34e;3.快速排序的优化之三数取中法 &#x1f34c;4.非递归版本&#xff08;使用栈实现&#xff09; &#x1f350;5…

什么是文件存储、对象存储、块存储?

什么是文件存储 文件存储带有文件系统&#xff0c;主要是以文件的形式存放数据&#xff0c;能将所有的目录、文件形成一个有层次的树形结构来管理&#xff0c;通过“树”不断伸展的枝丫就能找到你需要的文件。存储协议主要是NFS、CIFS等&#xff0c;以统一命名空间的形式共享一…

docker安装rabbitMQ,JAVA连接进行生产和消费,压测

1.docker安装 docker安装以及部署_docker bu shuminio_春风与麋鹿的博客-CSDN博客 2.doker安装rabbitMQ 使用此命令获取镜像列表 docker search rabbitMq 使用此命令拉取镜像 docker pull docker.io/rabbitmq:3.8-management 这里要注意&#xff0c;默认rabbitmq镜像是…

剑指 Offer 47: 礼物的最大价值

这道题看的出来只能往右往下只能走4步。 应该是每次i或者j加1&#xff01;不是先j后i 用for循&#xff0c;由于横纵坐标是固定的值&#xff08;一条直线&#xff09;&#xff0c;所以i0&#xff0c;j所有情况这种组合可以全部计算出来&#xff0c;当i1时&#xff0c;j0这里也…

图像的IO操作

1.读取图像 cv.imread()2.显示图像 cv.imshow()3.保存图像 cv.imwrite()4.参考代码 import numpy as np import cv2 as cv import matplotlib.pyplot as plt# 1.读取图像&#xff1a;默认方式是彩色图 img cv.imread("yangmi.jpg") # 彩色图 gray_img cv.imre…

C语言进阶---程序的编译(预处理操作)+链接

1、程序的翻译环境和执行环境 在ANSI C的任何一种实现中&#xff0c;存在两个不同的环境。 第1种是翻译环境&#xff0c;在这个环境中源代码被转换为可执行的机器指令。 第2种是执行环境&#xff0c;它用于实际执行代码。 1、每个源文件单独经过编译器处理&#xff0c;或生成一…

ps htop 输出可读文件

需要安装sudo apt-get install aha echo q | ps auxf | aha --black --line-fix > psps.html echo q | htop | aha --black --line-fix > htop.html 使用浏览器打开

Python神经网络学习(七)--强化学习--使用神经网络

前言 前面说到了强化学习&#xff0c;但是仅仅是使用了一个表格&#xff0c;这也是属于强化学习的范畴了&#xff0c;毕竟强化学习属于在试错中学习的。 但是现在有一些问题&#xff0c;如果这个表格非常大呢&#xff1f;悬崖徒步仅仅是一个长12宽4&#xff0c;每个位置4个动…

制造业网络安全最佳实践

网络安全已成为生产部门的一个重要关注点&#xff0c;制造业网络安全现在是高管层的主要考虑因素。 在工业 4.0和物联网 ( IoT )出现的推动下&#xff0c;当今的互连工业系统提供了多种优势&#xff0c;但也使组织面临新的风险和漏洞。 让我们探讨一些制造业网络安全最佳实践…

SOLIDWORKS软件有哪些版本?

SOLIDWORKS软件是基于Windows开发的三维 CAD系统&#xff0c;技术创新符合CAD技术的发展潮流和趋势&#xff0c;SOLIDWORKS每年都有数十乃至数百项的技术创新&#xff0c;公司也获得了很多荣誉。该系统在1995-1999年获得全球微机平台CAD系统评比NO1&#xff1b;从1995年至今&am…

Quiz 15: Object-Oriented Programming | Python for Everybody 配套练习_解题记录

文章目录 Python for Everybody课程简介 Quiz 15: Object-Oriented Programming单选题&#xff08;1-11&#xff09;Multiple instances Python for Everybody 课程简介 Python for Everybody 零基础程序设计&#xff08;Python 入门&#xff09; This course aims to teach e…

qemu 源码编译 qemu-system-aarch64 的方法

前言 最近调试 RT-Thread bsp qemu-virt64-aarch64 时&#xff0c;遇到无法使用网络设备问题&#xff0c;最后确认是 当前 ubuntu 20.04 系统 使用 apt install 安装的 qemu qemu-system-aarch64 版本太低。 RT-Thread qemu-virt64-aarch64 里面的网络设备&#xff0c;需要较新…

回顾分类决策树相关知识并利用python实现

大家好&#xff0c;我是带我去滑雪&#xff01; 决策树&#xff08;Decision Tree&#xff09;是一种基本的分类与回归方法&#xff0c;呈树形结构&#xff0c;在分类问题中&#xff0c;表示预计特征对实例进行分类的过程。它可以认为是if-then规则的集合&#xff0c;也可以认为…

多表-DDL以及DQL

多表DDL 个表之间也可能存在关系 存在在一对多和多对多和一对多的关系 一对多&#xff08;外键&#xff09; 在子表建一哥字段&#xff08;列&#xff09;和对应父表关联 父表是一&#xff0c;对应子表的多&#xff08;一个部门对应多个员工&#xff0c;但一个员工只能归属一…

结构体和数据结构--从基本数据类型到抽象数据类型、结构体的定义

在冯-诺依曼体系结构中&#xff0c;程序代码和数据都是以二进制存储的&#xff0c;因此对计算机系统和硬件本身而言&#xff0c;数据类型的概念其实是不存在的。 在高级语言中&#xff0c;为了有效的组织数据&#xff0c;规范数据的使用&#xff0c;提高程序的可读性&#xff0…

使用Streamlit和Matplotlib创建交互式折线图

大家好&#xff0c;本文将介绍使用Streamlit和Matplotlib创建一个用户友好的数据可视化Web应用程序。该应用程序允许上传CSV文件&#xff0c;并为任何选定列生成折线图。 构建Streamlit应用程序 在本文中&#xff0c;我们将指导完成创建此应用程序的步骤。无论你是专家还是刚刚…

three.js利用点材质打造星空

最终效果如图&#xff1a; 一、THREE.BufferGeometry介绍 这里只是做个简单的介绍&#xff0c;详细的介绍大家可以看看THREE.BufferGeometry及其属性介绍 THREE.BufferGeometry是Three.js中的一个重要的类&#xff0c;用于管理和操作几何图形数据。它是对THREE.Geometry的一…

leetcode 226. 翻转二叉树

2023.7.1 这题依旧可以用层序遍历的思路来做。 在层序遍历的代码上将所有节点的左右节点进行互换即可实现二叉树的反转。 下面上代码&#xff1a; class Solution { public:TreeNode* invertTree(TreeNode* root) {queue<TreeNode*> que;if(root nullptr) return{};que…

gradio库中的Dropdown模块:创建交互式下拉菜单

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

2020年全国硕士研究生入学统一考试管理类专业学位联考逻辑试题——纯享题目版

&#x1f3e0;个人主页&#xff1a;fo安方的博客✨ &#x1f482;个人简历&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;考取过HCIE Cloud Computing、CCIE Security、CISP等证书。&#x1f433; &#x1f495;兴趣爱好&#xff1a;b站天天刷&#xff0c;题目常常看&a…