简体   繁体   English

如何触发计划的Spring批处理作业?

[英]How to trigger a scheduled Spring Batch Job?

I want to be able to start my job with a REST controller, then when the job is started, it should run on a scheduled basis, until i stop it again with REST. 我希望能够使用REST控制器开始工作,然后在作业启动时,它应该按计划运行,直到我再次使用REST停止它。

So this is my Controller: 所以这是我的控制器:

@RestController
public class LauncherController {

    @Autowired
    JobLauncher jobLauncher;

    @Autowired
    Job job;

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

This is some part of the Batch conf: 这是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();
    }

I have also set the property spring.batch.job.enabled=false, since i do not want the jobs run as soon as the Spring Boot App starts. 我还设置了属性spring.batch.job.enabled = false,因为我不想在Spring Boot App启动后立即运行作业。

Now i can call my Rest api lauch, and the job runs, but only once. 现在我可以打电话给我的Rest api lauch,这个工作仍在运行,但只有一次。 Scheduler does not work. 调度程序不起作用。 And I could not figure it our where exactly i should define my @Scheduled Annotation.. 我无法确定我应该在哪里定义我的@Scheduled Annotation。

I would approach it in a way, that scheduled job runs always, but it does something only when the flag is set to true: 我会以某种方式处理它,预定的作业始终运行,但只有当标志设置为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());
    }

}

and a controller: 和一个控制器:

@RestController
class HelloController {

    private final ScheduledJob scheduledJob;

    // constructor

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

}

In first you are defining the job: 首先,您要定义工作:

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

In second you are initiating the execution of this job: 第二,你开始执行这项工作:

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

Also note that the "launchTime" paramter is introduced: by default spring batch is preventing launching the job with same parameter values. 另请注意,引入了“launchTime”参数:默认情况下,spring批处理阻止使用相同的参数值启动作业。

While your schedule is quite tight - every 5 seconds you should be aware of concurrency. 虽然您的日程安排非常紧张 - 每5秒钟您应该了解并发性。 Or if you want to be assured that at each and every moment only 1 instance of the job is executed you can configure custom single threaded job launcher: 或者,如果您想确保在每个时刻只执行一个作业实例,您可以配置自定义单线程作业启动器:

@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;
}

And use this single threaded job launcher during launch time. 并在启动期间使用此单线程作业启动器。

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

With this your job instances will be executed one by one (but this doesn't limits parallel execution of steps inside of your job). 通过这种方式,您的作业实例将逐个执行(但这不会限制并行执行作业中的步骤)。

In this solution you will be able to schedule and unschedule pre defined jobs using http requests. 在此解决方案中,您将能够使用http请求计划和取消计划预定义的作业。 In this example we will create a daily, weekly and an oneTime Job. 在这个例子中,我们将创建每日,每周和一个时间作业。 The application is using Quartz . 该应用程序使用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>

First we have to create an AutowiringSpringBeanJobFactory class extends SpringBeanJobFactory . 首先,我们必须创建一个AutowiringSpringBeanJobFactory类扩展SpringBeanJobFactory

  • Subclass of {@link AdaptableJobFactory} that also supports Spring-style * dependency injection on bean properties. {@link AdaptableJobFactory}的子类,它还支持对bean属性的Spring-style *依赖注入。 This is essentially the direct * equivalent of Spring's {@link QuartzJobBean} in the shape of a Quartz * {@link org.quartz.spi.JobFactory}. 这基本上是Spring的{@link QuartzJobBean}的直接*等价物,形状为Quartz * {@link org.quartz.spi.JobFactory}。 * * * *

    Applies scheduler context, job data map and trigger data map entries * as bean property values. 将调度程序上下文,作业数据映射和触发器数据映射条目*应用为bean属性值。 If no matching bean property is found, the entry * is by default simply ignored. 如果未找到匹配的bean属性,则默认情况下忽略条目*。 This is analogous to QuartzJobBean's behavior. 这类似于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;
    }
}

The second part is to configure the quartz configuration. 第二部分是配置石英配置。 In this config we need to create a 在这个配置中我们需要创建一个

  • SchedulerFactoryBean where we set global config and the application context, SchedulerFactoryBean我们设置全局配置和应用程序上下文,
  • JobDetailFactoryBean where we set our job, the jobGroup and the class, JobDetailFactoryBean我们设置我们的工作,jobGroup和类,

  • CronTriggerFactoryBean where we set the cron expression. CronTriggerFactoryBean我们在哪里设置cron表达式。

QuartzConfig.class 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;
    }
}

So, after the config is done we are now able to create our jobs where the business logic will be placed. 因此,在配置完成后,我们现在可以创建业务逻辑所在的作业。 For that we have to create a class which implements Job . 为此,我们必须创建一个实现Job的类。

@Component
public class DailyJob implements Job{

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

The DailyJob class is now ready to get scheduled. DailyJob类现在已准备就绪。 We want to schedule this job from outside via a http request. 我们希望通过http请求从外部安排此作业。 In this example we have a controller where we can send the jobname and the cron expression to schedule the dailyJob . 在这个例子中,我们有一个控制器,我们可以发送jobname和cron表达式来安排dailyJob

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

What we see here is that we will send a post request with a JobModel as @RequestBody . 我们在这里看到的是,我们将使用JobModel作为@RequestBody发送一个帖子请求。 JobModel is a simple Pojo with two attributes name and cronExpression both Strings. JobModel是一个简单的Pojo,有两个属性namecronExpression两个字符串。

In this method we have to create the bean instances which we have configured previously in our config class. 在这个方法中,我们必须创建我们之前在config类中配置的bean实例。 First create JobDetail with Quartz JobDetail.class , the name of your job, the name of the group and the Class which should be scheduled (in this case DailyJob.class ). 首先使用Quartz JobDetail.class创建JobDetail ,作业名称,组名称和应安排的类(在本例中为DailyJob.class )。 After that we have to create the Trigger with Quartz Trigger.class , the cronExpression and the group name. 之后我们必须使用Quartz Trigger.class ,cronExpression和组名创建Trigger。

After both beans are created we need to schedule the job now. 创建两个bean后,我们需要立即安排作业。 So we have autowired Quartz Scheduler to schedule the job. 所以我们已经自动安装了Quartz Scheduler来安排工作。 After that the job is enabled and ready to do its job. 之后,该作业已启用并准备好完成其工作。

So let's test the stuff. 所以让我们测试一下这些东西。 Start the application and send a post request to /job/create/daily : 启动应用程序并将发布请求发送到/job/create/daily

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

Here we say that the job should run every minute (just to see that everything works). 在这里,我们说工作应该每分钟运行一次(只是为了看到一切正常)。 In your console you should see every minute Daily Job runs! 在你的控制台中你应该看到每一分钟的Daily Job runs! .

And here are some additional things you can do. 以下是您可以做的其他一些事情。 For example get a list of the scheduled jobs: 例如,获取预定作业的列表:

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

To delete a job you can create endpoints, too. 要删除作业,您也可以创建端点。 For example: 例如:

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

You are free to create many different endpoints to get informations about currently running jobs, how often jobs were running, reschedule jobs and so on. 您可以自由创建许多不同的端点,以获取有关当前正在运行的作业的信息,作业运行的频率,重新安排作业等。 Important is just, that your jobname and the jobgroup( in our case "MyDailyJob" ) are reusable. 重要的是,您的工作名称和工作组(在我们的案例中为"MyDailyJob" )是可重用的。 Those information are needed to create the jobKey. 创建jobKey需要这些信息。

PS: Just to show the other mappings for the other jobs: PS:只是为了展示其他工作的其他映射:

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

The full application is on github 完整的应用程序在github上

@Scheduled is defined on a method and not on a Bean. @Scheduled是在方法上定义的,而不是在Bean上定义的。 So create a new Class which will be a Bean 因此,创建一个将成为Bean的新类

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

new Class: 新课程:

public class Job {

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

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

相关问题 如何从Spring Batch Admin触发远程批处理作业? - How to trigger remote batch job from Spring Batch Admin? 关于这个 Spring Batch @Scheduled() 注解以及如何手动启动 Spring Batch 作业的一些疑问? - Some doubts about this Spring Batch @Scheduled() annotation and how to manually start a Spring Batch job? 如何删除计划作业的特定触发器 - how to delete specific trigger for a scheduled job 尽管计划时间为5分钟,但Spring Batch Job阅读器仍在连续运行 - Spring Batch Job reader is running continuously although the scheduled time is 5 minutes 在上一个开始后5秒钟开始预定的春季批处理作业 - Start a scheduled spring batch job 5 seonds after start of previous one 如何在 1 个给定的 UAT 服务器中以计划模式运行 spring 批处理作业,但在所有 3 个给定的 UAT 服务器中运行手动作业? - How to run spring batch job in scheduled mode in 1 given UAT server, but run manual job in all 3 given UAT servers? 如何在 Spring 中取消预定的 Quartz 作业 - How to cancel a scheduled Quartz job in Spring 如何在Java Spring中运行预定作业? - How to run scheduled job in java spring? 春季批处理的批处理作业 - Batch job for spring batch Java Spring Scheduled作业不起作用 - Java Spring Scheduled job not working
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM