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.
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();
}
}
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).
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
@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();
}