• Quartz任务调度


    Quartz任务调度

    .Quartz概念QuartzOpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。quartz是开源且具有丰富特性的"任务调度库",能够集成于任何的java应用,小到独立的应用,大至电子商业系统。quartz能够创建亦简单亦复杂的调度,以执行上十、上百,甚至上万的任务。任务job被定义为标准的java组件,能够执行任何你想要实现的功能。quartz调度框架包含许多企业级的特性,如JTA事务、集群的支持。简而言之,quartz就是基于java实现的任务调度框架,用于执行你想要执行的任何任务。
    官网: http://www.quartz-scheduler.org/

    .Quartz运行环境Quartz 可以运行嵌入在另一个独立式应用程序Quartz 可以在应用程序服务器(servlet容器)内被实例化,并且参与事务Quartz 可以作为一个独立的程序运行(其自己的Java虚拟机内),可以通过RMI使用Quartz 可以被实例化,作为独立的项目集群(负载平衡和故障转移功能),用于作业的执行.Quartz设计模式Builder模式Factory模式
    组件模式
    链式编程

    .Quartz学习的核心概念任务Job
    Job就是你想要实现的任务类,每一个Job必须实现org.quartz.job接口,且只需实现接口定义的execute()方法。触发器Trigger
    Trigger为你执行任务的触发器,比如你想每天定时3点发送一份统计邮件,Trigger将会设置3点进行执行该任务。Trigger主要包含两种SimpleTriggerCronTrigger两种。关于二者的区别的使用场景,后续的课程会进行讨论。调度器Scheduler
    Scheduler为任务的调度器,它会将任务job及触发器Trigger整合起来,负责基于Trigger设定
    的时间来执行Job.Quartz的体系结构

     .Quartz的几个常用API以下是Quartz编程API几个重要接口,也是Quartz的重要组件Scheduler 用于与调度程序交互的主程序接口。 Scheduler 调度程序-任务执行计划表,只有安排进执行计划的任务Job(通过scheduler.scheduleJob方法安排进执行计划),当它预先定义的执行时间到了的时候(任务触发trigger),该任务才会执行。Job 我们预先定义的希望在未来时间能被调度程序执行的任务类,我们可以自定义。JobDetail 使用JobDetail来定义定时任务的实例,JobDetail实例是通过JobBuilder类创建的。JobDataMap 可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;JobDataMapJava Map接口的一个实现,额外增加了一些便于存取基本类型的数 据的方法。Trigger 触发器,Trigger对象是用来触发执行Job的。当调度一个job时,我们实例一个触发器然后调整它的属性来满足job执行的条件。表明任务在什么时候会执行。定义了一个已经被安排的任务将会在什么时候执行的时间条件,比如每2秒就执行一次。JobBuilder -用于声明一个任务实例,也可以定义关于该任务的详情比如任务名、组名等,这个声明的实例将会作为一个实际执行的任务。TriggerBuilder 触发器创建器,用于创建触发器trigger实例。JobListenerTriggerListenerSchedulerListener监听器,用于对组件的监听。

     .Quartz的使用  引入jar包

    1. <dependencies>
    2. <dependency>
    3. <groupId>org.quartz-scheduler</groupId>
    4. <artifactId>quartz</artifactId>
    5. <version>2.3.0</version>
    6. </dependency>
    7. <dependency>
    8. <groupId>junit</groupId>
    9. <artifactId>junit</artifactId>
    10. <version>4.12</version>
    11. </dependency>
    12. <dependency>
    13. <groupId>org.slf4j</groupId>
    14. <artifactId>slf4j-log4j12</artifactId>
    15. <version>1.7.5</version>
    16. </dependency>
    17. </dependencies>

     导入log4j.properties日志文件 

    1. ### direct log messages to stdout ###
    2. log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    3. log4j.appender.stdout.Target=System.out
    4. log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    5. log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L -
    6. %m%n
    7. ### direct messages to file mylog.log ###
    8. log4j.appender.file=org.apache.log4j.FileAppender
    9. log4j.appender.file.File=c:/mylog.log
    10. log4j.appender.file.layout=org.apache.log4j.PatternLayout
    11. log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L -
    12. %m%n
    13. ### set log levels - for more verbose logging change 'info' to 'debug' ###
    14. log4j.rootLogger=info, stdout

     HelloJob.java 

    1. // 定义任务类
    2. public class HelloJob implements Job {
    3. @Override
    4. public void execute(JobExecutionContext arg0) throws JobExecutionException {
    5. // 定义时间
    6. Date date = new Date();
    7. SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm: ss ");
    8. String dateString = dateFormat.format(date);
    9. // 定义工作任务内容
    10. System.out.println("进行数据库备份操作。当前任务执行的时间:"+dateString);
    11. }
    12. }

     HelloSchedulerDemo.java 

    1. public class HelloSchedulerDemo {
    2. public static void main(String[] args) throws Exception {
    3. // 1:从工厂中获取任务调度的实例
    4. Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
    5. // 2:定义一个任务调度实例,将该实例与HelloJob绑定,任务类需要实现Job接口
    6. JobDetail job = JobBuilder.newJob(HelloJob.class)
    7. .withIdentity("job1", "group1") // 定义该实例唯一标识
    8. .build();
    9. // 3:定义触发器 ,马上执行, 然后每5秒重复执行一次
    10. Trigger trigger = TriggerBuilder.newTrigger()
    11. .withIdentity("trigger1", "group1") // 定义该实例唯一标识
    12. .startNow() // 马上执行
    13. .withSchedule(SimpleScheduleBuilder.simpleSchedule()
    14. .repeatSecondlyForever(5)) //5秒执行一次
    15. .build();
    16. // 4:使用触发器调度任务的执行
    17. scheduler.scheduleJob(job, trigger);
    18. // 5:开启
    19. scheduler.start();
    20. // 关闭
    21. // scheduler.shutdown();
    22. }
    23. }

     

     4.JobJobDetail介绍Job工作任务调度的接口,任务类需要实现该接口。该接口中定义execute方法,类似JDK提供的TimeTask类的run方法。在里面编写任务执行的业务逻辑。Job实例在Quartz中的生命周期:每次调度器执行Job时,它在调用execute方法前会创建一个新的Job实例,当调用完成后,关联的Job对象实例会被释放,释放的实例会被垃圾回收机制回收。JobDetailJobDetailJob实例提供了许多设置属性,以及JobDetaMap成员变量属性,它用来存储特定Job实例的状态信息,调度器需要借助JobDetail对象来添加Job实例。JobDetail重要属性:namegroupjobClassjobDataMap

    5.JobExecutionContext介绍Scheduler调用一个Job,就会将JobExecutionContext传递给Jobexecute()方法;Job能通过JobExecutionContext对象访问到Quartz运行时候的环境以及Job本身的明细数据。6.JobDataMap介绍(1)使用Map获取。
    在进行任务调度时,JobDataMap存储在JobExecutionContext中 ,非常方便获取。JobDataMap可以用来装载任何可序列化的数据对象,当job实例对象被执行时这些参数对象会传递给它。JobDataMap实现了JDKMap接口,并且添加了非常方便的方法用来存取基本数据类型。HelloSchedulerDemo.java 

    1. // 2:定义一个任务调度实例,将该实例与HelloJob绑定,任务类需要实现Job接口
    2. JobDetail job = JobBuilder.newJob(HelloJob.class)
    3. .withIdentity("job1", "group1") // 定义该实例唯一标识
    4. .usingJobData("message", "打印日志")
    5. .build();
    6. // 3:定义触发器 ,马上执行, 然后每5秒重复执行一次
    7. Trigger trigger = TriggerBuilder.newTrigger()
    8. .withIdentity("trigger1", "group1") // 定义该实例唯一标识
    9. .startNow() // 马上执行
    10. //.startAt(triggerStartTime) // 针对某个时刻执行
    11. .withSchedule(SimpleScheduleBuilder.simpleSchedule()
    12. .repeatSecondlyForever(5)) //5秒执行一次
    13. .usingJobData("message", "simple触发器")
    14. .build();

     HelloJob.java 

    1. JobKey jobKey = context.getJobDetail().getKey();
    2. System.out.println("工作任务名称:"+jobKey.getName()+";工作任务组:"+jobKey.getGroup());
    3. System.out.println("任务类名称(带包 名):"+context.getJobDetail().getJobClass().getName());
    4. System.out.println("任务类名 称:"+context.getJobDetail().getJobClass().getSimpleName());
    5. System.out.println("当前任务执行时间:"+context.getFireTime());
    6. System.out.println("下一任务执行时间:"+context.getNextFireTime());
    7. TriggerKey triggerKey = context.getTrigger().getKey();
    8. System.out.println("触发器名称:"+triggerKey.getName()+";触发器组:"+triggerKey.getGroup());
    9. JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
    10. String jobMessage = jobDataMap.getString("message");
    11. System.out.println("任务参数消息值:"+jobMessage);
    12. JobDataMap triggerDataMap = context.getTrigger().getJobDataMap();
    13. String triggerMessage = triggerDataMap.getString("message");
    14. System.out.println("触发器参数消息值:"+triggerMessage);
    1. 运行结果:
    2. 工作任务名称:hjob;工作任务组:hgroup
    3. 任务类名称(带包名):com.topcheer.HelloJob
    4. 任务类名称:HelloJob
    5. 当前任务执行时间:Sun May 12 12:56:40 CST 2019
    6. 下一任务执行时间:Sun May 12 12:56:45 CST 2019
    7. 触发器名称:htrigger;触发器组:hgroup2
    8. ========================================
    9. 任务参数消息值:打印日志
    10. 触发器参数消息值:simple触发器
    11. ========================================

    (2)Job实现类中添加setter方法对应JobDataMap的键值,Quartz框架默认的JobFactory
    现类在初始化job实例对象时会自动地调用这些setter方法

    1. private String message;
    2. public void setMessage(String message) {
    3. this.message = message;
    4. } /
    5. /参数值信息-----》simple触发器

    这里注意:如果遇到同名的keyTrigger中的.usingJobData("message", "simple触发器")会覆盖JobDetail中的.usingJobData("message", "打印日志")7.有状态的Job和无状态的Job@DisallowConcurrentExecution 注解的使用 

    基本意思就是,比如当前任务(每个JobDetail实例,同一job class、不同的jobKey也算不同的实例)每隔10秒执行一次,但是,任务的执行花去了15秒的时间,那么必然会对下一次的执行产生影
    响,加上这个注释之后可以防止这种事情发生。看我的MyParaJob代码里面注释的内容,如果是11的话,线程挂起11秒,下次的结果就乱掉了,所以如果有状态可能花去比较长的时间,并且是有状态的话,就加上这个注解 。

    @PersistJobDataAfterExecution注解的使用有状态的Job可以理解为多次Job调用期间可以持有一些状态信息,这些状态信息存储在JobDataMap中,而默认的无状态job每次调用时都会创建一个新的JobDataMap(1)修改HelloSchedulerDemo.java。添加.usingJobData("count", 0),表示计数器。

    1. JobDetail job = JobBuilder.newJob(HelloJob.class)
    2. .withIdentity("job1", "group1") // 定义该实例唯一标识
    3. .usingJobData("message", "打印日志")
    4. .usingJobData("count", 0)
    5. .build();

    (2)修改HelloJob.java添加countsettinggetting方法。

    1. private Integer count;
    2. public void setCount(Integer count) {
    3. this.count = count;
    4. }

    public void execute(JobExecutionContext context) throws JobExecutionException的方法
    中添加。

    1. ++count;
    2. System.out.println("count数量:"+count);
    3. context.getJobDetail().getJobDataMap().put("count", count);

    HelloJob类没有添加@PersistJobDataAfterExecution注解,每次调用时都会创建一个新的JobDataMap。不会累加;HelloJob类添加@PersistJobDataAfterExecution注解,多次Job调用期间可以持有一些状态信
    息,即可以实现count的累加。@PersistJobDataAfterExecution 同样, 也是加在Job,表示当正常执行完Job, JobDataMap中的数据应该被改动, 以被下一次调用时用。当使用@PersistJobDataAfterExecution 注解时,为了避免并发时, 存储数据造成混乱, 强烈建议把@DisallowConcurrentExecution注解也加
    上。8.Trigger介绍

     Quartz有一些不同的触发器类型,不过,用得最多的是SimpleTriggerCronTrigger
    (1)jobKey表示job实例的标识,触发器被触发时,该指定的job实例会被执行。
    (2)startTime表示触发器的时间表,第一次开始被触发的时间,它的数据类型是java.util.Date
    (3)endTime指定触发器终止被触发的时间,它的数据类型是java.util.Date
    案例:HelloJobTrigger.java

    1. // 定义任务类
    2. public class HelloJobTrigger implements Job {
    3. @Override
    4. public void execute(JobExecutionContext context) throws JobExecutionException {
    5. // 定义时间
    6. Date date = new Date();
    7. SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    8. String dateString = dateFormat.format(date);
    9. // 定义工作任务内容
    10. System.out.println("进行数据库备份操作。当前任务执行的时 间:"+dateString);
    11. // 获取jobKey、startTime、endTime
    12. Trigger trigger = context.getTrigger();
    13. System.out.println("jobKey的标识:"+trigger.getJobKey().getName()+";jobKey的组名 称:"+trigger.getJobKey().getGroup());
    14. System.out.println("任务开始时 间:"+dateFormat.format(trigger.getStartTime())+";任务结束时间:"+dateFormat.format(trigger.getEndTime()));
    15. }
    16. }

    HelloSchedulerDemoTrigger.java

    1. public class HelloSchedulerDemoTrigger {
    2. public static void main(String[] args) throws Exception {
    3. // 1:从工厂中获取任务调度的实例
    4. Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
    5. // 定义日期
    6. Date startDate = new Date();
    7. // 启动任务,任务在当前时间3秒后执行
    8. startDate.setTime(startDate.getTime()+3000);
    9. // 定义日期
    10. Date endDate = new Date();
    11. // 结束任务,任务在当前时间10秒后停止
    12. endDate.setTime(endDate.getTime()+10000);
    13. // 2:定义一个任务调度实例,将该实例与HelloJob绑定,任务类需要实现Job接口
    14. JobDetail job = JobBuilder.newJob(HelloJobTrigger.class)
    15. .withIdentity("job1", "group1") // 定义该实例唯一标识
    16. .usingJobData("message", "打印日志")
    17. .build();
    18. // 3:定义触发器 ,马上执行, 然后每5秒重复执行一次
    19. Trigger trigger = TriggerBuilder.newTrigger()
    20. .withIdentity("trigger1", "group1") // 定义该实例唯一标识
    21. .startAt(startDate)
    22. .endAt(endDate)
    23. .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5)) //5秒执行一次
    24. .usingJobData("message", "simple触发器")
    25. .build();
    26. // 4:使用触发器调度任务的执行
    27. scheduler.scheduleJob(job, trigger);
    28. // 5:开启
    29. scheduler.start();
    30. // 关闭
    31. // scheduler.shutdown();
    32. }
    33. }
    1. 进行数据库备份操作。当前任务执行的时间:2019-05-12 14:15:40
    2. jobKey的标识:job1;jobKey的组名称:group1
    3. 任务开始时间:2019-05-12 14:15:40;任务结束时间:2019-05-12 14:15:47
    4. 进行数据库备份操作。当前任务执行的时间:2019-05-12 14:15:45
    5. jobKey的标识:job1;jobKey的组名称:group1
    6. 任务开始时间:2019-05-12 14:15:40;任务结束时间:2019-05-12 14:15:47

    9.SimpleTrigger触发器SimpleTrigger对于设置和使用是最为简单的一种 QuartzTrigger
    它是为那种需要在特定的日期/时间启动,且以一个可能的间隔时间重复执行 n 次的 Job 所设计的。
    案例一:表示在一个指定的时间段内,执行一次作业任务;HelloJobSimpleTrigger.java

    1. // 定义任务类
    2. public class HelloJobSimpleTrigger implements Job {
    3. @Override
    4. public void execute(JobExecutionContext context) throws JobExecutionException {
    5. // 定义时间
    6. Date date = new Date();
    7. SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    8. String dateString = dateFormat.format(date);
    9. // 定义工作任务内容
    10. System.out.println("进行数据库备份操作。当前任务执行的时 间:"+dateString);
    11. }
    12. }

    HelloSchedulerDemoSimpleTrigger.java

    1. public class HelloSchedulerDemoSimpleTrigger {
    2. public static void main(String[] args) throws Exception {
    3. // 1:从工厂中获取任务调度的实例
    4. Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
    5. // 定义日期
    6. Date startDate = new Date();
    7. // 启动任务,任务在当前时间3秒后执行
    8. startDate.setTime(startDate.getTime()+3000);
    9. // 2:定义一个任务调度实例,将该实例与HelloJob绑定,任务类需要实现Job接口
    10. JobDetail job = JobBuilder.newJob(HelloJobSimpleTrigger.class)
    11. .withIdentity("job1", "group1") // 定义该实例唯一标识
    12. .build();
    13. // 3:定义触发器 ,马上执行, 然后每5秒重复执行一次
    14. Trigger trigger = TriggerBuilder.newTrigger()
    15. .withIdentity("trigger1", "group1") // 定义该实例唯一标识
    16. .startAt(startDate)
    17. .build();
    18. // 4:使用触发器调度任务的执行
    19. scheduler.scheduleJob(job, trigger);
    20. // 5:开启
    21. scheduler.start();
    22. // 关闭
    23. // scheduler.shutdown();
    24. }
    25. }

    案例二:或在指定的时间间隔内多次执行作业任务。
    修改HelloSchedulerDemoSimpleTrigger.java

    1. // 3:定义触发器 ,马上执行, 然后每5秒重复执行一次
    2. Trigger trigger = TriggerBuilder.newTrigger()
    3. .withIdentity("trigger1", "group1") // 定义该实例唯一标识
    4. .startAt(startDate)
    5. .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever
    6. (5)
    7. .withRepeatCount(2)) //5秒执行一次,连续执行3次后停止,默认值是0
    8. .build();

    案例三:指定任务的结束时间。
    修改HelloSchedulerDemoSimpleTrigger.java

    1. // 定义日期
    2. Date endDate = new Date();
    3. // 启动结束,任务在当前时间10秒后停止
    4. endDate.setTime(endDate.getTime()+10000);
    5. // 2:定义一个任务调度实例,将该实例与HelloJob绑定,任务类需要实现Job接口
    6. JobDetail job = JobBuilder.newJob(HelloJobSimpleTrigger.class)
    7. .withIdentity("job1", "group1") // 定义该实例唯一标识
    8. .build();
    9. // 3:定义触发器 ,马上执行, 然后每5秒重复执行一次
    10. Trigger trigger = TriggerBuilder.newTrigger()
    11. .withIdentity("trigger1", "group1") // 定义该实例唯一标识需要注意的点
    12. .startAt(startDate)
    13. .endAt(endDate)
    14. .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5)
    15. .withRepeatCount(3)) //5秒执行一次,连续执行3次后停止
    16. .build();

    需要注意的点SimpleTrigger的属性有:开始时间、结束时间、重复次数和重复的时间间隔。
    重复次数属性的值可以为0、正整数、或常量 SimpleTrigger.REPEAT_INDEFINITELY
    重复的时间间隔属性值必须为大于0或长整型的正整数,以毫秒作为时间单位,当重复的时间间隔为0时,意味着与Trigger同时触发执行。
    如果有指定结束时间属性值,则结束时间属性优先于重复次数属性,这样的好处在于:当我们需要创建一个每间隔10秒钟触发一次直到指定的结束时间的 Trigger,而无需去计算从开始到结束的所重复的次数,我们只需简单的指定结束时间和使用REPEAT_INDEFINITELY作为重复次数的属性 值即可 

    1. 进行数据库备份操作。当前任务执行的时间:2019-05-12 15:18:23
    2. jobKey的标识:job1;jobKey的组名称:group1
    3. 任务开始时间:2019-05-12 15:18:23;任务结束时间:2019-05-12 15:18:40
    4. 进行数据库备份操作。当前任务执行的时间:2019-05-12 15:18:28
    5. jobKey的标识:job1;jobKey的组名称:group1
    6. 任务开始时间:2019-05-12 15:18:23;任务结束时间:2019-05-12 15:18:40
    7. 进行数据库备份操作。当前任务执行的时间:2019-05-12 15:18:33
    8. jobKey的标识:job1;jobKey的组名称:group1
    9. 任务开始时间:2019-05-12 15:18:23;任务结束时间:2019-05-12 15:18:40

    10.CronTrigger触发器如果你需要像日历那样按日程来触发任务,而不是像SimpleTrigger 那样每隔特定的间隔时间触发,CronTriggers通常比SimpleTrigger更有用,因为它是基于日历的作业调度器。
    使用CronTrigger,你可以指定诸如每个周五中午,或者每个工作日的9:30”或者从每个周一、周三、周五的上午900到上午1000之间每隔五分钟这样日程安排来触发。甚至,象SimpleTrigger一样,CronTrigger也有一个startTime以指定日程从什么时候开始,也有一个(可选的)endTime以指定何时日程不再继续。
    (1)Cron Expressions——Cron 表达式Cron表达式被用来配置CronTrigger实例。Cron表达式是一个由7个子表达式组成的字符串。每
    个子表达式都描述了一个单独的日程细节。这些子表达式用空格分隔,分别表示:1. Seconds 2. Minutes

  • 相关阅读:
    OpenMesh 网格顶点Quadric误差计算
    Lsky Pro+云服务器搭建私人图床
    springboot基于JAVA的邮件过滤系统设计与实现
    Vue + Element UI 前端篇(十一):第三方图标库
    Vue 使用vue-cli构建SPA项目(超详细)
    微模块化炙手可热,数据中心走向智能化取胜
    记于2022.7.21
    PMP每日一练 | 考试不迷路-9.12(包含敏捷+多选)
    C++——类和对象练习(日期类)
    如何选择线程数量
  • 原文地址:https://blog.csdn.net/qq_29860591/article/details/127736483