webentwicklung-frage-antwort-db.com.de

Wie starte ich einen geplanten Spring Batch Job?

Ich möchte in der Lage sein, meinen Job mit einem REST -Controller zu starten. Wenn der Job gestartet wird, sollte er nach Zeitplan ausgeführt werden, bis ich ihn mit REST wieder anhalte.

Das ist also mein Controller:

@RestController
public class LauncherController {

    @Autowired
    JobLauncher jobLauncher;

    @Autowired
    Job job;

    @RequestMapping("/launch")
    public String launch() throws Exception {
             ...
            jobLauncher.run(job, jobParameters);
    }

Dies ist ein Teil des Batch-Conf:

@Configuration
@EnableBatchProcessing
@EnableScheduling
public class BatchConfiguration {

    @Autowired
    public JobBuilderFactory jobBuilderFactory;

    @Autowired
    public StepBuilderFactory stepBuilderFactory;

    @Scheduled(cron = "0/5 * * * * ?")
    @Bean
    public Job job() {
        return jobBuilderFactory.get("job")
                .incrementer(new RunIdIncrementer())
                .flow(step1())
                .end()
                .build();
    }

    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                .<Person, Person> chunk(10)
                .reader(reader())
                .processor(processor())
                .writer(writer())
                .build();
    }

Ich habe auch die Eigenschaft spring.batch.job.enabled = false festgelegt, da die Jobs nicht ausgeführt werden sollen, sobald die Spring Boot App gestartet wird.

Jetzt kann ich meinen Rest api lauch anrufen und der Job läuft, aber nur einmal. Scheduler funktioniert nicht. Und ich konnte nicht herausfinden, wo genau ich meine @ Scheduled Annotation definieren sollte. 

8
akcasoy

Ich würde so vorgehen, dass geplante Jobs immer ausgeführt werden, aber nur dann, wenn das Flag auf true gesetzt ist:

@Component
class ScheduledJob {

    private final AtomicBoolean enabled = new AtomicBoolean(false);

    @Scheduled(fixedRate = 1000)
    void execute() {
        if (enabled.get()) {
            // run spring batch here.
        }
    }

    void toggle() {
        enabled.set(!enabled.get());
    }

}

und ein Controller:

@RestController
class HelloController {

    private final ScheduledJob scheduledJob;

    // constructor

    @GetMapping("/launch")
    void toggle() {
        scheduledJob.toggle();
    }

}
11

Zunächst definieren Sie den Job:

@Bean
@Qualifier("fancyScheduledJob")
public Job job() {
    return jobBuilderFactory.get("job")
            .incrementer(new RunIdIncrementer())
            .flow(step1())
            .end()
            .build();
}

Zweitens initiieren Sie die Ausführung dieses Jobs: 

@Autowired
@Qualifier(value = "fancyScheduledJob")
private Job job;

@Autowired
private JobLauncher jobLauncher;

@Scheduled(cron = "0/5 * * * * ?")
public void launch() throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, JobInstanceAlreadyExistsException, NoSuchJobException {

    jobLauncher.run(job, JobParametersBuilder()
            .addLong("launchTime", System.currentTimeMillis())
            .toJobParameters())
}

Beachten Sie auch, dass der Parameter "launchTime" eingeführt wird: Standardmäßig verhindert Spring Batch, dass der Job mit denselben Parameterwerten gestartet wird. 

Ihr Zeitplan ist zwar sehr eng - alle 5 Sekunden sollten Sie sich der Parallelität bewusst sein. Oder wenn Sie sicher sein möchten, dass zu jedem Zeitpunkt nur eine Instanz des Jobs ausgeführt wird, können Sie ein benutzerdefiniertes Startprogramm für einen einzelnen Thread konfigurieren: 

@Bean(name = "fancyJobExecutorPool")
public TaskExecutor singleThreadedJobExecutorPool() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(1);
    executor.setMaxPoolSize(1);
    executor.setQueueCapacity(100500);
    executor.setThreadNamePrefix("fancy-job-batch-");
    return executor;
}

@Bean(name = "fancyJobLauncher")
public JobLauncher singleThreadedJobLauncher(JobRepository jobRepository)
{
    SimpleJobLauncher sjl = new SimpleJobLauncher();
    sjl.setJobRepository(jobRepository);
    sjl.setTaskExecutor(singleThreadedJobExecutorPool());
    return sjl;
}

Verwenden Sie diesen Jobstarter für einzelne Threads während der Startzeit.

@Autowired
@Qualifier("fancyJobLauncher")
private JobLauncher jobLauncher;

Damit werden Ihre Job-Instanzen nacheinander ausgeführt (dies beschränkt jedoch nicht die parallele Ausführung von Schritten innerhalb Ihres Jobs).

5
Ilya Dyoshin

In dieser Lösung können Sie vordefinierte Jobs mithilfe von HTTP-Anforderungen planen und ausplanen. In diesem Beispiel erstellen wir einen täglichen, wöchentlichen und einen einmaligen Job. Die Anwendung verwendet Quartz.

<!--Quartz Scheduler -->
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.2.3</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
</dependency>

Zuerst müssen wir eine AutowiringSpringBeanJobFactory-Klasse erstellen, die SpringBeanJobFactory erweitert. 

  • Unterklasse von {@link AdaptableJobFactory}, die auch .__ unterstützt. Spring-style * Abhängigkeitsinjektion von Bean-Eigenschaften. Das ist im Wesentlichen das direkte * Äquivalent von Spring {@link QuartzJobBean} in Form eines Quartz * {@link org.quartz.spi.JobFactory}. * *

    Wendet den Scheduler-Kontext, die Job-Datenzuordnung und die Trigger-Datenzuordnung an Einträge * als Bean-Eigenschaftswerte. Wenn keine übereinstimmende Bean-Eigenschaft ist gefunden, wird der Eintrag * standardmäßig einfach ignoriert. Dies ist analog zu QuartzJobBeans Verhalten.

public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {

    private transient AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        beanFactory = applicationContext.getAutowireCapableBeanFactory();        
    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }
}

Der zweite Teil ist die Konfiguration der Quarzkonfiguration. In dieser Konfiguration müssen wir ein erstellen 

  • SchedulerFactoryBean wo wir die globale Konfiguration und den Anwendungskontext einstellen,
  • JobDetailFactoryBean wo wir unseren Job, die JobGroup und die Klasse einstellen,

  • CronTriggerFactoryBean wo wir den Cron-Ausdruck setzen.

QuartzConfig.class

@Configuration
public class QuartzConfig {

    @Autowired
    ApplicationContext context;

    @Bean
    public SchedulerFactoryBean quartzScheduler(){
        SchedulerFactoryBean quartzScheduler = new SchedulerFactoryBean();
        quartzScheduler.setOverwriteExistingJobs(true);
        quartzScheduler.setSchedulerName("job-scheduler");
        AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
        jobFactory.setApplicationContext(context);
        quartzScheduler.setJobFactory(jobFactory);
        return quartzScheduler;
    }

    @Bean
    @Scope(value = "prototype")
    public JobDetailFactoryBean getJobBean(String jobName, String jobGroup, Class<?> clazz){
        JobDetailFactoryBean bean = new JobDetailFactoryBean();
        bean.setJobClass(clazz);
        bean.setGroup(jobGroup);
        bean.setName(jobName);
        return bean;
    }

    @Bean
    @Scope(value = "prototype")
    public CronTriggerFactoryBean getCronTriggerBean(String cronExpression, String triggerGroup){
        CronTriggerFactoryBean bean = new CronTriggerFactoryBean();
        bean.setCronExpression(cronExpression);
        bean.setGroup(triggerGroup);
        return bean;
    }
}

Nachdem die Konfiguration abgeschlossen ist, können wir nun unsere Jobs erstellen, an denen die Geschäftslogik platziert wird. Dafür müssen wir eine Klasse erstellen, die Job implementiert. 

@Component
public class DailyJob implements Job{

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("Daily Job runs!");
    }
}

Die DailyJob-Klasse kann jetzt geplant werden. Wir möchten diesen Job über eine http-Anfrage von außen einplanen. In diesem Beispiel haben wir einen Controller, an den wir den Jobnamen und den Cron-Ausdruck senden können, um die dailyJob zu planen.

@Controller
public class JobController {

    @Autowired
    private Scheduler scheduler;
    @Autowired
    private ApplicationContext context;;

    @ResponseBody
    @RequestMapping(value = "/job/create/daily", method = RequestMethod.POST)
    public ResponseEntity<JobModel> dailyJob(@RequestBody JobModel jobModel) throws SchedulerException {
        JobDetail jobDetail = context.getBean(
                JobDetail.class, jobModel.getName(), "MyDailyJob", DailyJob.class);
        Trigger cronTrigger = context.getBean(
                Trigger.class, jobModel.getCronExpression(), "MyDailyJob");

        scheduler.scheduleJob(jobDetail, cronTrigger);

        return new ResponseEntity<JobModel>(jobModel, HttpStatus.CREATED);
    }
}

Was wir hier sehen, ist, dass wir eine Post-Anfrage mit einem JobModel als @RequestBody senden. JobModel ist ein einfaches Pojo mit zwei Attributen name und cronExpression, beide Strings.

In dieser Methode müssen wir die Bean-Instanzen erstellen, die wir zuvor in unserer config-Klasse konfiguriert haben. Erstellen Sie zunächst JobDetail mit Quartz JobDetail.class, den Namen Ihres Jobs, den Namen der Gruppe und die Klasse, die geplant werden soll (in diesem Fall DailyJob.class). Danach müssen wir den Trigger mit Quartz Trigger.class, die cronExpression und den Gruppennamen erstellen.

Nachdem beide Beans erstellt wurden, müssen wir den Job jetzt einplanen. Daher haben wir automatisch Quarz Scheduler für die Planung des Jobs eingestellt. Danach ist der Job aktiviert und kann seine Arbeit erledigen.

Also lasst uns das Zeug testen. Starten Sie die Anwendung und senden Sie eine Post-Anfrage an /job/create/daily:

{"name":"Job 1", "cronExpression":"0 * * * * ?"}

Hier sagen wir, dass der Job jede Minute laufen sollte (nur um zu sehen, dass alles funktioniert). In Ihrer Konsole sollten Sie jede Minute Daily Job runs! sehen.

Und hier sind einige zusätzliche Dinge, die Sie tun können. Holen Sie sich zum Beispiel eine Liste der geplanten Jobs:

 @ResponseBody
 @RequestMapping("job/list")
 public List<String> jobList() throws SchedulerException {
     return scheduler.getJobGroupNames();
 }

Um einen Job zu löschen, können Sie auch Endpunkte erstellen. Zum Beispiel: 

@ResponseBody
@RequestMapping(value = "job/delete/daily", method = RequestMethod.POST)
public ResponseEntity<Boolean> deleteJob(@RequestBody JobModel jobModel) throws SchedulerException {
    JobKey jobKey = new JobKey(jobModel.getName(), "MyDailyJob");
    return new ResponseEntity<Boolean>(scheduler.deleteJob(jobKey), HttpStatus.OK);
}

Sie können viele verschiedene Endpunkte erstellen, um Informationen zu den aktuell ausgeführten Jobs zu erhalten, wie oft Jobs ausgeführt wurden, Jobs neu zu planen und so weiter. Wichtig ist nur, dass Ihr Jobname und die Jobgruppe (in unserem Fall "MyDailyJob") wiederverwendbar sind. Diese Informationen werden benötigt, um den jobKey zu erstellen.

P .: Nur um die anderen Zuordnungen für die anderen Jobs anzuzeigen:

@ResponseBody
@RequestMapping(value = "/job/create/weekly", method = RequestMethod.POST)
public ResponseEntity<JobModel> weeklyJob(@RequestBody JobModel jobModel) throws SchedulerException {
    JobDetail jobDetail = context.getBean(JobDetail.class, jobModel.getName(), JobGroup.WEEKLY_GROUP.name(),
            WeeklyJob.class);
    Trigger cronTrigger = context.getBean(Trigger.class, jobModel.getCronExpression(),
            JobGroup.WEEKLY_GROUP.name());

    scheduler.scheduleJob(jobDetail, cronTrigger);

    return new ResponseEntity<JobModel>(jobModel, HttpStatus.CREATED);

}

@ResponseBody
@RequestMapping(value = "/job/create/oneTime", method = RequestMethod.POST)
public ResponseEntity<JobModel> oneTimeJob(@RequestBody JobModel jobModel) throws SchedulerException {
    JobDetail jobDetail = context.getBean(JobDetail.class, jobModel.getName(), JobGroup.ONE_TIME_GROUP.name(),
            OneTimeJob.class);
    Trigger cronTrigger = context.getBean(Trigger.class, jobModel.getCronExpression(),
            JobGroup.ONE_TIME_GROUP.name());

    scheduler.scheduleJob(jobDetail, cronTrigger);

    return new ResponseEntity<JobModel>(jobModel, HttpStatus.CREATED);
}

Die vollständige Anwendung ist auf github

3
Patrick

@Scheduled ist für eine Methode definiert und nicht für ein Bean. Erstellen Sie also eine neue Klasse, die eine Bean sein wird

public class BatchConfiguration {
...
@Bean
public Job job() {
    return new Job();
}

neue Klasse:

public class Job {

@Scheduled(cron = "0/5 * * * * ?")
public Job job() {
    return jobBuilderFactory.get("job")
            .incrementer(new RunIdIncrementer())
            .flow(step1())
            .end()
            .build();
}
1
user7294900