Java开发 - Quartz初体验

news2024/10/5 21:05:57

前言

在上一篇博客中,我们对单点登录有了初步了解,这也让我们独立做系统有了最基础的保障。但在业务开发中,总是会出现一些定期处理的任务,我们首先想到的是Timer,但由于其调度功能单一,我们实际并不会用它来做项目中的任务调度。今天我们就针对定期任务来说说Quartz这个时间调度工具和它的具体用法。

Quartz

什么是Quartz

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Quartz 允许程序开发人员根据时间的间隔来调度作业,它实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。听起来好可怕,功能也太强大了。但从字里行间我们还是读出来,它主要是用于延时的。

Quartz作用

上面我们说,Quartz主要用于延时,这么说没错,也是其最本质的功能,但它还有一个强大的功能:任务调度。因为它并不是只针对某一个任务,而是针对成千上万的任务,就像一个强的控制中心,我们称之为调度。

我们知道,城市的公交地铁是有调度中心的,他们都是按照某些固定的规律来运行,比如间隔多久发一趟车,更高级的会有动态调度,这恐怕超过了我们今天要说的范畴。

之所以不使用Timer而使用Quartz,是因为Quartz有着更加便捷的时间指定方式。比如,每月1号执行一个任务,可以想想Timer要怎么做?是不是要计算每月的天数,往大了还有平年闰年,而Quartz则不用这么麻烦,两相比较,Quartz成了最好的选择。

Quartz结构

在继续讲下去之前,我们要先知道Quartz的几个核心概念,了解了这几个组件,有助于我们更好地理解Quartz的工作原理。

  1. Job 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:
    void execute(JobExecutionContext context) 

    一般是创建一个类,继承此接口,重写内部方法来执行一些具体的任务。

  2. JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。通俗的讲,Quartz每次执行job时,job类型对象会被实例化,上面的方法会被调用,JobDetail则用来描述其静态信息,我们会在下面的案例中进行使用。
  3. Trigger 代表一个调度参数的配置,表示什么时候去调。简单触发可以使用SimplTrigger实现类,其功能类似timer。复杂触发可以使用CronTrigger实现类,其内部利用cron表达式描述各种复杂的时间调度计划,这个用的比较多。
  4. Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler容器调度了。SpringBoot框架下,添加Quartz依赖后,调度器将由SpringBoot管理,开发者不需要编写其代码,Spring Boot再次为我们简化了步骤,让我们为其鼓掌,还有谁不会使用Spring Boot的,欢迎查看博主文章:Java开发 - 问君能有几多愁,Spring Boot瞅一瞅

看到这里,Quartz我们就了解了大半了,听起来很像移动端的通知,或者叫广播,也很像nacos的注册服务,总之服务一定要注册才能被统一管理。你会发现,统一管理的理念就是一定要知道注册方和调用方是谁才能正常的工作,否则就全乱套了。
 

表达式

序号时间单位必填/选填值范围可选通配符
1必填0~59,-*/
2必填0~59,-*/
3必填0~23,-*/
4必填1~31,-*/?LW
5必填1~12/JAN~DEC,-*/
6必填1~7/SUN~SAT,-*/?L#
7选填empty/1970~2099,-*/
  • ,是分割符,假如我想在每分钟里的20s和40s分别触发一次,就可以这么写:20,40
  • -表示一个区间,以秒为例,1-5表示在1,2,3,4,5秒都触发
  • *表示任何值,在秒那里写就代表每秒都触发,在分那里写就代表每分都触发,依此类推 
  • /表示递增触发,以秒为例,1/20表示从第1秒开始每20秒触发一次,在其他单位上依此类推
  • ?表示不确定值,比如我们在确定了具体日期后,就不确定是周几,还要去算,可以用?代替,反之亦然
  • L表示最后,设置当月的最后一天就在日字段用L表示,周字段使用L就表示最后一周,一般会和1-7的数字组合,2L就是每月最后一周的星期二
  • W表示离某一天最近的工作日(工作日:周一~周五),只能用在日上面,10W则表示离10号最近的工作日,10号是工作日就10号触发,是周六就周五触发,周日就周一触发,LW通常一起使用,表示每月最后一天工作日,有些公司是每月最后一个工作日发工资,系统内就可以用这个通配符
  • #表示第几个,只能用在周字段,用来写父亲节,母亲节这些第几个周几,最合适不过。7#2表示每月的第二个周日,当然,要写古琴姐母亲节还要指定固定月份,我们稍后可以写写看,需要注意的是,#后面的数字写大了,超出了当月最大天数就不会执行了

案例

双十一写法:0 0 0 11 11 ?

每月最后一个工作日写法:0 0 0 LW * ?

父亲节写法:0 0 0 * 6 7#3  父亲节是每年6月的第3个星期日,看看解析结果:

不确定对不对的小伙伴可以去对日历看看,哈哈。

国庆节写法:0 0 0 1 10 * 

写的有点停不下来,真是太有意思了。大家可以自己写写看,从左到右要和:秒,分,时,日,月,周,年一一对应,最后一个年可以不写,如果是每年的话,你就是加个*也是可以的,因为指定具体年份貌似没有意义,一般都是当年,你不可能今年安排明年的活动,即使年底12月,预约下一年12月前的日期,也不需要写年份如果你当年11月预约下年12月的活动,那就需要写具体年份,但你不觉得这样很不礼貌吗?

反解析Cron工具地址给大家贴一个:Cron - 在线Cron表达式生成器

类似的工具还有很多,根据自己喜好选择即可。 

Quartz实战

下面,我们要在Spring Boot项目中来进行接入,就以我们前文中微服务项目为模版,新朋友没有一跟着做前面教程的话也可单独建项目来接入,并不会有什么影响。

添加依赖

我们选择在cloud-stock子工程下引入Quartz依赖:

<!--  SpringBoot Quartz依赖  -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

单独建项目的童鞋根据后续使用需要再自行引入其他的依赖,后续不再针对依赖引入做额外说明。 

创建Job类

在stock子项目下创建quartz包,包下创建QuartzJob类:

以后默认子项目下创建包都是在主包下,启动文件同级别目录创建,万万不要创建在src之外。在其他包下创建会说明的,之前就出现有童鞋创建错位置的情况,作为一名Java开发工程师,这个是 不应该的。

看看这个类中的代码:

package com.codingfire.cloud.stock.quartz;

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

import java.time.LocalDateTime;

public class QuartzJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        //输出当前时间
        System.out.println("--------------"+ LocalDateTime.now() +"---------------");
    }
}

暂不做任何处置,只是重写了前面提到的 execute方法,因为Job会执行此方法,重写可以做一些其他操作,此处暂时只输出当前时间。

创建配置类

在quartz包下,继续创建一个QuartzConfig类,这是一个SpringBoot的配置类,也是Quartz调度器的配置类,其代码的编写格式基本是固定的,以后需要使用时可直接套用此格式,下面我们写个每10秒触发一次输出当前日期的任务:

package com.codingfire.cloud.stock.quartz;

import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// 这个配置类就是在配置已经保存在Spring容器中的调度器Scheduler
// 我们需要按下面格式进行配置,才能让Scheduler正常工作
@Configuration
public class QuartzConfig {
    // 创建一个JobDetail(工作详情)类对象,保存到Spring容器中,这个类用于封装我们编写的job接口实现类
    // @Bean注解标记的方法的返回值会自动保存到Spring容器中(方法名随意)
    @Bean
    public JobDetail showTime(){
        System.out.println("showTime方法运行");
        return JobBuilder.newJob(QuartzJob.class)   // 绑定要运行的任务类的反射
                .withIdentity("date")               // 设置这个job的名称
                .storeDurably()                     //
                .build();
    }

    // 下面要声明触发器,Trigger,触发器决定我们的工作/任务何时触发
    @Bean
    public Trigger showTimeTrigger(){
        System.out.println("showTime触发器运行");
        // 定义Cron表达式   每10秒触发一次的定义
        CronScheduleBuilder cronScheduleBuilder=
                CronScheduleBuilder.cronSchedule("0/10 * * * * ?");
        return TriggerBuilder.newTrigger()
                .forJob(showTime())        // 绑定JobDetail JobDetail对象已经在Spring容器中
                .withIdentity("dateTrigger")       // 定义触发器名称
                .withSchedule(cronScheduleBuilder) // 绑定Cron表达式
                .build();
    }

}

测试代码

测试前,如果你实在微服务项目中跟着博主一起操作的,nacos和seata需要先启动,否则会报错,我们的配置文件有配置,若你是新建的项目,则不需要启动这两个服务,只需要确保你的项目能运行起来即可。

启动后,理论上每隔10s会输出一次当前系统时间,我们看看控制台有没有输出:

可以看到是每10s输出一次,你也可以在此处做一些其他的操作,都随你。

storeDurably 需要特别说明下,设置后,如果没有触发器指向此JobDetail,JobDetail也不会被从Spring容器内删除,否则,就会自动从Spring容器内删除。

扩展练习

其实在这个任务中,我们更希望能看到它去调用微服务其他模块的东西,这才是任务调度的真实使用场景,而不是去输出一些东西。下面,我们来尝试调用业务模块来减少库存。

在quartz包下新建一个job类:

package com.codingfire.cloud.stock.quartz;

import com.codingfire.cloud.commons.pojo.stock.dto.StockReduceCountDTO;
import com.codingfire.cloud.stock.service.IStockService;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;

public class StockJob implements Job {

    @Autowired
    private IStockService iStockService;

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        //库存-10
        StockReduceCountDTO stockReduceCountDTO = new StockReduceCountDTO();
        stockReduceCountDTO.setCommodityCode("PC100");
        stockReduceCountDTO.setReduceCount(-10);
        iStockService.reduceCommodityCount(stockReduceCountDTO);
    }
}

这就是Spring容器的好处,我们直接 @Autowired就可以拿到调用接口,我们在controller里面也是这么用的,在任务调度中也可以当作controller来用,是不是很棒。

这里可以新建一个config类,但我们选择写在输出时间的配置类里,方便大家和前面的代码做对比,添加如下代码:

    @Bean
    public JobDetail addStock(){
        return JobBuilder.newJob(StockJob.class)
                .withIdentity("addStock")
                .storeDurably()
                .build();
    }
    @Bean
    public Trigger addStockTrigger(){
        //从0开始,每1分钟运行一次
        CronScheduleBuilder cronScheduleBuilder=
                CronScheduleBuilder.cronSchedule("0 0/1 * * * ?");
        return TriggerBuilder.newTrigger()
                .forJob(addStock())
                .withIdentity("addStockTrigger")
                .withSchedule(cronScheduleBuilder)
                .build();
    }

几乎和上面的代码是一样的,只是改了一点参数。那么到这里练习就结束了,如果需要用到Dubbo的,和微服务里面一样的,直接用就行,代码就不再贴了,大家可以自己试试。

结语

赶紧关掉这些服务,博主的电脑已经吼起来了。Quart使用起来还是很简单的,也很实用,在项目中使用也很频繁,比如在csdn平台,每周都会有一周小结,都会有一些固定的服务,这些都是通过任务调度自动完成的。看到这里,你对Quart了解了吗,会用了吗?有没有自己上手?咱们下一篇内容再见。

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

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

相关文章

sqlmap工具说明

目录 1 工具简介 3 1.1 简述 3 1.2 背景及需求 3 1.3 主要功能 3 2 功能确认 4 2.1 安装和使用 4 2.1.1 Windows操作系统 4 2.1.2 Linux操作系统 6 2.1.3 Kali 7 2.2 添加目标 7 2.2.1 参数-u 7 2.2.2 参数-m 8 2.2.3 参数-r 9 2.3 指定参数 11 2.4 爆破数据库 11 2.5 爆破表 …

【LeetCode】剑指 Offer(16)

目录 题目&#xff1a;剑指 Offer 33. 二叉搜索树的后序遍历序列 - 力扣&#xff08;Leetcode&#xff09; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 写在最后&#xff1a; 题目&#xff1a;剑指 Offer …

Uipath DataTable-Build Data Table(构建数据表)

Build Data Table(构建数据表) 活动描述 Build Data Table(构建数据表)&#xff1a;通过在“构建数据表向导”窗口可以构建数据表。 使用如下图&#xff1a; Build Data Table(构建数据表)属性配置 属性 作用 DisplayName 在设计器面板中设置活动显示的名称 Private 如…

List、Set、Map众多集合框架等你来学,让我们一起精进Java框架的知识点吧

文章目录一、各集合特性二、HashMap深入解析三、遍历集合元素的若干方式一、各集合特性 1. ArrayList特性 不唯一&#xff0c;有序&#xff1a;实现了List接口&#xff0c;该接口是序列&#xff0c;所以不唯一且按顺序保存不同步&#xff1a;因为ArrayList.add&#xff08;&am…

Java 编写Vue组件(VueGWT的初尝试)

在之前&#xff0c;我曾写过这样的文章《不会前端没事&#xff0c;用GWT Boot和Spring Boot构建Web程序》&#xff0c;这篇文字使用的Domino UI来做前端页面&#xff0c;由于现在更流行VUE&#xff0c;并且VUE的页面更具现代化&#xff0c;所以我尝试了一下VueGWT。 VueGWT 有…

Nodejs运行vue项目时,报错:Error: error:0308010C:digital envelope routines::unsupported

前言 前端项目使用( npm run dev ) 运行vue项目时&#xff0c;出现错误&#xff1a;Error: error:0308010C:digital envelope routines::unsupported 经过探索&#xff0c;发现问题所在&#xff0c;主要是nodeJs V17版本发布了OpenSSL3.0对算法和秘钥大小增加了更为严格的限制…

xFormers安装使用

xFormers是一个模块化和可编程的Transformer建模库&#xff0c;可以加速图像的生成。 这种优化仅适用于nvidia gpus&#xff0c;它加快了图像生成&#xff0c;并降低了vram的使用量&#xff0c;而成本产生了非确定性的结果。 下载地址&#xff1a; https://github.com/faceb…

C++ | 你真的了解namespace吗?

文章目录一、前言二、命名冲突三、命名空间1、域作用限定符2、命名空间的概念&#x1f449;示例1&#x1f449;示例23、命名空间的定义4、命名空间的使用① 指定命名空间访问【做项目】② 使用using部分展开【做项目】③ 使用using namespace全局展开【日常练习】5、小结解答&a…

【经验分享】使用了6年的实时操作系统,是时候梳理一下它的知识点了 | 文末赠书4本

使用了6年的实时操作系统&#xff0c;是时候梳理一下它的知识点了 摘要&#xff1a; 本文简单介绍了博主学习操作系统的心路历程&#xff0c;同时还给大家总结了一下当下流行的几种实时操作系统&#xff0c;以及在工程中OSAL应该如何设计。希望对大家有所启发和帮助。 文章目录…

【开放域目标检测】一:Open-Vocabulary Object Detection Using Captions论文讲解

出发点是制定一种更加通用的目标检测问题&#xff0c;目的是借助于大量的image-caption数据来覆盖更多的object concept&#xff0c;使得object detection不再受限于带标注数据的少数类别&#xff0c;从而实现更加泛化的object detection&#xff0c;识别出更多novel的物体类别…

【数据结构】顺序表:尾部操作我很行,随机访问我超快!!!

顺序表的模拟实现 文章目录顺序表的模拟实现1.线性表2.顺序表2.1概念结构2.2顺序表的模拟实现2.2.1顺序表的初始化2.2.2顺序表的销毁2.2.3尾插数据2.2.4尾删数据2.2.5头插数据2.2.6头删数据2.2.7中间插入数据2.2.8中间删除数据2.2.9打印顺序表2.2.10查找数据2.2.11复用Insert和…

Linux学习第二十一节-sudo提权

1.概念 管理员提前为用户设置执行权限许可&#xff1b; 被授权用户有权执行授权命令&#xff1b; 配置文件&#xff1a;/etc/sudoers&#xff1b; 命令格式&#xff1a;sudo 特权命令。 2.提权操作 ①方式一vim编辑配置文件后wq&#xff01;&#xff1a;#vim /etc/sudo…

pnpm 基本详细使用(安装、卸载、使用)

一、简介 官网地址、GitHub地址、官方安装文档、官方卸载文档。 pnpm 全称 performant npm&#xff0c;意思为 高性能的 npm。pnpm 由 npm/yarn 衍生而来&#xff0c;解决了 npm/yarn 内部潜在的 bug&#xff0c;极大的优化了性能&#xff0c;扩展了使用场景。被誉为 最先进的…

【Docker】之docker-compose的介绍与命令的使用

&#x1f341;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; 文章目录docker-compose简介docker-compose基础…

Word文档的密码忘记了怎么办?

Word文档可以设置两种密码&#xff0c;文件的“限制密码”和“打开密码”&#xff0c;今天来分享一下忘记这两种密码可以如何处理。 如果忘记的是Word文档的“限制密码”&#xff0c;文档就无法编辑及更改了&#xff0c;菜单目录中的相关选项也都是灰色状态&#xff0c;无法点…

LeetCode-343. 整数拆分

目录递归动态规划题目来源 343. 整数拆分 递归 对于给定的一个整数 n&#xff0c;穷举它的每一种分解情况&#xff0c;然后对所有情况&#xff0c;求最大值。 并且我们知道&#xff0c;n 可以拆成如下情况&#xff1a; 通过上图&#xff0c;我们很容易得到一个递归表达式&am…

Verilog使用always块实现时序逻辑

这篇文章将讨论 verilog 中一个重要的结构---- always 块&#xff08;always block&#xff09;。verilog 中可以实现的数字电路主要分为两类----组合逻辑电路和时序逻辑电路。与组合逻辑电路相反&#xff0c;时序电路电路使用时钟并一定需要触发器等存储元件。因此&#xff0c…

用Chrome浏览器加入新必应候补名单,微软最终还是无法赢得一切(~ ̄▽ ̄)~

前言 ChatGPT最近太火了&#xff0c;国内厂商刚开始宣布跟进时&#xff0c;微软必应搜索直接接入了ChatGPT的能力&#xff0c;推出了新必应。此举给Google当头一棒&#xff0c;相比于传统搜索引擎&#xff0c;新必应的这种搜索方式&#xff0c;让用户更快速的获取到自己问题的…

02 C语言计算

02 C语言计算 0、编程练习题 #include<stdio.h>int main(int argc,char const *argv[]){int a,b;scanf("%d %d",&a,&b);printf("%d %d %d\n",a,b,a b);printf("%d - %d %d\n",a,b,a - b);printf("%d * %d %d\n",a…

C/C++每日一练(20230305)

目录 1. 整数分解 ☆ 2. 二叉树的最小深度 ★★ 3. 找x ★★ 1. 整数分解 输入一个正整数&#xff0c;将其按7进制位分解为各乘式的累加和。 示例 1&#xff1a; 输入&#xff1a;49 输出&#xff1a;497^2示例 2&#xff1a; 输入&#xff1a;720 输出&#xff1a;720…