【问题标题】:Running a Job only once Using Quartz使用 Quartz 只运行一次作业
【发布时间】:2011-02-12 05:23:58
【问题描述】:

有没有一种方法可以让我在 Java 中使用 Quartz 只运行一次作业?我知道在这种情况下使用 Quartz 是没有意义的。但是,问题是,我有多个工作并且它们运行多次。所以,我正在使用 Quartz。

这可能吗?

【问题讨论】:

    标签: java quartz-scheduler


    【解决方案1】:

    您应该使用在特定时间触发且不重复的 SimpleTrigger。 TriggerUtils 有很多方便的方法来创建这些东西。

    【讨论】:

    • 所有方便的方法都在 2.0 版中消失了。知道他们现在在哪里吗?
    • @JensSchauder 看起来这些都移到了 ScheduleBuilder 的各个子项中,例如 CronScheduleBuilder。设置的方式有点混乱。因为您必须使用 TiggerBuilder 来构建触发器,并使用 withSchedule 方法向它传递一个计划,其中计划本身就是一个触发器。
    • 每次启动应用程序时,使用 SimpleTrigger 的作业不会运行一次吗?它不会持续轮询,这很好,但是,它会在每次运行应用程序时运行,从而导致额外的工作以确保不会再次运行非幂等操作。
    • 只能触发一次作业,如 scheduler.TriggerJob。如果您将此类作业配置为在应用程序启动时运行,它当然会运行。这一切都取决于配置。
    【解决方案2】:

    是的,有可能!

    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
    

    【讨论】:

      【解决方案3】:

      在quartz > 2.0中,你可以让调度器在工作完成后取消调度任何工作:

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

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

      【讨论】:

        【解决方案4】:

        这是一个如何使用 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 Enterprise Job Scheduler TutorialsSimpleTriggers

        【讨论】:

          【解决方案5】:

          我不确定 Mono 和 Java 中的 Quartz 有多少相似之处,但这似乎在 .Net 中有效

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

          【讨论】:

            【解决方案6】:

            我不得不问自己,尝试配置作业并添加检查是否已按照 Marko Lahma 的回答中的建议运行是否有意义(因为安排作业运行一次会导致它每次运行一次我们启动应用程序)。我发现了一些对我来说不太适用的 CommandLineRunner 应用程序示例,主要是因为我们已经有一个 ApplicationRunner 用于其他使用 Quartz 调度/cron 的作业。我对让 Quartz 使用 SimpleTrigger 初始化这项工作感到不满意,所以我必须找到其他东西。

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

            我能够拼凑出一个有效的实现,它允许我执行以下操作:

            • 通过 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 等可以防止它被执行两次

            【讨论】:

              【解决方案7】:

              另一种解决方案: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();
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2011-06-20
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2017-02-15
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多