使用Quartz仅运行一次作业

15

我能否在Java中使用Quartz只运行一次任务?我知道在这种情况下使用Quartz没有意义。但问题是,我有多个作业需要执行多次,所以我正在使用Quartz。

这个可能吗?

7个回答

20

你应该使用SimpleTrigger,它会在特定时间触发且不会重复。TriggerUtils有许多方便的方法可以创建这些类型的触发器。


3
所有方便的方法在2.0版本中都不存在了。你知道它们现在在哪里吗? - Jens Schauder
1
@JensSchauder 看起来这些都移动到了 ScheduleBuilder 的各个子类中,例如 CronScheduleBuilder。设置方式有点混乱。因为你必须使用 TiggerBuilder 来构建触发器,并通过 withSchedule 方法传递一个包含触发器的日程安排,其中日程安排本身就是一个触发器。 - ams
1
使用SimpleTrigger的作业每次应用程序启动时只会运行一次,不会持续轮询,这是好的,但它将在每次应用程序运行时运行,导致额外的工作以确保不再运行非幂等操作。 - Andrew Cotton
您可以像调度程序一样仅触发作业,例如TriggerJob。如果您将此类作业配置为在应用程序启动时运行,则它当然会运行。这完全取决于配置。 - Marko Lahma

10

可以的!

JobKey jobKey = new JobKey("testJob");
JobDetail job = newJob(TestJob.class)
            .withIdentity(jobKey)
            .storeDurably()
            .build();
scheduler.addJob(job, true);
scheduler.triggerJob(jobKey); //trigger a job inmediately

6
在Quartz 2.0以上版本中,您可以让调度程序在工作完成后取消任何作业:
@Override
protected void execute(JobExecutionContext context)
            throws JobExecutionException {
    ...
    // process execution
    ...
    context.getScheduler().unscheduleJob(triggerKey);
    ...
}

其中triggerKey是仅运行一次的作业的ID。之后,该作业将不再被调用。


5

以下是如何使用Quartz 2.x立即运行TestJob类的示例:

public JobKey runJob(String jobName)
{
    // if you don't call startAt() then the current time (immediately) is assumed.
    Trigger runOnceTrigger = TriggerBuilder.newTrigger().build();
    JobKey jobKey = new JobKey(jobName);
    JobDetail job = JobBuilder.newJob(TestJob.class).withIdentity(jobKey).build();
    scheduler.scheduleJob(job, runOnceTrigger);
    return jobKey;
}

另请参阅Quartz企业作业调度器教程SimpleTriggers


3
我不得不问自己,是否有意义尝试配置作业并添加检查,如果像Marko Lahma的答案建议的那样已经运行过了(因为将作业安排为仅运行一次会导致每次启动应用程序时都运行它)。我找到了一些CommandLineRunner应用程序的示例,但它们对我来说并不完全有效,主要是因为我们已经有一个ApplicationRunner,用于其他使用Quartz调度/ cron的作业。我对使用SimpleTrigger初始化此作业的Quartz并不满意,所以我必须找到其他东西。

使用以下文章中的一些想法:

我成功地拼凑出了一个工作实现,使我能够执行以下操作:

  • 通过Quartz定时器运行现有的作业
  • 编程运行新的单次任务(使用SimpleTrigger的一次性Quartz任务不能满足我的要求,因为它会在每次应用程序加载时运行一次)

我想到了以下CommandLineRunner类:

public class BatchCommandLineRunner implements CommandLineRunner {

@Autowired
private Scheduler scheduler;

private static final Logger LOGGER = LoggerFactory.getLogger(BatchCommandLineRunner.class);

public void run(final String... args) throws SchedulerException {

    LOGGER.info("BatchCommandLineRunner: running with args -> " + Arrays.toString(args));

    for (final String jobName : args) {

        final JobKey jobKey = findJobKey(jobName);
        if (jobKey != null) {

            LOGGER.info("Triggering job for: " + jobName);
            scheduler.triggerJob(jobKey);

        } else {

            LOGGER.info("No job found for jobName: " + jobName);
        }

    }
}

private JobKey findJobKey(final String jobNameToFind) throws SchedulerException {

    for (final JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals("DEFAULT"))) {

        final String jobName = jobKey.getName();

        if (jobName.equals(jobNameToFind)) {

            return jobKey;
        }
    }
    return null;
}
}

在我的一个配置类中,我添加了一个CommandLineRunner bean,它调用了我创建的自定义CommandLineRunner:
@Configuration
public class BatchConfiguration {

    private static final Logger LOGGER = LoggerFactory.getLogger(BatchConfiguration.class);

    @Bean
    public BatchCommandLineRunner batchCommandLineRunner() {

        return new BatchCommandLineRunner();
    }

    @Bean
    public CommandLineRunner runCommandLineArgs(final ApplicationArguments applicationArguments) throws Exception {

        final List<String> jobNames = applicationArguments.getOptionValues("jobName");

        LOGGER.info("runCommandLineArgs: running the following jobs -> " + ArrayUtils.toString(jobNames));

        batchCommandLineRunner().run(jobNames.toArray(ArrayUtils.EMPTY_STRING_ARRAY));

        return null;
    }
}

后来,我可以通过CLI启动这些作业,而不会影响我的当前Quartz计划的作业,并且只要没有人通过CLI多次运行命令,它就永远不会再次运行。由于我接受ApplicationArguments并将它们转换为String[],所以需要进行一些类型方面的调整。
最后,我可以像这样调用它:
java -jar <your_application>.jar --jobName=<QuartzRegisteredJobDetailFactoryBean>

结果是只有在调用该作业时初始化,它被排除在我用于其他作业的CronTriggerFactoryBean触发器之外。
这里做出了几个假设,因此我将尝试总结一下:
- 该作业必须注册为JobDetailFactoryBean(例如:scheduler.setJobDetails(...) ) - 除缺少scheduler.setTriggers(...) 调用外,一切都与CronTriggerFactoryBean的作业基本相同 - Spring知道在应用程序启动后执行CommandLineRunner类 - 我将传递到应用程序中的参数硬编码为“jobName” - 我假定所有作业的组名为“DEFAULT”;如果您想使用不同的组,则在获取用于实际运行作业的JobKey时需要进行调整 - 没有任何内容可以防止通过CLI多次运行此作业,但是它使用SimpleTrigger方法在每次应用程序加载时触发,因此对我来说更好;如果这不可接受,可能使用StepListener和ExitStatus等可以防止它被执行两次。

3

我不确定Mono和Java中的Quartz有多相似,但这似乎在.Net中可以工作。

TriggerBuilder.Create ()
        .StartNow ()
        .Build (); 

这在Java中也可以工作,但我认为OP要求一个特定的时间,而不是now() - jmizv

1
另一种解决方案:在SimpleSchedulerBuilder中有一个方法.withRepeatCount(0)
public final int TEN_SECONDS = 10;
Trigger trigger = newTrigger()
    .withIdentity("myJob", "myJobGroup")
    .startAt(new Date(System.currentMillis()+TEN_SECONDS*1000)
    .withSchedule(SimpleScheduleBuilder.simpleSchedule()
      .withRepeatCount(0)
      .withIntervalInMinutes(1))
    .build();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接