quartz框架_Quartz定时任务框架(一)入门使用

0e5cd9fa0a970caff1071cb3724187c4.gif


前言

    前段时间接到任务,处理Quartz多节点Job并发执行的问题。在此之前,对于Quartz了解甚微,为了完成开发任务,利用了两个周末的时间研究了Quartz,写下了笔记,记录下Quartz学习过程。


一、什么是Quartz?

    Quartz Job Scheduling是基于Java实现的成熟的企业级作业调度组件,可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。

是完全由java开发的一个开源的任务日程管理系统,“任务进度管理器”就是一个在预先确定(被纳入日程)的时间到达时,负责执行(或者通知)其他软件组件的系统。Quartz是目前最为成熟,使用最广泛的Java任务调度框架。


二、使用场景

    定时任务处理,大部分公司都会有涉及到这一块业务。比较常见的如订单超时未支付。当用户生成一条订单,迟迟没有支付,业务判定这一订单是无效订单,然后进行恢复库存等业务处理。在技术层面,我们需要有一个“任务”,不断的刷新超过30分钟(就拿设置超时时间为30分钟为例)没有支付成功的订单。这里就可以使用Quartz定时任务调度框架。


三、代码实现

    这一篇笔记主要记录入门的简单使用,后续会分享集群分布式部署处理。大部分名词解释写在代码注释中,方便阅读。

    第一步导入maven依赖

<dependency>    <groupId>org.quartz-schedulergroupId>    <artifactId>quartzartifactId>    <version>2.3.0version>dependency><dependency>    <groupId>org.quartz-schedulergroupId>    <artifactId>quartz-jobsartifactId>    <version>2.3.0version>dependency><dependency>    <groupId>javax.transactiongroupId>    <artifactId>jtaartifactId>    <version>1.1version>dependency>

    我们定义一个简单的Job类

import org.quartz.*;/*** @description: 定义job,输出打印,测试功能**  Job(作业)是指执行一些作业的特定的Java类*  Job必须实现 org.quartz.Job接口*      这个接口要求在Job中实现execute()方法*  当 Quartz 调用 execute() 方法,*      会传递一个 JobExecutionContext 上下文变量,*      里面封装有 Quartz 的运行时环境和当前正执行的 Job。*  JobExecutionContext可以被用来访问 JobDetail 类,*      JobDetail 类持有Job的详细信息,包括为Job实例指定的名称,Job 所属组,Job 是否被持久化(易失性)。*      JobDetail又持有一个指向JobDataMap的引用。JobDataMap中包含Job配置的自定义属性。** @author: Lishuzhen* @create: 2019-12-13 12:35*/public class HelloJob implements Job {    @Override    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {        JobDetail jobDetail = jobExecutionContext.getJobDetail();        // JobDataMap        JobDataMap dataMap = jobDetail.getJobDataMap();        String content = dataMap.getString("HELLO");        System.out.println(content);    }}

创建一个调度器,带动HelloJob运行

import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.quartz.*;import org.quartz.impl.StdSchedulerFactory;import static org.quartz.JobBuilder.newJob;import static org.quartz.SimpleScheduleBuilder.simpleSchedule;import static org.quartz.TriggerBuilder.newTrigger;/*** @description: job调度器* @author: Lishuzhen* @create: 2019-12-13 12:54*/public class HelloQuartz {    private static Log logger = LogFactory.getLog(HelloQuartz.class);    public static void main(String[] args) {        try {            /**             * 从Scheduler工厂获取一个Scheduler的实例             *             * Scheduler(调度器)是Quartz框架的心脏。             * Scheduler的生命周期始于通过SchedulerFactory工厂类创建实例 终于调用shutdown() 方法             * Scheduler不仅可以用于新增、移除、列举Jobs和Triggers             * 还可以执行调度相关操作             *  比如暂停Trigger、恢复Trigger等             * 需要注意的是             *  直到调用start()方法时,Scheduler才正式开始执行job和trigger。             */            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();            /**             * 注册jobDetail1,打印"Hello Quartz!",第5秒钟执行一次             * JobDetail表示一个具体的可执行的调度程序             * Job 是这个可执行程调度程序所要执行的内容             * 另外 JobDetail 还包含了这个任务调度的方案和策略             */            JobDetail jobDetail = newJob(HelloJob.class).withIdentity("job", "hello").build();            jobDetail.getJobDataMap().put("HELLO", "Hello Quartz!");            /**             * Trigger(触发器)用于触发Job的执行。最常用的类型包括 SimpleTrigger和CronTrigger。             */            Trigger trigger = newTrigger().withIdentity("trigger", "hello").startNow()                    .withSchedule(simpleSchedule().withIntervalInSeconds(5).withRepeatCount(10)).build();            /**             * 组合Job和trigger             */            scheduler.scheduleJob(jobDetail, trigger);            /**             * 调度器启动             */            scheduler.start();            /**             * 调度器关闭             */            //scheduler.shutdown(true);        } catch (SchedulerException e) {            logger.error(e);        }    }}

此时,运行main,即可得到以下结果,看到我们的HelloJob已经在定时执行了

2480f847544ba456bc3706d271d1fcae.png

上面的代码简单演示Job执行,quartz通过调用器来根据Trigger(触发器)用于触发Job的执行。我们最常用的Trigger类型有 SimpleTrigger和CronTrigger。我们分别写两个Demo来演示使用过程。

SimpleTrigger 触发 job,定义一个 HelloSimpleTrigger触发器测试类

import org.quartz.JobDetail;import org.quartz.Scheduler;import org.quartz.SchedulerException;import org.quartz.SimpleTrigger;import org.quartz.impl.StdSchedulerFactory;import java.text.DateFormat;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;import static org.quartz.DateBuilder.dateOf;import static org.quartz.DateBuilder.evenHourDate;import static org.quartz.JobBuilder.newJob;import static org.quartz.SimpleScheduleBuilder.simpleSchedule;import static org.quartz.TriggerBuilder.newTrigger;/*** @description: 使用 SimpleTrigger 部署job*  SimpleTrigger对于设置和使用是一种最简单的一种Quartz Trigger。*  它是为需要在特定日期/时间启动,且以一个可能间隔的时间执行n次的Job设计的。* @author: Lishuzhen* @create: 2019-12-13 14:42*/public class HelloSimpleTrigger {    public static void main(String[] args) {        try {            // 以 StdScheduleFactory的方式获取一个 Schedule 实例            Scheduler scheduled = StdSchedulerFactory.getDefaultScheduler();            JobDetail jobDetail = newJob(HelloJob.class).build();            jobDetail.getJobDataMap().put("simpleTrigger", "Hello SimpleTrigger...");            // 创建指定时间            DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");            // 开始时间            Date startTime = dateFormat.parse("2019-12-13 15:00:00");            // 结束时间            Date endTime = dateFormat.parse("2019-12-13 22:00:00");            // 创建 SimpleTrigger 触发器            SimpleTrigger trigger = createSimpleTrigger1(startTime,endTime);//            SimpleTrigger trigger = createSimpleTrigger2(5);//            SimpleTrigger trigger = createSimpleTrigger3(16,23, 0);            scheduled.scheduleJob(jobDetail, trigger);            scheduled.start();        } catch (SchedulerException e) {            e.printStackTrace();        } catch (ParseException e) {            e.printStackTrace();        }    }    /**     * 创建一个指定时间开始,指定时间结束的触发器     * @param startTime     * @param endTime     * @return     */    private static SimpleTrigger createSimpleTrigger1(Date startTime,Date endTime){        return newTrigger()                /**                 * group 是用于分类的,相当于一个命名空间。                 * 作用 :                 *      比如 判断两个 trigger 或者 job 是否相同                 *      在 trigger 中,使用 equal 方法就是在父类 key 中的 equal 方法,这里就用到 group                 */                .withIdentity("simpleTrigger","hello")                // 指定开始时间                .startAt(startTime)                // 执行策略                .withSchedule(simpleSchedule()                        // 间隔 5 分钟//                        .withIntervalInMinutes(5)                        // 间隔 5 秒                        .withIntervalInSeconds(5)                        // 重复执行次数,默认 1 次                        .withRepeatCount(100)                )                // 结束时间                .endAt(endTime)                .build();    }    /**     * 创建下一个整点开始执行,X 小时执行一次,永久执行的触发器     * @param hour     * @return     */    private static SimpleTrigger createSimpleTrigger2(Integer hour){        return newTrigger()                /**                 * group 是用于分类的,相当于一个命名空间。                 * 作用 :                 *      比如 判断两个 trigger 或者 job 是否相同                 *      在 trigger 中,使用 equal 方法就是在父类 key 中的 equal 方法,这里就用到 group                 */                .withIdentity("simpleTrigger","hello")                // 指定开始时间,下一个整点开始执行                .startAt(evenHourDate(null))                // 执行策略                .withSchedule(simpleSchedule()                        // 间隔 x 小时                        .withIntervalInHours(hour)                        // 间隔 5 分钟//                        .withIntervalInMinutes(5)                        // 间隔 5 秒//                        .withIntervalInSeconds(5)                        // 重复执行次数,默认 1 次//                        .withRepeatCount(100)                        // 永远重复执行                        .repeatForever()                )                .build();    }    /**     * 创建现在开始执行,指定时间结束的触发器     * @param hour     * @param minute     * @param second     * @return     */    private static SimpleTrigger createSimpleTrigger3(Integer hour, Integer minute ,Integer second){        return newTrigger()                /**                 * group 是用于分类的,相当于一个命名空间。                 * 作用 :                 *      比如 判断两个 trigger 或者 job 是否相同                 *      在 trigger 中,使用 equal 方法就是在父类 key 中的 equal 方法,这里就用到 group                 */                .withIdentity("simpleTrigger","hello")                // 指定开始时间,默认 当前时间开始执行//                .startAt(evenHourDate(null))                // 执行策略                .withSchedule(simpleSchedule()                                // 间隔 x 小时//                        .withIntervalInHours(hour)                                // 间隔 5 分钟//                        .withIntervalInMinutes(5)                                // 间隔 5 秒                        .withIntervalInSeconds(5)                                // 重复执行次数,默认 1 次//                        .withRepeatCount(100)                                // 永远重复执行                                .repeatForever()                )                // 指定时间结束                .endAt(dateOf(hour, minute, second))                .build();    }}

运行 main ,得到运行结果

0491a5ba54d3a39640d67d1e01e8645c.png

CronTrigger 触发job , 新建一个 HelloCronTrigger 测试类

import org.quartz.*;import org.quartz.impl.StdSchedulerFactory;import static org.quartz.JobBuilder.newJob;import static org.quartz.TriggerBuilder.newTrigger;/*** @description: 使用 CronTrigger 触发器部署Job** CronTrigger 是基于 Unix 类似于 cron 的表达式,允许设定非常复杂的触发时间表。* Cron表达式由七个子表达式组成的字符串,它描述了不同的调度细节。* 这些子表达式是用空格分隔的,并表示:秒、分钟、小时、天、月、星期几、年(可选项)**      “,” :表示指定多个值*      “-”:表示指定一个范围的值*      “*”:表示整个时间段*      “/”:表示指定一个值的增加幅度。n/m表示从n开始,每次增加m*      “?”:表示不确定的值*      “L”:用在日表示一个月中的最后一天,用在周表示该月最后一个星期X*      “W”:指定离给定日期最近的工作日(周一到周五)。LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。*      “#”:用于指定月份中的第几周的哪一天。例如,如果你指定周域的值为 6#3,它意思是某月的第三个周五 (6=星期五,#3意味着月份中的第三周)。** @author: Lishuzhen* @create: 2019-12-13 16:25*/public class HelloCronTrigger {    public static void main(String[] args) {        try {            // 调度器            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();            // jobDetail 可执行程序            JobDetail jobDetail = newJob(HelloJob.class).build();            jobDetail.getJobDataMap().put("cronTrigger","Hello CronTrigger...");            // 触发器            CronTrigger trigger = newTrigger()                    .withIdentity("cronTrigger","hello")                    .startNow()                    .withSchedule(                            // 每天 01:00:00 执行                           // CronScheduleBuilder.cronSchedule("0 0 1 * * ?")                            // 每隔 5 秒执行一次                            CronScheduleBuilder.cronSchedule("*/5 * * * * ?")                    )                    .build();            scheduler.scheduleJob(jobDetail, trigger);            scheduler.start();        } catch (SchedulerException e) {            e.printStackTrace();        }    }

运行结果

1fbb5acd5fc2601b1d82e44dfb0dfadd.png

CronTrigger触发模式使用cron表达式,可以设置非常复杂的触发时间,对于cron表达式很多同学可能不熟悉,这里附上常用的cron表达式并且对此进行解释

   (1)0 0 2 1 * ? *   表示在每月的1日的凌晨2点调整任务  (2)0 15 10 ? * MON-FRI   表示周一到周五每天上午10:15执行作业  (3)0 15 10 ? 6L 2002-2006   表示2002-2006年的每个月的最后一个星期五上午10:15执行作  (4)0 0 10,14,16 * * ?   每天上午10点,下午2点,4点  (5)0 0/30 9-17 * * ?   朝九晚五工作时间内每半小时  (6)0 0 12 ? * WED    表示每个星期三中午12点  (7)0 0 12 * * ?   每天中午12点触发  (8)0 15 10 ? * *    每天上午10:15触发  (9)0 15 10 * * ?     每天上午10:15触发  (10)0 15 10 * * ? *    每天上午10:15触发  (11)0 15 10 * * ? 2005    2005年的每天上午10:15触发  (12)0 * 14 * * ?     在每天下午2点到下午2:59期间的每1分钟触发  (13)0 0/5 14 * * ?    在每天下午2点到下午2:55期间的每5分钟触发  (14)0 0/5 14,18 * * ?     在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发  (15)0 0-5 14 * * ?    在每天下午2点到下午2:05期间的每1分钟触发  (16)0 10,44 14 ? 3 WED    每年三月的星期三的下午2:10和2:44触发  (17)0 15 10 ? * MON-FRI    周一至周五的上午10:15触发  (18)0 15 10 15 * ?    每月15日上午10:15触发  (19)0 15 10 L * ?    每月最后一日的上午10:15触发  (20)0 15 10 ? * 6L    每月的最后一个星期五上午10:15触发  (21)0 15 10 ? * 6L 2002-2005   2002年至2005年的每月的最后一个星期五上午10:15触发  (22)0 15 10 ? * 6#3   每月的第三个星期五上午10:15触发============================================================================================================解释    有些子表达式能包含一些范围或列表  例如:子表达式(天(星期))可以为 “MON-FRI”,“MON,WED,FRI”,“MON-WED,SAT”“*”字符代表所有可能的值  因此,“*”在子表达式(月)里表示每个月的含义,“*”在子表达式(天(星期))表示星期的每一天  “/”字符用来指定数值的增量  例如:在子表达式(分钟)里的“0/15”表示从第0分钟开始,每15分钟在子表达式(分钟)里的“3/20”表示从第3分钟开始,每20分钟(它和“3,23,43”)的含义一样  “?”字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值  当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为“?”  “L” 字符仅被用于天(月)和天(星期)两个子表达式,它是单词“last”的缩写  但是它在两个子表达式里的含义是不同的。  在天(月)子表达式中,“L”表示一个月的最后一天  在天(星期)自表达式中,“L”表示一个星期的最后一天,也就是SAT  如果在“L”前有具体的内容,它就具有其他的含义了  例如:“6L”表示这个月的倒数第6天,“FRIL”表示这个月的最一个星期五  注意:在使用“L”参数时,不要指定列表或范围,因为这会导致问题

Quartz 的 Calender 专门用于屏闭一个时间区间,使 Trigger 在这个区间中不被触发。Quartz包括了多种类型的Calender:

1e5edfd20b9432f74b8e8ee26f6ff6cc.png

如果是Quartz只在一个节点中运行,可以通过注入springbean,在tomcat启动时,带动job执行

import com.frame.quartz.job.HelloJob;import com.frame.quartz.scheduler.SchedulerInstance;import org.quartz.*;import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;import static org.quartz.JobBuilder.newJob;import static org.quartz.TriggerBuilder.newTrigger;/*** @description: 通过 spring 注入bean初始化的方式带动job启动* @author: Lishuzhen* @create: 2019-12-13 20:25*/@Componentpublic class HelloStartJob {    @PostConstruct    public void initStart(){        System.out.println("init start running ...");        // 调度器        Scheduler scheduler = SchedulerInstance.getInstance();        // 可执行程序        JobDetail jobDetail = newJob(HelloJob.class).build();        jobDetail.getJobDataMap().put("startRunning", "Job StartRunning...");        // 触发器 CronTrigger        CronTrigger trigger = newTrigger()                .withIdentity("cronTrigger","hello")                .startNow()                .withSchedule(                        // 每隔 5 秒执行一次                        CronScheduleBuilder.cronSchedule("*/5 * * * * ?")                )                .build();        try {            // jobDetail 和 trigger 组合            scheduler.scheduleJob(jobDetail, trigger);            // 调度器启动            scheduler.start();        } catch (SchedulerException e) {            e.printStackTrace();        }    }}

启动日志如下 

e95d2729cabe8834f1b6165a9095597a.png


这一篇笔记记录的Quartz的简单入门,在实际开发过程中,几乎不会这样使用。后续会更新 Quartz在集群中部署使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值