RESTコントローラでジョブを開始できるようにしたいので、ジョブが開始されると、RESTで再度停止するまで、スケジュールに従って実行する必要があります。
これが私のコントローラーです:
@RestController
public class LauncherController {
@Autowired
JobLauncher jobLauncher;
@Autowired
Job job;
@RequestMapping("/launch")
public String launch() throws Exception {
...
jobLauncher.run(job, jobParameters);
}
これは、バッチ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();
}
Spring Boot Appが起動するとすぐにジョブを実行したくないので、spring.batch.job.enabled = falseプロパティも設定しました。
これで、Rest api lauchを呼び出すことができ、ジョブは1回だけ実行されます。スケジューラが機能しません。そして、@ Scheduled Annotationを正確に定義する必要がある場所を把握できませんでした。
スケジュールされたジョブは常に実行されるという方法でアプローチしますが、フラグがtrueに設定されている場合にのみ何かを行います:
@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());
}
}
およびコントローラー:
@RestController
class HelloController {
private final ScheduledJob scheduledJob;
// constructor
@GetMapping("/launch")
void toggle() {
scheduledJob.toggle();
}
}
最初に、ジョブを定義しています:
@Bean
@Qualifier("fancyScheduledJob")
public Job job() {
return jobBuilderFactory.get("job")
.incrementer(new RunIdIncrementer())
.flow(step1())
.end()
.build();
}
次に、このジョブの実行を開始します。
@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())
}
また、「launchTime」パラメーターが導入されていることに注意してください。デフォルトでは、Springバッチは同じパラメーター値でジョブを起動できません。
あなたのスケジュールは非常にタイトです-5秒ごとに並行性に注意する必要があります。または、毎回ジョブのインスタンスが1つだけ実行されるようにしたい場合は、カスタムシングルスレッドジョブランチャーを構成できます。
@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;
}
そして、起動時にこのシングルスレッドジョブランチャーを使用します。
@Autowired
@Qualifier("fancyJobLauncher")
private JobLauncher jobLauncher;
これにより、ジョブインスタンスは1つずつ実行されます(ただし、これはジョブ内のステップの並列実行を制限しません)。
このソリューションでは、httpリクエストを使用して、事前定義されたジョブをスケジュールおよびスケジュール解除できます。この例では、毎日、毎週、およびワンタイムジョブを作成します。アプリケーションは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>
まず、AutowiringSpringBeanJobFactory
クラスを拡張してSpringBeanJobFactory
を拡張する必要があります。
- BeanプロパティへのSpringスタイル*依存性注入もサポートする{@link AdaptableJobFactory}のサブクラス。これは本質的に、Quartz * {@link org.quartz.spi.JobFactory}の形をしたSpringの{@link QuartzJobBean}に直接相当するものです。 * *
スケジューラコンテキスト、ジョブデータマップ、およびトリガーデータマップエントリをBeanプロパティ値として適用します。一致するBeanプロパティが見つからない場合、エントリ*はデフォルトで単に無視されます。これはQuartzJobBeanの動作に似ています。
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;
}
}
2番目の部分は、クォーツ構成を構成することです。この構成では、作成する必要があります
SchedulerFactoryBean
ここで、グローバル構成とアプリケーションコンテキストを設定します。JobDetailFactoryBean
ジョブ、jobGroup、およびクラスを設定した場所、
CronTriggerFactoryBean
cron式を設定します。
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;
}
}
そのため、構成が完了したら、ビジネスロジックを配置するジョブを作成できるようになります。そのためには、Job
を実装するクラスを作成する必要があります。
@Component
public class DailyJob implements Job{
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("Daily Job runs!");
}
}
これで、DailyJob
クラスをスケジュールする準備ができました。 httpリクエストを介して外部からこのジョブをスケジュールします。この例では、dailyJob
をスケジュールするためにジョブ名とcron式を送信できるコントローラーがあります。
@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);
}
}
ここでわかるのは、JobModel
を@RequestBody
として投稿リクエストを送信することです。 JobModel
は、2つの属性name
およびcronExpression
両方の文字列を持つ単純なPojoです。
このメソッドでは、構成クラスで以前に構成したBeanインスタンスを作成する必要があります。最初に、JobDetail
をQuartz JobDetail.class
、ジョブの名前、グループの名前、およびスケジュールするクラス(この場合はDailyJob.class
)で作成します。その後、Quartz Trigger.class
、cronExpression、およびグループ名を使用してトリガーを作成する必要があります。
両方のBeanが作成されたら、今すぐジョブをスケジュールする必要があります。したがって、ジョブをスケジュールするためにQuartz Scheduler
を自動配線しました。その後、ジョブが有効になり、ジョブを実行する準備が整います。
それで、ものをテストしましょう。アプリケーションを起動し、/job/create/daily
に投稿リクエストを送信します。
{"name":"Job 1", "cronExpression":"0 * * * * ?"}
ここでは、ジョブは毎分実行する必要があると言います(すべてが機能することを確認するためだけです)。コンソールには、毎分Daily Job runs!
が表示されます。
そして、あなたができるいくつかの追加の事柄があります。たとえば、スケジュールされたジョブのリストを取得します。
@ResponseBody
@RequestMapping("job/list")
public List<String> jobList() throws SchedulerException {
return scheduler.getJobGroupNames();
}
ジョブを削除するには、エンドポイントも作成できます。例えば:
@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);
}
現在実行中のジョブ、ジョブの実行頻度、ジョブの再スケジュールなどに関する情報を取得するために、さまざまなエンドポイントを自由に作成できます。重要なのは、ジョブ名とジョブグループ(この例では"MyDailyJob"
)が再利用可能であることだけです。これらの情報は、jobKeyを作成するために必要です。
追伸:他のジョブの他のマッピングを表示するだけです:
@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);
}
完全なアプリケーションは github にあります
@Scheduled
は、Beanではなくメソッドで定義されます。 Beanになる新しいクラスを作成します
public class BatchConfiguration {
...
@Bean
public Job job() {
return new Job();
}
新しいクラス:
public class Job {
@Scheduled(cron = "0/5 * * * * ?")
public Job job() {
return jobBuilderFactory.get("job")
.incrementer(new RunIdIncrementer())
.flow(step1())
.end()
.build();
}