How to trigger a scheduled Spring Batch Job?

后端 未结 4 1391
日久生厌
日久生厌 2021-02-06 00:51

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.

So this is m

相关标签:
4条回答
  • 2021-02-06 01:29

    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.

    While your schedule is quite tight - every 5 seconds you should be aware of concurrency. 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).

    0 讨论(0)
  • 2021-02-06 01:38

    @Scheduled is defined on a method and not on a Bean. So create a new Class which will be a 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();
    }
    
    0 讨论(0)
  • 2021-02-06 01:41

    I would approach it in a way, that scheduled job runs always, but it does something only when the flag is set to 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();
        }
    
    }
    
    0 讨论(0)
  • 2021-02-06 01:42

    In this solution you will be able to schedule and unschedule pre defined jobs using http requests. In this example we will create a daily, weekly and an oneTime Job. The application is using 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.

    • Subclass of {@link AdaptableJobFactory} that also supports Spring-style * dependency injection on bean properties. This is essentially the direct * equivalent of Spring's {@link QuartzJobBean} in the shape of a Quartz * {@link org.quartz.spi.JobFactory}. * *

      Applies scheduler context, job data map and trigger data map entries * as bean property values. If no matching bean property is found, the entry * is by default simply ignored. This is analogous to QuartzJobBean's behavior.

    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,
    • JobDetailFactoryBean where we set our job, the jobGroup and the class,

    • CronTriggerFactoryBean where we set the cron expression.

    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.

    @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. We want to schedule this job from outside via a http request. In this example we have a controller where we can send the jobname and the cron expression to schedule the 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 is a simple Pojo with two attributes name and cronExpression both Strings.

    In this method we have to create the bean instances which we have configured previously in our config class. 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). After that we have to create the Trigger with Quartz Trigger.class, the cronExpression and the group name.

    After both beans are created we need to schedule the job now. So we have autowired Quartz Scheduler to schedule the job. 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:

    {"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!.

    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. Those information are needed to create the jobKey.

    P.S.: Just to show the other mappings for the other jobs:

    @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

    0 讨论(0)
提交回复
热议问题