简体   繁体   English

仅使用Quartz运行一次作业

[英]Running a Job only once Using Quartz

Is there a way I could run a job only once using Quartz in Java? 有没有办法在Java中使用Quartz只运行一次作业? I understand it does not make sense to use Quartz in this case. 我知道在这种情况下使用Quartz是没有意义的。 But, the thing is, I have multiple jobs and they are run multiple times. 但事实是,我有多个工作,他们多次运行。 So, I am using Quartz. 所以,我正在使用Quartz。

Is this even possible? 这甚至可能吗?

You should use SimpleTrigger that fires at specific time and without repeating. 您应该使用在特定时间触发而不重复的SimpleTrigger。 TriggerUtils has many handy methods for creating these kind of things. TriggerUtils有许多方便的方法来创建这些东西。

In quartz > 2.0, you can get the scheduler to unschedule any job after work is done: 在quartz> 2.0中,您可以让调度程序在工作完成后取消计划任何作业:

@Override
protected void execute(JobExecutionContext context)
            throws JobExecutionException {
    ...
    // process execution
    ...
    context.getScheduler().unscheduleJob(triggerKey);
    ...
}

where triggerKey is the ID of the job to run only once. 其中triggerKey是仅运行一次的作业的ID。 After this, the job wouldn't be called anymore. 在此之后,不再调用该作业。

Yes, it's possible! 是的,这是可能的!

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

Here is an example of how to run a TestJob class immediately with Quartz 2.x : 以下是如何使用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;
}

see also Quartz Enterprise Job Scheduler TutorialsSimpleTriggers 另请参见Quartz Enterprise Job Scheduler教程SimpleTriggers

I'm not sure how much similar is Quartz in Mono and Java but this seems working in .Net 我不确定在Mono和Java中有多少相似的Quartz,但这似乎在.Net中有效

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

I had to ask myself if it made sense to try to configure a job and add checks if it had been run already as suggested in Marko Lahma's answer (since scheduling a job to run once results in it being run once, every time we start the app). 我不得不问自己是否有意义尝试配置一个工作并添加检查是否已按照Marko Lahma的回答中的建议运行(因为安排一个工作运行一次导致它运行一次,每次我们开始应用程序)。 I found examples of CommandLineRunner apps which didn't quite work for me, mostly because we already had an ApplicationRunner which was used for other jobs which use Quartz scheduling / cron. 我发现CommandLineRunner应用程序的例子并不适用于我,主要是因为我们已经有一个ApplicationRunner用于其他使用Quartz scheduling / cron的作业。 I wasn't happy with having Quartz initialize this job using a SimpleTrigger, so I had to find something else. 我不满意Quartz使用SimpleTrigger初始化这个工作,所以我必须找到别的东西。

Using some ideas from the following articles: 使用以下文章中的一些想法:

I was able to piece together a working implementation which allows me to do the following: 我能够将一个工作实现拼凑在一起,这使我可以执行以下操作:

  • run existing jobs via Quartz, on a timer 通过Quartz在计时器上运行现有作业
  • run new job, one time programmatically (single use Quartz job using the SimpleTrigger didn't satisfy my requirements, since it would be run once on every application load) 以编程方式运行一次新作业(单次使用SimpleTrigger的Quartz作业不满足我的要求,因为它会在每次应用程序加载时运行一次)

I came up with the following CommandLineRunner class: 我想出了以下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;
}
}

In one of my configuration classes I added a CommandLineRunner bean which calls the custom CommandLineRunner I created: 在我的一个配置类中,我添加了一个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;
    }
}

Later, I am able to initiate these jobs via the CLI without affecting my current Quartz scheduled jobs, and as long as no one runs the command via CLI multiple times, it will never be run again. 稍后,我可以通过CLI启动这些作业,而不会影响我当前的Quartz预定作业,只要没有人通过CLI多次运行命令,它就永远不会再运行。 I have to do some juggling of types since I accept ApplicationArguments, and then convert them into String[]. 因为我接受ApplicationArguments,然后将它们转换为String [],我必须做一些类型的杂耍。

Finally, I am able to call it like this: 最后,我可以这样称呼它:

java -jar <your_application>.jar --jobName=<QuartzRegisteredJobDetailFactoryBean>

The result is that the job is initialized only when I call it, and it is excluded from my CronTriggerFactoryBean triggers which I used for my other jobs. 结果是只有在我调用它时才初始化作业,并且它被排除在我用于其他作业的CronTriggerFactoryBean触发器之外。

There are several assumptions being made here, so I'll try to summarize: 这里有几个假设,所以我将总结一下:

  • the job must be registered as a JobDetailFactoryBean (eg: scheduler.setJobDetails(...) ) 该作业必须注册为JobDetailFactoryBean(例如: scheduler.setJobDetails(...)
  • everything is essentially the same as a job with CronTriggerFactoryBean, excepting the lacking scheduler.setTriggers(...) call 一切都与CronTriggerFactoryBean的工作基本相同,除了缺少scheduler.setTriggers(...)调用
  • Spring knows to execute the CommandLineRunner classes after the application has booted Spring知道在应用程序启动后执行CommandLineRunner类
  • I hardcoded the parameter being passed into the application to "jobName" 我将传递给应用程序的参数硬编码为“jobName”
  • I assumed a group name of "DEFAULT" for all jobs; 我为所有工作假设了一个名为“DEFAULT”的组名; if you want to use differing groups this would need to be adjusted when fetching JobKey, which is used to actually run the job 如果要使用不同的组,则需要在获取JobKey时进行调整,JobKey用于实际运行作业
  • there is nothing which prevents this job from being run multiple times via CLI, but it was triggered on every application load using SimpleTrigger approach, so this is better for me; 没有什么可以阻止这个作业通过CLI多次运行,但它是使用SimpleTrigger方法在每个应用程序加载时触发的,所以这对我来说更好; if this is not acceptable, perhaps using StepListener and ExitStatus, etc. can prevent it from being executed twice 如果这是不可接受的,可能使用StepListener和ExitStatus等可以防止它被执行两次

Another solution: There is a method .withRepeatCount(0) in SimpleSchedulerBuilder: 另一种解决方案: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();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM