原文: https://howtodoinjava.com/spring-batch/batch-quartz-java-config-example/
学习配置Quartz
调度程序以运行使用 Spring Boot Java 配置配置的 Spring Batch 作业。 尽管 Spring 的默认调度程序也不错,但是 Quartz 可以更好地并且以更可配置的方式更好地调度和调用任务。 这使 Spring Batch 仅专注于创建批处理作业,而让 Quartz 执行它们。
项目概述和结构
在此带有 Quartz 示例的 SpringBatch 中,我们将创建两个具有不同步骤数的不同批处理作业。 然后,我们将使用夸脱作业数据映射和触发器安排作业执行时间,并在控制台中观察输出。
Spring Batch Quartz Java 配置示例
Maven 依赖
Spring Boot 具有对 Quartz 的内置支持,因此您所需要做的就是导入依赖项,例如spring-boot-starter-quartz
和spring-boot-starter-batch
。
请注意,quartz 需要至少一个数据库来存储作业执行详细信息。 在这个例子中,我使用的是 Spring Boot 支持的H2
数据库。 因此也包括'h2'
依赖项。
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd;
<modelVersion>4.0.0</modelVersion>
<groupId>com.howtodoinjava</groupId>
<artifactId>App</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>App</name>
<url>http://maven.apache.org</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>repository.spring.release</id>
<name>Spring GA Repository</name>
<url>http://repo.spring.io/release</url>
</repository>
</repositories>
</project>
创建具有多个任务的批处理作业
首先创建几个小任务。 这些是在单个批处理作业中一步处理的策略。
MyTaskOne.java
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
public class MyTaskOne implements Tasklet {
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("MyTaskOne start..");
// ... your code
System.out.println("MyTaskOne done..");
return RepeatStatus.FINISHED;
}
}
MyTaskTwo.java
public class MyTaskTwo implements Tasklet {
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("MyTaskTwo start..");
// ... your code
System.out.println("MyTaskTwo done..");
return RepeatStatus.FINISHED;
}
}
现在创建 SpringBatch,并在其中配置步骤。 我正在创建两个批处理作业。
demoJobOne
具有 2 个步骤,即stepOne
和stepTwo
。demoJobTwo
和一个步骤,即stepOne
。
`BatchConfig.java````java import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
import com.howtodoinjava.demo.tasks.MyTaskOne; import com.howtodoinjava.demo.tasks.MyTaskTwo;
@Configuration @EnableBatchProcessing public class BatchConfig {
@Autowired
private JobBuilderFactory jobs;
@Autowired
private StepBuilderFactory steps;
@Bean
public Step stepOne(){
return steps.get("stepOne")
.tasklet(new MyTaskOne())
.build();
}
@Bean
public Step stepTwo(){
return steps.get("stepTwo")
.tasklet(new MyTaskTwo())
.build();
}
@Bean(name="demoJobOne")
public Job demoJobOne(){
return jobs.get("demoJobOne")
.start(stepOne())
.next(stepTwo())
.build();
}
@Bean(name="demoJobTwo")
public Job demoJobTwo(){
return jobs.get("demoJobTwo")
.flow(stepOne())
.build()
.build();
}
}
<a name="4e6ff5de"></a>
## 使用`QuartzJobBean`创建 Quartz 作业运行器
现在我们需要创建标准的`QuartzJobBean`实例,该实例将用于执行批处理作业。
`CustomQuartzJob.java`
```java
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.configuration.JobLocator;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.scheduling.quartz.QuartzJobBean;
public class CustomQuartzJob extends QuartzJobBean {
private String jobName;
private JobLauncher jobLauncher;
private JobLocator jobLocator;
public String getJobName() {
return jobName;
}
public void setJobName(String jobName) {
this.jobName = jobName;
}
public JobLauncher getJobLauncher() {
return jobLauncher;
}
public void setJobLauncher(JobLauncher jobLauncher) {
this.jobLauncher = jobLauncher;
}
public JobLocator getJobLocator() {
return jobLocator;
}
public void setJobLocator(JobLocator jobLocator) {
this.jobLocator = jobLocator;
}
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException
{
try
{
Job job = jobLocator.getJob(jobName);
JobParameters params = new JobParametersBuilder()
.addString("JobID", String.valueOf(System.currentTimeMillis()))
.toJobParameters();
jobLauncher.run(job, params);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
在SchedulerFactoryBean
中配置 Quartz 作业详细信息和触发器
- 现在可以配置 Quartz
JobDetail
和triggers
,并将它们注册到 spring 的上下文中。 JobDetail
实例包含有关QuartzJobBean
的信息和要使用JobDataMap
注入的信息。- 还创建
Trigger
实例以配置批处理作业的执行时间和频率。 - 最后,在
SchedulerFactoryBean
中添加作业详细信息和触发器,以配置 Spring 并管理其生命周期(作为 Spring 应用程序上下文的一部分)。 它会在初始化时自动启动调度程序,并在销毁时将其关闭。
QuartzConfig.java
import java.io.IOException;
import java.util.Properties;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.springframework.batch.core.configuration.JobLocator;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import com.howtodoinjava.demo.jobs.CustomQuartzJob;
@Configuration
public class QuartzConfig
{
@Autowired
private JobLauncher jobLauncher;
@Autowired
private JobLocator jobLocator;
@Bean
public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor();
jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry);
return jobRegistryBeanPostProcessor;
}
@Bean
public JobDetail jobOneDetail() {
//Set Job data map
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("jobName", "demoJobOne");
jobDataMap.put("jobLauncher", jobLauncher);
jobDataMap.put("jobLocator", jobLocator);
return JobBuilder.newJob(CustomQuartzJob.class)
.withIdentity("demoJobOne")
.setJobData(jobDataMap)
.storeDurably()
.build();
}
@Bean
public JobDetail jobTwoDetail() {
//Set Job data map
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("jobName", "demoJobTwo");
jobDataMap.put("jobLauncher", jobLauncher);
jobDataMap.put("jobLocator", jobLocator);
return JobBuilder.newJob(CustomQuartzJob.class)
.withIdentity("demoJobTwo")
.setJobData(jobDataMap)
.storeDurably()
.build();
}
@Bean
public Trigger jobOneTrigger()
{
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(10)
.repeatForever();
return TriggerBuilder
.newTrigger()
.forJob(jobOneDetail())
.withIdentity("jobOneTrigger")
.withSchedule(scheduleBuilder)
.build();
}
@Bean
public Trigger jobTwoTrigger()
{
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(20)
.repeatForever();
return TriggerBuilder
.newTrigger()
.forJob(jobTwoDetail())
.withIdentity("jobTwoTrigger")
.withSchedule(scheduleBuilder)
.build();
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean() throws IOException
{
SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
scheduler.setTriggers(jobOneTrigger(), jobTwoTrigger());
scheduler.setQuartzProperties(quartzProperties());
scheduler.setJobDetails(jobOneDetail(), jobTwoDetail());
return scheduler;
}
@Bean
public Properties quartzProperties() throws IOException
{
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
}
使用属性文件自定义 Quartz
在 Quartz 中,您可以从简单的属性文件中控制很多东西。 例如
quartz.properties
#scheduler name will be "MyScheduler"
org.quartz.scheduler.instanceName = MyScheduler
#maximum of 3 jobs can be run simultaneously
org.quartz.threadPool.threadCount = 3
#All of Quartz data is held in memory (rather than in a database).
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
示例
在运行应用程序之前,请确保已在application.properties
文件中禁用了批处理作业自动启动功能。
application.properties
#Disable batch job's auto start
spring.batch.job.enabled=false
#enable h2 databse console
spring.h2.console.enabled=true
现在,将该应用程序作为 Spring Batch 应用程序运行,并检查日志。
App.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App
{
public static void main(String[] args)
{
SpringApplication.run(App.class, args);
}
}
Console
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.3.RELEASE)
2018-07-05 10:50:53 INFO - Starting App on FFC15B4E9C5AA with PID 9740 (C:\Users\zkpkhua\DigitalDashboard_Integrated\App\target\classes started by zkpkhua in C:\Users\zkpkhua\DigitalDashboard_Integrated\App)
2018-07-05 10:50:53 INFO - No active profile set, falling back to default profiles: default
2018-07-05 10:50:53 INFO - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@78b1cc93: startup date [Thu Jul 05 10:50:53 IST 2018]; root of context hierarchy
2018-07-05 10:50:54 INFO - Bean 'org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari' of type [org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:54 INFO - Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$EnhancerBySpringCGLIB$169797f6] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:54 INFO - Bean 'org.springframework.boot.context.properties.ConversionServiceDeducer$Factory' of type [org.springframework.boot.context.properties.ConversionServiceDeducer$Factory] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:54 INFO - Bean 'spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties' of type [org.springframework.boot.autoconfigure.jdbc.DataSourceProperties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:54 INFO - Bean 'dataSource' of type [com.zaxxer.hikari.HikariDataSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:54 INFO - Bean 'org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker' of type [org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:54 INFO - Bean 'org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration' of type [org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$EnhancerBySpringCGLIB$165a725c] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:54 INFO - Bean 'jobLauncher' of type [com.sun.proxy.$Proxy41] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:54 INFO - Bean 'jobRegistry' of type [com.sun.proxy.$Proxy43] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:54 INFO - Bean 'quartzConfig' of type [com.howtodoinjava.demo.config.QuartzConfig$EnhancerBySpringCGLIB$7dc0f057] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:55 INFO - HikariPool-1 - Starting...
2018-07-05 10:50:55 INFO - HikariPool-1 - Start completed.
2018-07-05 10:50:55 INFO - No database type set, using meta data indicating: H2
2018-07-05 10:50:55 INFO - No TaskExecutor has been set, defaulting to synchronous executor.
2018-07-05 10:50:56 INFO - Executing SQL script from class path resource [org/quartz/impl/jdbcjobstore/tables_h2.sql]
2018-07-05 10:50:56 INFO - Executed SQL script from class path resource [org/quartz/impl/jdbcjobstore/tables_h2.sql] in 69 ms.
2018-07-05 10:50:56 INFO - Using default implementation for ThreadExecutor
2018-07-05 10:50:56 INFO - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2018-07-05 10:50:56 INFO - Quartz Scheduler v.2.3.0 created.
2018-07-05 10:50:56 INFO - RAMJobStore initialized.
2018-07-05 10:50:56 INFO - Scheduler meta-data: Quartz Scheduler (v2.3.0) 'schedulerFactoryBean' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 3 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
2018-07-05 10:50:56 INFO - Quartz scheduler 'schedulerFactoryBean' initialized from an externally provided properties instance.
2018-07-05 10:50:56 INFO - Quartz scheduler version: 2.3.0
2018-07-05 10:50:56 INFO - JobFactory set to: org.springframework.scheduling.quartz.AdaptableJobFactory@1e7f2e0f
2018-07-05 10:50:56 INFO - Executing SQL script from class path resource [org/springframework/batch/core/schema-h2.sql]
2018-07-05 10:50:56 INFO - Executed SQL script from class path resource [org/springframework/batch/core/schema-h2.sql] in 30 ms.
2018-07-05 10:50:57 INFO - Registering beans for JMX exposure on startup
2018-07-05 10:50:57 INFO - Bean with name 'dataSource' has been autodetected for JMX exposure
2018-07-05 10:50:57 INFO - Located MBean 'dataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=dataSource,type=HikariDataSource]
2018-07-05 10:50:57 INFO - Starting beans in phase 2147483647
2018-07-05 10:50:57 INFO - Starting Quartz Scheduler now
2018-07-05 10:50:57 INFO - Scheduler schedulerFactoryBean_$_NON_CLUSTERED started.
2018-07-05 10:50:57 INFO - Started App in 4.164 seconds (JVM running for 4.941)
2018-07-05 10:50:57 INFO - Job: [SimpleJob: [name=demoJobOne]] launched with the following parameters: [{JobID=1530768057055}]
2018-07-05 10:50:57 INFO - Job: [FlowJob: [name=demoJobTwo]] launched with the following parameters: [{JobID=1530768057057}]
2018-07-05 10:50:57 INFO - Executing step: [stepOne]
MyTaskOne start..
MyTaskOne done..
2018-07-05 10:50:57 INFO - Executing step: [stepTwo]
MyTaskTwo start..
MyTaskTwo done..
2018-07-05 10:50:57 INFO - Executing step: [stepOne]
2018-07-05 10:50:57 INFO - Job: [SimpleJob: [name=demoJobOne]] completed with the following parameters: [{JobID=1530768057055}] and the following status: [COMPLETED]
MyTaskOne start..
MyTaskOne done..
2018-07-05 10:50:57 INFO - Job: [FlowJob: [name=demoJobTwo]] completed with the following parameters: [{JobID=1530768057057}] and the following status: [COMPLETED]
2018-07-05 10:51:05 INFO - Job: [SimpleJob: [name=demoJobOne]] launched with the following parameters: [{JobID=1530768065955}]
2018-07-05 10:51:05 INFO - Executing step: [stepOne]
MyTaskOne start..
MyTaskOne done..
2018-07-05 10:51:05 INFO - Executing step: [stepTwo]
MyTaskTwo start..
MyTaskTwo done..
2018-07-05 10:51:05 INFO - Job: [SimpleJob: [name=demoJobOne]] completed with the following parameters: [{JobID=1530768065955}] and the following status: [COMPLETED]
2018-07-05 10:51:15 INFO - Job: [SimpleJob: [name=demoJobOne]] launched with the following parameters: [{JobID=1530768075955}]
2018-07-05 10:51:15 INFO - Executing step: [stepOne]
MyTaskOne start..
MyTaskOne done..
2018-07-05 10:51:15 INFO - Executing step: [stepTwo]
MyTaskTwo start..
MyTaskTwo done..
2018-07-05 10:51:15 INFO - Job: [SimpleJob: [name=demoJobOne]] completed with the following parameters: [{JobID=1530768075955}] and the following status: [COMPLETED]
2018-07-05 10:51:15 INFO - Job: [FlowJob: [name=demoJobTwo]] launched with the following parameters: [{JobID=1530768075980}]
2018-07-05 10:51:15 INFO - Executing step: [stepOne]
MyTaskOne start..
MyTaskOne done..
2018-07-05 10:51:16 INFO - Job: [FlowJob: [name=demoJobTwo]] completed with the following parameters: [{JobID=1530768075980}] and the following status: [COMPLETED]
显然,两个 Spring Batch 作业都基于在 Quartz 触发器中配置的调度执行。
将我的问题放在评论部分。
学习愉快!