JavaQuartz定时作业的创建

Posted Do_GH

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaQuartz定时作业的创建相关的知识,希望对你有一定的参考价值。

一、Quartz 简介

1.1 Quartz

Quartz 是一个开源的作业调度框架,它完全由 Java 写成,并设计用于 J2SE 和 J2EE 应用中。它提供了巨大的灵 活性而不牺牲简单性。你能够用它来为执行一个作业而创建简单的或复杂的调度。它有很多特征,如:数据库支持,集群,插件,EJB 作业预构 建,JavaMail 及其它,支持 cron-like 表达式等等。

1.2 引入依赖

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>$quartz.version</version>
</dependency>

1.3 创建简单的实例

创建Job作业,实现Job类的execute方法,execute()方法用于定义需要执行的内容。

public class HelloJob implements Job 

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException 
        // 获取任务调度中提供的参数
        Object tv1 = context.getTrigger().getJobDataMap().get("t1");
        Object tv2 = context.getTrigger().getJobDataMap().get("t2");
        Object jv1 = context.getJobDetail().getJobDataMap().get("j1");
        Object jv2 = context.getJobDetail().getJobDataMap().get("j2");
        Object sv = null;
        try 
            sv = context.getScheduler().getContext().get("skey");
         catch (SchedulerException e) 
            e.printStackTrace();
        
        System.out.println(tv1+":"+tv2);
        System.out.println(jv1+":"+jv2);
        System.out.println(sv);
        System.out.println("hello:"+LocalDateTime.now());
    


编写测试代码,创建任务调度

public class TestQuarteJob extends TestConfig 
    @Test
    public void jobTest() throws SchedulerException, InterruptedException 

        //创建一个scheduler
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.getContext().put("skey", "svalue");

        //创建一个Trigger
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .usingJobData("t1", "tv1")
            	// 定义触发时间
                .withSchedule(
                        SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(3)   // 每3秒执行一次
                        .repeatForever()		   // 重复执行
                ).build();
        trigger.getJobDataMap().put("t2", "tv2");

        //创建一个job
        JobDetail job = JobBuilder.newJob(HelloJob.class)
                .usingJobData("j1", "jv1")
                .withIdentity("myjob", "mygroup")
                .build();
        job.getJobDataMap().put("j2", "jv2");

        //注册trigger并启动scheduler
        scheduler.scheduleJob(job,trigger);
        scheduler.start();

        // 休眠当前线程,方便观察定时作业的结果
        Thread.sleep(30*1000);
    

1.4 API

Quartz API的关键接口是:

  • Scheduler - 与调度程序交互的主要API。
  • Job - 你想要调度器执行的任务组件需要实现的接口
  • JobDetail - 用于定义作业的实例。
  • Trigger(即触发器) - 定义执行给定作业的计划的组件。
  • JobBuilder - 用于定义/构建 JobDetail 实例,用于定义作业的实例。
  • TriggerBuilder - 用于定义/构建触发器实例。
  • Scheduler 的生命期,从 SchedulerFactory 创建它时开始,到 Scheduler 调用shutdown() 方法时结束;Scheduler 被创建后,可以增加、删除和列举 Job 和 Trigger,以及执行其它与调度相关的操作(如暂停 Trigger)。但是,Scheduler 只有在调用 start() 方法后,才会真正地触发 trigger(即执行 job)。

job的一个 trigger 被触发后,execute() 方法会被 scheduler 的一个工作线程调用;传递给 execute() 方法的 JobExecutionContext 对象中保存着该 job 运行时的一些信息 ,执行 job 的 scheduler 的引用,触发 job 的 trigger 的引用,JobDetail 对象引用,以及一些其它信息。

JobDetail 对象是在将 job 加入 scheduler 时,由客户端程序(你的程序)创建的。它包含 job 的各种属性设置,以及用于存储 job 实例状态信息的 JobDataMap。本节是对 job 实例的简单介绍,更多的细节将在下一节讲到。

Trigger 用于触发 Job 的执行。当你准备调度一个 job 时,你创建一个 Trigger 的实例,然后设置调度相关的属性。Trigger 也有一个相关联的 JobDataMap,用于给 Job 传递一些触发相关的参数。Quartz 自带了各种不同类型的 Trigger,最常用的主要是 SimpleTrigger 和 CronTrigger。

SimpleTrigger 主要用于一次性执行的 Job(只在某个特定的时间点执行一次),或者 Job 在特定的时间点执行,重复执行 N 次,每次执行间隔T个时间单位。CronTrigger 在基于日历的调度上非常有用,如“每个星期五的正午”,或者“每月的第十天的上午 10:15”等。

为什么既有 Job,又有 Trigger 呢?很多任务调度器并不区分 Job 和 Trigger。有些调度器只是简单地通过一个执行时间和一些 job 标识符来定义一个 Job;其它的一些调度器将 Quartz 的 Job 和 Trigger 对象合二为一。在开发 Quartz 的时候,我们认为将调度和要调度的任务分离是合理的。在我们看来,这可以带来很多好处。

例如,Job 被创建后,可以保存在 Scheduler 中,与 Trigger 是独立的,同一个 Job可以有多个 Trigger;这种松耦合的另一个好处是,当与 Scheduler 中的 Job 关联的 trigger 都过期时,可以配置 Job 稍后被重新调度,而不用重新定义 Job;还有,可以修改或者替换 Trigger,而不用重新定义与之关联的 Job。

二、JobDetail

按照上述的示例,在创建调度任务时,会调用JobDetail,而在JobDetail中会指定需要执行的作业(实现了Job接口的实现类)和执行的频率等信息。因此在调用时都会创建新的Job实例,旧的实例将被系统回收,因此无法在Job中定义有状态的数据属性。

此时就需要用到Quartz提供的参数JobDataMap,该类可以提供一些基础数据类型的赋值。

2.1 JobDataMap

public class DumbJob implements Job 

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException 
        // 获取key
        JobKey jobKey = jobExecutionContext.getJobDetail().getKey();

        // 获取JobDataMap
        JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
        String username = jobDataMap.getString("username");
        int age = jobDataMap.getInt("age");

        ArrayList<Integer> arr = (ArrayList) jobDataMap.get("arr");

        System.out.println("jobKey:" + jobKey + ", username:" + username + ", age:" + age + ", arr:" + arr);

    


编写测试类

@Test
public void testDumbJob() 
    try 
        ArrayList<Integer> arr = new ArrayList<>();
        arr.add(11);
        arr.add(12);
        arr.add(13);
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("arr", arr);
        
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        JobDetail jobDetail = JobBuilder.newJob(DumbJob.class)
            .withIdentity("dumbJob", "group1")
            .usingJobData("username", "Tom")
            .usingJobData("age", 18)
            .usingJobData("arr", arr)
            .build();

        Trigger trigger = TriggerBuilder.newTrigger()
            .withIdentity("dumbJob", "group1")
            .startNow()
            .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                          .withIntervalInSeconds(3)
                          .repeatForever())
            .build();

        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();

        Thread.sleep(30*1000);
     catch (SchedulerException e) 
        e.printStackTrace();
     catch (InterruptedException e) 
        e.printStackTrace();
    

运行结果:


根据运行结果看出,获取的jobKey值就是获取调度任务时赋予的任务名和分组信息。从任务调度中可以为Job中传递各种类型的数据,例如数值型、字符型以及一些集合类型,这里需要注意的是在创建JobDetail时,根据usingJobData()方法,可以添加基础数据类型,如果还需要添加其他类型的数据可以自行创建JobDataMap对象,调用put()方法,为其添加需要的数据。

除了调用JobDataMapget()方法获取传递的数据外,还可以通过setter方法进行自动赋值。例如:

public class DumbAutoJob implements Job 
    private String username;
    private int age;
    private ArrayList arr;

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException 
        // 获取key
        JobKey jobKey = jobExecutionContext.getJobDetail().getKey();

        System.out.println("jobKey:" + jobKey + ", username:" + username + ", age:" + age + ", arr:" + arr);
    

    public void setUsername(String username) 
        this.username = username;
    

    public void setAge(int age) 
        this.age = age;
    

    public void setArr(ArrayList arr) 
        this.arr = arr;
    


通过Job类的setter方法,可以将scheduler中的JobDataMap数据根据key值自动填充进去。

2.2 Job状态与并发

关于job的状态数据(即JobDataMap)和并发性,还有一些地方需要注意。在job类上可以加入一些注解,这些注解会影响job的状态和并发性。

@DisallowConcurrentExecution:将该注解加到job类上,告诉Quartz不要并发地执行同一个job定义(这里指特定的job类)的多个实例。请注意这里的用词。拿前一小节的例子来说,如果“SalesReportJob”类上有该注解,则同一时刻仅允许执行一个“SalesReportForJoe”实例,但可以并发地执行“SalesReportForMike”类的一个实例。所以该限制是针对JobDetail的,而不是job类的。但是我们认为(在设计Quartz的时候)应该将该注解放在job类上,因为job类的改变经常会导致其行为发生变化。

@PersistJobDataAfterExecution:将该注解加在job类上,告诉Quartz在成功执行了job类的execute方法后(没有发生任何异常),更新JobDetail中JobDataMap的数据,使得该job(即JobDetail)在下一次执行的时候,JobDataMap中是更新后的数据,而不是更新前的旧数据。和 @DisallowConcurrentExecution注解一样,尽管注解是加在job类上的,但其限制作用是针对job实例的,而不是job类的。由job类来承载注解,是因为job类的内容经常会影响其行为状态(比如,job类的execute方法需要显式地“理解”其”状态“)。

如果你使用了@PersistJobDataAfterExecution注解,我们强烈建议你同时使用@DisallowConcurrentExecution注解,因为当同一个job(JobDetail)的两个实例被并发执行时,由于竞争,JobDataMap中存储的数据很可能是不确定的。

2.3 Job的其它特性

通过JobDetail对象,可以给job实例配置的其它属性有:

  • Durability:如果一个job是非持久的,当没有活跃的trigger与之关联的时候,会被自动地从scheduler中删除。也就是说,非持久的job的生命期是由trigger的存在与否决定的;
  • RequestsRecovery:如果一个job是可恢复的,并且在其执行的时候,scheduler发生硬关闭(hard shutdown)(比如运行的进程崩溃了,或者关机了),则当scheduler重新启动的时候,该job会被重新执行。此时,该job的JobExecutionContext.isRecovering() 返回true。

三、Trigger

Trigger用于设置定时作业的调度信息,例如何时开始、何时结束、作业的执行频率等信息。

3.1 SimpleTrigger

所有类型的trigger都有TriggerKey这个属性,表示trigger的身份;除此之外,trigger还有很多其它的公共属性。这些属性,在构建trigger的时候可以通过TriggerBuilder设置。

Trigger的公共属性有:

  • jobKey属性:当trigger触发时被执行的job的身份;
  • startTime属性:设置trigger第一次触发的时间;该属性的值是java.util.Date类型,表示某个指定的时间点;有些类型的trigger,会在设置的startTime时立即触发,有些类型的trigger,表示其触发是在startTime之后开始生效。比如,现在是1月份,你设置了一个trigger–“在每个月的第5天执行”,然后你将startTime属性设置为4月1号,则该trigger第一次触发会是在几个月以后了(即4月5号)。
  • endTime属性:表示trigger失效的时间点。比如,”每月第5天执行”的trigger,如果其endTime是7月1号,则其最后一次执行时间是6月5号。
@Test
public void testTriggerJob() 
    try 
        Date triggerStartTime = new Date();
        Date triggerEndTime = new Date();

        Calendar c = Calendar.getInstance();
        c.set(2022, Calendar.AUGUST, 9, 15, 52, 0);
        triggerStartTime = c.getTime();

        c.set(2022, Calendar.AUGUST, 9, 15, 53, 0);
        triggerEndTime = c.getTime();

        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        JobDetail jobDetail = JobBuilder.newJob(DumbAutoJob.class)
                .withIdentity("dumbJob", "group1")
                .usingJobData("username", "Tom")
                .usingJobData("age", 18)
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                		.withIdentity("dumbJob", "group1")
//                    .startNow()                           // 在当前时间启动
                		.startAt(triggerStartTime)              // 设置开始时间
               			.endAt(triggerEndTime)                  // 设置结束时间
		                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
//                            .withIntervalInMilliseconds(1)  // 设置间隔时间,单位:毫秒
                				  .withIntervalInSeconds(10)       // 设置间隔时间,单位:秒
//                            .withIntervalInMinutes(1)       // 设置间隔时间,单位:分
//                            .withIntervalInHours(1)         // 设置间隔时间,单位:小时
//                            .withRepeatCount(10)            // 设置重复次数
                .repeatForever())               // 永远执行
                .build();

        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();

        Thread.sleep(300*1000);

     catch (SchedulerException e) 
    	e.printStackTrace();
     catch (InterruptedException e) 
    	e.printStackTrace();
    


这里需要注意withRepeatCount()在调用时,如果指定执行10次,那么会执行11次。

如果设置了定时作业的结束时间,就可以不用考虑设置执行次数,只需要设置终止时间并将重复次数设置为REPEAT_INDEFINITELY

3.2 CronTrigger

CronTrigger通常比Simple Trigger更有用,如果您需要基于日历的概念而不是按照SimpleTrigger的精确指定间隔进行重新启动的作业启动计划。

使用CronTrigger,您可以指定号时间表,例如“每周五中午”或“每个工作日和上午9:30”,甚至“每周一至周五上午9:00至10点之间每5分钟”和1月份的星期五“。

即使如此,和SimpleTrigger一样,CronTrigger有一个startTime,它指定何时生效,以及一个(可选的)endTime,用于指定何时停止计划。

3.2.1 Cron表达式

Cron表达式由7位组成用空格分离,分别是秒、分、时、日、月、周、年。每一位都可以设置为频次、周期、循环、指定时间,灵活设定要执行的频率。

    • * 通配符,表示作业每秒执行一次;
    • 1-2 周期,表示从第1秒执行到第2秒,第一位为开始时间,第二位为结束时间;
    • 0/15 循环,表示从第0秒开始,每15秒执行一次,第一位为开始时间,第二位为多长时间执行一次;
    • 0,1,2 指定,表示每逢0秒,1秒,2秒时执行一次,将指定的时间点用,连接。
    • * 通配符,表示作业每分种执行一次;
    • 1-2 周期,表示从第1分种执行到第2分种,第一位为开始时间,第二位为结束时间;
    • 0/15 循环,表示从第0分种开始,每15分种执行一次,第一位为开始时间,第二位为多长时间执行一次;
    • 0,1,2 指定,表示每逢0分,1分,2分时执行一次,将指定的时间点用,连接。
    • * 通配符,表示作业每小时执行一次;
    • 1-2 周期,表示从1点开始执行到2点结束,第一位为开始时间,第二位为结束时间;
    • 0/1 循环,表示从0点开始,每1小时执行一次,第一位为开始时间,第二位为多长时间执行一次;
    • 0,1,2 指定,表示每逢0点,1点,2点时执行一次,将指定的时间点用,连接,采用24小时计时。
    • * 通配符,表示作业每日执行一次;
    • ? 不指定;
    • 1-2 周期,表示从1号开始执行到2号结束,第一位为开始时间,第二位为结束时间;
    • 1/1 循环,表示从1号开始,每1天执行一次,第一位为开始时间,第二位为多长时间执行一次;
    • 15W 工作日,表示最近给定日期的工作日(星期一至星期五);
    • L 每月的最后一天;
    • 1,2,3 指定,表示每逢1号,2号,3号时执行一次,将指定的时间点用,连接。
    • * 通配符,表示作业每月执行一次;
    • 1-2 周期,表示从1月开始执行到2月结束,第一位为开始时间,第二位为结束时间;
    • 1/1 循环,表示从1月开始,每1个月执行一次,第一位为开始时间,第二位为多长时间执行一次;
    • 1,2,3 指定,表示每逢1月,2月,3月时执行一次,将指定的时间点用,连接。
    • * 通配符,表示作业每周执行一次;
    • ? 不指定;
    • 1-2 周期,表示从星期一开始执行到星期二结束,第一位为开始时间,第二位为结束时间;
    • 1#2 指定周,表示本月的第2个星期一;
    • 1L 最后一个星期一,表示本月最后一个星期一,取值是1-7,超过7后进行循环;
    • 1,2 指定,表示每逢星期一、星期二执行一次,将指定的时间点用,连接。
    • * 通配符,表示作业每年执行一次;
    • ? 不指定;
    • 2022-2023 周期,表示从2022年开始执行到2023年结束,第一位为开始时间,第二位为结束时间。

具体可以使用一些在线的cron表达式编辑器设定。

@Test
public void testCronTriggerJob() throws Exception 
    Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

    JobDetail jobDetail = JobBuilder.newJob(DumbJob.class)
        .withIdentity("dumbJob", "group")
        .usingJobData("username", "Tom")
        .usingJobData("age", 18)
        .build();

    Trigger trigger = TriggerBuilder.newTrigger()
        .withIdentity("dumbJob", "group")
        // 设定每分钟的0秒时执行
        .withSchedule(CronScheduleBuilder.cronSchedule("0 * * * * ? "))
        .build();

    scheduler.scheduleJob(jobDetail, trigger);
    scheduler.start();

    Thread.sleep(300*1000);

    scheduler.shutdown();

3.3 优先级

如果你的trigger很多(或者Quartz线程池的工作线程太少),Quartz可能没有足够的资源同时触发所有的trigger;这种情况下,你可能希望控制哪些trigger优先使用Quartz的工作线程,要达到该目的,可以在trigger上设置priority属性。比如,你有N个trigger需要同时触发,但只有Z个工作线程,优先级最高的Z个trigger会被首先触发。如果没有为trigger设置优先级,trigger使用默认优先级,值为5;priority属性的值可以是任意整数,正数、负数都可以。

    @Test
    public void testPriorityJob() 
        try 
            ArrayList<Integer> arr = new ArrayListJavaQuartz定时作业的创建

如果 NodeMCU 定时器内的代码在我设置的定时器间隔内执行会发生啥?

ORACLE 定时运行存储过程经常使用时间间隔

java定时任务

oozie coordinator 定时调度

关于动态修改定时器的时间间隔