Java开发 - Quartz初体验
前言
在上一篇博客中,我们对单点登录有了初步了解,这也让我们独立做系统有了最基础的保障。但在业务开发中,总是会出现一些定期处理的任务,我们首先想到的是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的工作原理。
- Job 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:
void execute(JobExecutionContext context)一般是创建一个类,继承此接口,重写内部方法来执行一些具体的任务。
- JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。通俗的讲,Quartz每次执行job时,job类型对象会被实例化,上面的方法会被调用,JobDetail则用来描述其静态信息,我们会在下面的案例中进行使用。
- Trigger 代表一个调度参数的配置,表示什么时候去调。简单触发可以使用SimplTrigger实现类,其功能类似timer。复杂触发可以使用CronTrigger实现类,其内部利用cron表达式描述各种复杂的时间调度计划,这个用的比较多。
- 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 {@Overridepublic 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容器中(方法名随意)@Beanpublic JobDetail showTime(){System.out.println("showTime方法运行");return JobBuilder.newJob(QuartzJob.class) // 绑定要运行的任务类的反射.withIdentity("date") // 设置这个job的名称.storeDurably() //.build();}// 下面要声明触发器,Trigger,触发器决定我们的工作/任务何时触发@Beanpublic 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 {@Autowiredprivate IStockService iStockService;@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {//库存-10StockReduceCountDTO stockReduceCountDTO = new StockReduceCountDTO();stockReduceCountDTO.setCommodityCode("PC100");stockReduceCountDTO.setReduceCount(-10);iStockService.reduceCommodityCount(stockReduceCountDTO);}
}
这就是Spring容器的好处,我们直接 @Autowired就可以拿到调用接口,我们在controller里面也是这么用的,在任务调度中也可以当作controller来用,是不是很棒。
这里可以新建一个config类,但我们选择写在输出时间的配置类里,方便大家和前面的代码做对比,添加如下代码:
@Beanpublic JobDetail addStock(){return JobBuilder.newJob(StockJob.class).withIdentity("addStock").storeDurably().build();}@Beanpublic 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了解了吗,会用了吗?有没有自己上手?咱们下一篇内容再见。
相关文章:
Java开发 - Quartz初体验
前言 在上一篇博客中,我们对单点登录有了初步了解,这也让我们独立做系统有了最基础的保障。但在业务开发中,总是会出现一些定期处理的任务,我们首先想到的是Timer,但由于其调度功能单一,我们实际并不会用它…...
无头盔开发vr XR Device Simulator操作(更新)
1.摄像机(未开启TY键) 平移 上下左右:右键鼠标,移哪去哪 前后:右键快速滚动鼠标滚轮 旋转 XOY平面旋转:右键按住鼠标滚轮滚动鼠标滚轮 XOZ\YOZ平面旋转:右键按住鼠标滚轮移动鼠标 2.左手右手&am…...
《C++代码分析》第二回:函数重载const char* ,char*,const char[],char[]汇编代码上的区别
一、前言 C相比C是支持函数重载的,现在我们详细探讨一下C函数重载与类方法承载。 二、案例代码 我们编译如下代码,同样的我们关闭代码优化,删除符号链接文(.pdb) #include "windows.h" #include "w…...
【学习笔记】深入理解JVM之垃圾回收机制
【学习笔记】深入理解JVM之垃圾回收机制 更多文章首发地址:地址 参考: 《深入理解JAVA虚拟机》第三版 第三章尚硅谷 第134 - 203 集参考文章:https://blog.csdn.net/qq_48435252/article/details/123697193 1、概念 🌻 首先我们…...
49.在ROS中实现local planner(2)- 实现Purepersuit(纯跟踪)算法
48.在ROS中实现local planner(1)- 实现一个可以用的模板实现了一个模板,接下来我们将实现一个简单的纯跟踪控制,也就是沿着固定的路径运动,全局规划已经规划出路径点,基于该路径输出相应的控制速度 1. Pur…...
Allegro如何设通孔Pin和Via的消盘操作指导
Allegro如何设通孔Pin和Via的消盘操作指导 用Allegro做PCB设计的时候,除了可以在光绘设置里面设置内层通孔Pin和Via的消盘,在设计过程中,同样也可以设置消盘效果,以便实时显示,如下图 如何设置,具体操作如下 点击Setup点击Unused Pads Suppression...
Android工厂模式
工厂模式分为三种 :简单工厂模式 、工厂方法模式 、抽象工厂模式 。 目录 简单工厂模式 UML图 实现 使用场景: 优点 : 缺点: 工厂方法模式 UML图 实现 使用场景: 优点: 缺点: 抽象工厂模式 UM…...
神经网络硬件加速器-架构篇
架构设计 常规架构通常包括两种: 1、全流水线架构,顾名思义,将整个神经网络进行平铺,并对每一层进行优化设计,优点:实现高吞吐率和低延时。缺点:消耗大量硬件资源,通常无法跨网络或…...
Python raise用法(超级详细,看了无师自通)
是否可以在程序的指定位置手动抛出一个异常?答案是肯定的,Python 允许我们在程序中手动设置异常,使用 raise 语句即可。 大家可能会感到疑惑,即我们从来都是想方设法地让程序正常运行,为什么还要手动设置异常呢&#…...
1.SpringSecurity快速入门
*SpringScurity的核心功能: 认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户 授权:经过认证后判断当前用户是否有权限进行某个操作 *第一步:创建springboot工程 *第二步:引入SpringSecurity依赖 *第三步:写controller,访问对应的url:localhos…...
Graph Partition: Edge cut and Vertex cut
Graph PartitionEdge cut and Vertex cutEdge cutVertex cut实际如何进行点分割和边分割的呢?Graph store format情况1:按照边列表存储:情况2:按照邻接表存储:Edge cut and Vertex cut 图结构描述了数据流动ÿ…...
Javascript周学习小结(初识,变量,数据类型)
JS的三大书写方式行内式如图所示:几点说明:JS的行内式写在HTML的标签内部,(常以on开头),如onclick行内式常常使用单引号括住字符串以区分HTML的双引号可读性差,不建议使用引号易出错,不建议使用特殊情况下使…...
C语言-基础了解-10-C函数
C函数 一、C函数 函数是一组一起执行一个任务的语句。每个 C 程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数。 您可以把代码划分到不同的函数中。如何划分代码到不同的函数中是由您来决定的,但在逻辑上&…...
【LeetCode】剑指 Offer(16)
目录 题目:剑指 Offer 33. 二叉搜索树的后序遍历序列 - 力扣(Leetcode) 题目的接口: 解题思路: 代码: 过啦!!! 写在最后: 题目:剑指 Offer …...
第三十九章 linux-并发解决方法二(互斥锁mutex)
第三十九章 linux-并发解决方法二(互斥锁mutex) 文章目录第三十九章 linux-并发解决方法二(互斥锁mutex)互斥锁的定义与初始化互斥锁的DOWN操作互斥锁的UP操作用count1的信号量实现的互斥方法还不是Linux下经典的用法,…...
脚本方式本地仓库jar包批量导入maven私服
脚本内容,将以下内容保存为mavenimport.sh,放置于需要上传的目录下,可以是顶层目录,或者某个分包的目录,若私服已有待上传的包,则执行会被替换 #!/bin/bash # copy and run this script to the root of th…...
【c++】引用的学习
引用的定义和声明 引用是一种别名,它允许使用与原变量相同的内存位置。在C中,引用是使用&符号来定义的。引用必须在定义时初始化,并且可以与原变量分别使用。 int a 10; int& b a; // 定义了一个引用b,它指向a引用的作用…...
linux 软件安装及卸载
1.联网在线安装及卸载ubuntu环境下:使用apt-get 工具apt-get install - 安装软件包apt-get remove - 移除(卸载)软件包CentOS环境下:使用yum工具 (银河麒麟系统属于centos)yum install - 安装软件包yum rem…...
XShell连接ubuntu20.04.LTS
1 下载XshellXShell官方下载地址打开XSHELL官方下载地址,我们可以选择【家庭和学校用户的免费许可证】,输入邮箱之后即可获得下载链接安装非常简单,跟着提示进行即可。2 连接ubuntu2.1 查看ubuntu的ip地址输入命令查看ip地址ifconfig刚开始可…...
【FPGA】Verilog:MSI/LSI 组合电路之解码器 | 多路分解器
写在前面:本章将理解编码器与解码器、多路复用器与多路分解器的概念,通过使用 Verilog 实现多样的解码器与多路分解器,通过 FPGA 并使用 Verilog 实现。 Ⅰ. 前置知识 0x00 解码器与编码器(Decoder / Encoder) 解码器…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
