Quartz 定时任务使用 —— Quartz Job Scheduler经典案例(十七)

Quartz Cookbook是Quartz的一些简洁的代码示例,用于做特殊的事情。

目录:

这些示例假定您已经使用Quartz的DSL类的静态导入,例如:

import static org.quartz.JobBuilder.*;
import static org.quartz.TriggerBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.CalendarIntervalScheduleBuilder.*;
import static org.quartz.JobKey.*;
import static org.quartz.TriggerKey.*;
import static org.quartz.DateBuilder.*;
import static org.quartz.impl.matchers.KeyMatcher.*;
import static org.quartz.impl.matchers.GroupMatcher.*;
import static org.quartz.impl.matchers.AndMatcher.*;
import static org.quartz.impl.matchers.OrMatcher.*;
import static org.quartz.impl.matchers.EverythingMatcher.*;

参考地址:http://www.quartz-scheduler.org/documentation/quartz-2.2.x/cookbook/

1、实例化调度器

实例化默认调度器

// “默认”调度程序在当前工作目录中的“quartz.properties”中定义,在类路径中定义,或者在quartz.jar中默认定义
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();

// 调度程序在启动之前不会执行任务(尽管可以在start()之前进行调度)
scheduler.start();

从特定属性实例化特定的调度程序   

StdSchedulerFactory sf = new StdSchedulerFactory();
sf.initialize(schedulerProperties);
Scheduler scheduler = sf.getScheduler();

scheduler.start();

从特定属性文件中实例化特定调度程序

StdSchedulerFactory sf = new StdSchedulerFactory();
sf.initialize(fileName);
Scheduler scheduler = sf.getScheduler();

scheduler.start();

2、将计划程序置于待机模式

// start() was previously invoked on the scheduler
scheduler.standby();
// 现在调度程序将不会触发/执行job
// ...
scheduler.start();
// 现在调度程序将触发并执行job

3、关闭计划程序

要关闭/销毁调度程序,只需调用shutdown(..)方法之一。

关闭调度程序后,无法重新启动(因为线程和其他资源被永久销毁)。 如果您想简单地暂停调度程序一段时间,请参阅suspend方法。

等待执行作业完成

  //shutdown() 在执行作业完成执行之前不返回
  scheduler.shutdown(true);

不要等待执行作业完成

  //shutdown() 立即返回,但执行作业继续运行到完成
  scheduler.shutdown(); 
  //或者 
  scheduler.shutdown(false);

If you are using the org.quartz.ee.servlet.QuartzInitializerListener to fire up a scheduler in your servlet container, its contextDestroyed() method will shutdown the scheduler when your application is undeployed or the application server shuts down (unless its shutdown-on-unload property has been explicitly set to false).

如果您正在使用org.quartz.ee.servlet.QuartzInitializerListener来启动servlet容器中的调度程序,那么当您的应用程序取消部署或应用程序服务器关闭时,其contextDestroyed()方法将关闭调度程序(除非其shutdown-on-unload属性已被明确设置为false)

4、在Servlet容器内初始化调度程序

有两种方法,如下所示。

对于这两种情况,请确保查看相关类的JavaDOC以查看所有可能的配置参数,因为下面并非完整示例。

添加Context/Container Listene 到 web.xml

...
     <context-param>
         <param-name>quartz:config-file</param-name>
         <param-value>/some/path/my_quartz.properties</param-value>
     </context-param>
     <context-param>
         <param-name>quartz:shutdown-on-unload</param-name>
         <param-value>true</param-value>
     </context-param>
     <context-param>
         <param-name>quartz:wait-on-shutdown</param-name>
         <param-value>false</param-value>
     </context-param>
     <context-param>
         <param-name>quartz:start-scheduler-on-load</param-name>
         <param-value>true</param-value>
     </context-param>
...
     <listener>
         <listener-class>
             org.quartz.ee.servlet.QuartzInitializerListener
         </listener-class>
     </listener>
...

将启动Servlet添加到web.xml

...
    <servlet>
      <servlet-name>QuartzInitializer</servlet-name>
      <servlet-class>org.quartz.ee.servlet.QuartzInitializerServlet</servlet-class>
      <init-param>
        <param-name>shutdown-on-unload</param-name>
        <param-value>true</param-value>
      </init-param>
      <load-on-startup>2</load-on-startup>
    </servlet>
...

5、利用多个(非群集)调度器实例

看原文:http://www.quartz-scheduler.org/documentation/quartz-2.2.x/cookbook/MultipleSchedulers.html

6、定义一个Job(包含输入数据)

job.class

public class PrintPropsJob implements Job {
    public PrintPropsJob() {
       // Job的实例必须有一个public的无参数构造函数。
    }
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap data = context.getMergedJobDataMap();
        System.out.println("someProp = " + data.getString("someProp"));
    }
}

定义job实例

// Define job instance
JobDetail job1 = newJob(MyJobClass.class)
    .withIdentity("job1", "group1")
    .usingJobData("someProp", "someValue")
    .build();

另请注意,如果您的Job类包含与您的JobDataMap键匹配的setter方法(例如上述示例中的数据为“setSomeProp”),并且您使用默认的JobFactory实现,则Quartz将使用JobDataMap值自动调用setter方法, 并且没有必要在Job的执行方法中具有从JobDataMap中检索值的代码。

参考文章:Quartz 定时任务使用 —— 参数传递(六)

7、Scheduling job

// 定义job实例
JobDetail job1 = newJob(ColorJob.class)
    .withIdentity("job1", "group1")
    .build();

// 定义一个触发器,立即触发,不重复
Trigger trigger = newTrigger()
    .withIdentity("trigger1", "group1")
    .startNow()
    .build();

// 安排与触发任务
sched.scheduleJob(job, trigger);

8、Unscheduling Job

Unscheduling特定触发器的job

// 从job中取消特定的触发器(一个job可能有多个触发器)
scheduler.unscheduleJob(triggerKey("trigger1", "group1"));

删除一个job并且Unscheduling所有相关此job的触发器

// Schedule the job with the trigger
scheduler.deleteJob(jobKey("job1", "group1"));

9、存储 job 任务

Storing(存储) a Job

// 定义持久的作业实例(持久作业可以不存在触发器)
JobDetail job1 = newJob(MyJobClass.class)
    .withIdentity("job1", "group1")
    .storeDurably()
    .build();

// 将job添加到调度程序去存储
sched.addJob(job, false);

10、调度一个已经存储过的 Job 任务

// Define a Trigger that will fire "now" and associate it with the existing job
Trigger trigger = newTrigger()
    .withIdentity("trigger1", "group1")
    .startNow()
    .forJob(jobKey("job1", "group1"))
    .build();

// 调度关联触发器
sched.scheduleJob(trigger);

11、更新已有 Job 任务

// Add the new job to the scheduler, instructing it to "replace" the existing job with the given name and group (if any)
JobDetail job1 = newJob(MyJobClass.class)
    .withIdentity("job1", "group1")
    .build();

// store, 设置覆盖flag为 'true'     
scheduler.addJob(job1, true);

12、更新已有 Tragger 触发器

替换触发器

// 定义一个新的触发器
Trigger trigger = newTrigger()
    .withIdentity("newTrigger", "group1")
    .startNow()
    .build();

// 告诉调度程序使用给定的键删除旧的触发器,并将新的触发器放在其位置
sched.rescheduleJob(triggerKey("oldTrigger", "group1"), trigger);

更新已有触发器

// 检索触发器
Trigger oldTrigger = sched.getTrigger(triggerKey("oldTrigger", "group1");

// 获取产生触发器的构建器
TriggerBuilder tb = oldTrigger.getTriggerBuilder();

// 更新与构建器关联的计划,并构建新的触发器(可以调用其他构建器方法,以任何所需的方式更改触发器)
Trigger newTrigger = tb.withSchedule(simpleSchedule()
    .withIntervalInSeconds(10)
    .withRepeatCount(10)
    .build();

sched.rescheduleJob(oldTrigger.getKey(), newTrigger);

13、使用XML文件中定义的Job和Trigger初始化调度程序

您可以使用XMLSchedulingDataProcessorPlugin(其中1.8版本替换旧的JobInitializationPlugin)来初始化具有预定义作业和触发器的调度程序。 在示例/ example10中的Quartz分布中提供了一个示例。 但是,以下是插件的简单描述。

首先,我们需要在调度器属性中明确指定我们要使用XMLSchedulingDataProcessorPlugin。 这是一个例子的摘录quartz.properties:

#===================================================
# Configure the Job Initialization Plugin
#===================================================
org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin
org.quartz.plugin.jobInitializer.fileNames = jobs.xml
org.quartz.plugin.jobInitializer.failOnFileNotFound = true
org.quartz.plugin.jobInitializer.scanInterval = 10
org.quartz.plugin.jobInitializer.wrapInUserTransaction = false

让我们看看每个属性的作用:

  • fileNames:逗号分隔的文件名列表(带路径)。 这些文件包含作业和关联触发器的xml定义。 稍后我们将看到一个示例jobs.xml定义。

  • failOnFileNotFound:如果没有找到xml定义文件,插件是否会引发异常,从而防止自身(插件)初始化?

  • scanInterval:如果检测到文件更改,则可以重新加载xml定义文件。 这是文件查看的时间间隔(以秒为单位)。 设置为0以禁用扫描。

  • wrapInUserTransaction:如果使用JobStoreCMT的XMLSchedulingDataProcessorPlugin,请确保将此属性的值设置为true,否则可能会遇到意外行为。

jobs.xml文件(或在fileNames属性中为其使用的任何其他名称)声明性地定义作业和触发器。 它还可以包含删除现有数据的指令。 这是一个不言自明的例子:

<?xml version='1.0' encoding='utf-8'?>
<job-scheduling-data xmlns="http://www.quartz-scheduler.org/xml/JobSchedulingData"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.quartz-scheduler.org/xml/JobSchedulingData http://www.quartz-scheduler.org/xml/job_scheduling_data_1_8.xsd"
  version="1.8">
    <schedule>
        <job>
            <name>my-very-clever-job</name>
            <group>MYJOB_GROUP</group>
            <description>The job description</description>
            <job-class>com.acme.scheduler.job.CleverJob</job-class>
            <job-data-map allows-transient-data="false">
                <entry>
                    <key>burger-type</key>
                    <value>hotdog</value>
                </entry>
                <entry>
                    <key>dressing-list</key>
                    <value>ketchup,mayo</value>
                </entry>
            </job-data-map>
        </job>
        <trigger>
            <cron>
                <name>my-trigger</name>
                <group>MYTRIGGER_GROUP</group>
                <job-name>my-very-clever-job</job-name>
                <job-group>MYJOB_GROUP</job-group>
                <!-- trigger every night at 4:30 am -->
                <!-- do not forget to light the kitchen's light -->
                <cron-expression>0 30 4 * * ?</cron-expression>
            </cron>
        </trigger>
    </schedule>
</job-scheduling-data>

另一个jobs.xml示例在Quartz发行版的examples / example10目录中。

检查XML Schema,了解可能的全部细节。

14、在Scheduler中列出所有Job作业

// enumerate each job group
for(String group: sched.getJobGroupNames()) {
    // enumerate each job in group
    for(JobKey jobKey : sched.getJobKeys(groupEquals(group))) {
        System.out.println("Found job identified by: " + jobKey);
    }
}

15、在Scheduler中列出所有Trigger触发器

// enumerate each trigger group
for(String group: sched.getTriggerGroupNames()) {
    // enumerate each trigger in group
    for(TriggerKey triggerKey : sched.getTriggerKeys(groupEquals(group))) {
        System.out.println("Found trigger identified by: " + triggerKey);
    }
}

16、查找一个Job关联的所有Trigger触发器

List<Trigger> jobTriggers = sched.getTriggersOfJob(jobKey("jobName", "jobGroup"));

17、使用JobListeners

创建JobListener,实现 JobListener 接口。

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
public class MyJobListener implements JobListener {
    private String name;
    public MyJobListener(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void jobToBeExecuted(JobExecutionContext context) {
        // do something with the event
    }
    public void jobWasExecuted(JobExecutionContext context,
            JobExecutionException jobException) {
        // do something with the event
    }
    public void jobExecutionVetoed(JobExecutionContext context) {
        // do something with the event
    }
}

或者继承 JobListenerSupport。

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.listeners.JobListenerSupport;
public class MyOtherJobListener extends JobListenerSupport {
    private String name;
    public MyOtherJobListener(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
        @Override
    public void jobWasExecuted(JobExecutionContext context,
            JobExecutionException jobException) {
        // do something with the event
    }
}

Registering A JobListener With The Scheduler To Listen To All Jobs(所有Job)

scheduler.getListenerManager().addJobListener(myJobListener, allJobs());

Registering A JobListener With The Scheduler To Listen To A Specific Job(特定Job)

scheduler.getListenerManager().addJobListener(myJobListener, jobKeyEquals(jobKey("myJobName", "myJobGroup")));

Registering A JobListener With The Scheduler To Listen To All Jobs In a Group(特定组的所有Job)

scheduler.getListenerManager().addJobListener(myJobListener, jobGroupEquals("myJobGroup"));

参考阅读文章:Quartz 定时任务使用 —— JobListener、Triggerlistener、SchedulerListener(十三)

18、使用TriggerListeners

创建一个TriggerListener,实现 TriggerListener 接口

import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.TriggerListener;
import org.quartz.Trigger.CompletedExecutionInstruction;
public class MyTriggerListener implements TriggerListener {
    private String name;
    public MyTriggerListener(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void triggerComplete(Trigger trigger, JobExecutionContext context,
            CompletedExecutionInstruction triggerInstructionCode) {
        // do something with the event
    }
    public void triggerFired(Trigger trigger, JobExecutionContext context) {
        // do something with the event
    }
    public void triggerMisfired(Trigger trigger) {
        // do something with the event
    }
    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
        // do something with the event
        return false;
    }
}

或者继承 TriggerListenerSupport

import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.listeners.TriggerListenerSupport;
public class MyOtherTriggerListener extends TriggerListenerSupport {
    private String name;
    public MyOtherTriggerListener(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    @Override
    public void triggerFired(Trigger trigger, JobExecutionContext context) {
        // do something with the event
    }
}

Registering A TriggerListener With The Scheduler To Listen To All Triggers(所有触发器)

scheduler.getListenerManager().addTriggerListener(myTriggerListener, allTriggers());

Registering A TriggerListener With The Scheduler To Listen To A Specific Trigger(特定触发器)

scheduler.getListenerManager()
    .addTriggerListener(myTriggerListener, triggerKeyEquals(triggerKey("myTriggerName", "myTriggerGroup")));

Registering A TriggerListener With The Scheduler To Listen To All Triggers In a Group(特定组的所有触发器)

scheduler.getListenerManager().addTriggerListener(myTriggerListener, triggerGroupEquals("myTriggerGroup"));

参考阅读文章:Quartz 定时任务使用 —— JobListener、Triggerlistener、SchedulerListener(十三)

19、使用SchedulerListeners

创建一个SchedulerListener,继承TriggerListenerSupport,重写您感兴趣的事件的方法。

import org.quartz.Trigger;
import org.quartz.listeners.SchedulerListenerSupport;
public class MyOtherSchedulerListener extends SchedulerListenerSupport {
    @Override
    public void schedulerStarted() {
        // do something with the event
    }
    @Override
    public void schedulerShutdown() {
        // do something with the event
    }
    @Override
    public void jobScheduled(Trigger trigger) {
        // do something with the event
    }
}

注册SchedulerListener

scheduler.getListenerManager().addSchedulerListener(mySchedListener);

参考阅读文章:Quartz 定时任务使用 —— JobListener、Triggerlistener、SchedulerListener(十三)

20、触发器每10秒触发一次

使用 SimpleTrigger

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startNow()
    .withSchedule(simpleSchedule()
            .withIntervalInSeconds(10)
            .repeatForever())
    .build();

21、触发器每90分钟触发一次

使用 SimpleTrigger

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startNow()
    .withSchedule(simpleSchedule()
            .withIntervalInMinutes(90)
            .repeatForever())
    .build();

使用 CalendarIntervalTrigger

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startNow()
    .withSchedule(calendarIntervalSchedule()
            .withIntervalInMinutes(90))
    .build();

22、每天触发的触发器

如果您希望在一天中的特定时间内始终触发触发器,请使用CronTrigger或CalendarIntervalTrigger,因为它们可以通过夏令时间来保留更改

使用 CronTrigger

创建一个CronTrigger。 每天下午3:00执行:

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startNow()
    .withSchedule(dailyAtHourAndMinute(15, 0)) // fire every day at 15:00
    .build();

使用 SimpleTrigger

创建一个SimpleTrigger,执行明天3:00 PM,然后每24小时执行一次(可能并不总是在下午3:00),因为在夏令时可能导致下午2:00或下午4:00的24小时。 取决于3:00 PM时间是否在DST或标准时间内启动):

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startAt(tomorrowAt(15, 0, 0)  // first fire time 15:00:00 tomorrow
    .withSchedule(simpleSchedule()
            .withIntervalInHours(24) // interval is actually set at 24 hours' worth of milliseconds
            .repeatForever())
    .build();

使用 CalendarIntervalTrigger

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startAt(tomorrowAt(15, 0, 0)  // 15:00:00 tomorrow
    .withSchedule(calendarIntervalSchedule()
            .withIntervalInDays(1)) // interval is set in calendar days
    .build();

23、触发器每2天触发一次

乍一看,您可能会试图使用CronTrigger。 但是,如果这是真正的每两天,CronTrigger将无法工作。 为了说明这一点,只要想一个典型的月份(28-31)几天。 像“0 0 5 2/2 *?”这样的cron表达式将给我们一个触发器,它将在每个月初重新开始计数。 这意味着我们将在7月30日和8月2日之间接下来的发射,这是三天的时间间隔,而不是两次。

同样,像“0 0 5 1/2 *?”一样的表达式将在7月31日和8月1日结束,只有一天的时间。

因此,对于这个时间表,使用SimpleTrigger或CalendarIntervalTrigger是有意义的:

使用 SimpleTrigger

创建一个SimpleTrigger,明天3:00 PM,然后每48小时执行一次(可能并不总是在3:00 PM),因为在夏令时可能导致2:00 PM或4:00 PM的24小时 取决于3:00 PM时间是否在DST或标准时间内启动):

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startAt(tomorrowAt(15, 0, 0)  // first fire time 15:00:00 tomorrow
    .withSchedule(simpleSchedule()
            .withIntervalInHours(2 * 24) // interval is actually set at 48 hours' worth of milliseconds
            .repeatForever())
    .build();

使用 CalendarIntervalTrigger

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startAt(tomorrowAt(15, 0, 0)  // 15:00:00 tomorrow
    .withSchedule(calendarIntervalSchedule()
            .withIntervalInDays(2)) // interval is set in calendar days
    .build();

24、每周触发的触发器

如果您想要在一天中的某个时间点始终触发的触发器,请使用CronTrigger或CalendarIntervalTrigger,因为它们可以通过夏令时来保留更改

使用 CronTrigger

创建一个CronTrigger。 每星期三下午3:00执行:

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startNow()
    .withSchedule(weeklyOnDayAndHourAndMinute(DateBuilder.WEDNESDAY, 15, 0)) // fire every wednesday at 15:00
    .build();

或者

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startNow()
    .withSchedule(cronSchedule("0 0 15 ? * WED")) // fire every wednesday at 15:00
    .build();

使用 SimpleTrigger

创建一个SimpleTrigger,执行明天3:00 PM,然后每7 * 24小时执行一次(可能不总是在3:00 PM),因为在夏令时可能导致2:00 PM或4: 00 PM取决于是否在DST或标准时间内启动3:00 PM时间):

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startAt(tomorrowAt(15, 0, 0)  // first fire time 15:00:00 tomorrow
    .withSchedule(simpleSchedule()
            .withIntervalInHours(7 * 24) // interval is actually set at 7 * 24 hours' worth of milliseconds
            .repeatForever())
    .build();

使用 CalendarIntervalTrigger

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startAt(tomorrowAt(15, 0, 0)  // 15:00:00 tomorrow
    .withSchedule(calendarIntervalSchedule()
            .withIntervalInWeeks(1)) // interval is set in calendar weeks
    .build();

25、触发器每2周触发一次

由于触发器意味着每两天触发一次,CronTrigger将不会为此计划工作。 有关详细信息,请参阅上面触发器每2天触发一次。 我们需要使用SimpleTrigger或CalendarIntervalTrigger:

使用 SimpleTrigger

创建一个SimpleTrigger,明天3:00 PM,然后每48小时执行一次(可能并不总是在3:00 PM),因为在夏令时可能导致2:00 PM或4:00 PM的24小时 取决于3:00 PM时间是否在DST或标准时间内启动):

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startAt(tomorrowAt(15, 0, 0)  // first fire time 15:00:00 tomorrow
    .withSchedule(simpleSchedule()
            .withIntervalInHours(14 * 24) // interval is actually set at 14 * 24 hours' worth of milliseconds
            .repeatForever())
    .build();

使用 CalendarIntervalTrigger

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startAt(tomorrowAt(15, 0, 0)  // 15:00:00 tomorrow
    .withSchedule(calendarIntervalSchedule()
            .withIntervalInWeeks(2)) // interval is set in calendar weeks
    .build();

26、触发器每月触发

使用 CronTrigger

创建一个CronTrigger。 每星期三下午3:00执行:

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startNow()
    .withSchedule(monthlyOnDayAndHourAndMinute(5, 15, 0)) // fire on the 5th day of every month at 15:00
    .build();

或者

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startNow()
    .withSchedule(cronSchedule("0 0 15 5 * ?")) // fire on the 5th day of every month at 15:00
    .build();

或者

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startNow()
    .withSchedule(cronSchedule("0 0 15 L * ?")) // fire on the last day of every month at 15:00
    .build();

或者

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startNow()
    .withSchedule(cronSchedule("0 0 15 LW * ?")) // fire on the last weekday day of every month at 15:00
    .build();

或者

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startNow()
    .withSchedule(cronSchedule("0 0 15 L-3 * ?")) // fire on the third to last day of every month at 15:00
    .build();

还有其他可能的组合,这些组合在API文档中有更全面的介绍。 所有这些选项都是通过简单地更改月份字段来进行的。 想象一下,如果您利用其他领域,您可以做什么?

使用 CalendarIntervalTrigger

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .startAt(tomorrowAt(15, 0, 0)  // 15:00:00 tomorrow
    .withSchedule(calendarIntervalSchedule()
            .withIntervalInMonths(1)) // interval is set in calendar months
    .build();


赞(52) 打赏
未经允许不得转载:优客志 » JAVA开发
分享到:

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏