本文基于spring batch reference 第四章
Configuring and Running a Job
在spring batch之一 域模型中我们讨论了spring batch 的原型.

配置一个job,只需要三个必要的依赖: 一个名字,JobRepository
, 和一列steps.
<job id="footballJob"> <step id="playerload" parent="s1" next="gameLoad"/> <step id="gameLoad" parent="s2" next="playerSummarization"/> <step id="playerSummarization" parent="s3"/> </job>
上面的例子使用一个父Bean定义去创建step.Xml命名空间默认引用一个id 是'jobRepository' job repository。'jobRepository' 大小区分大小写。 然而,我们也可以明确定义job repository:
<job id="footballJob" job-repository="specialRepository">
<step id="playerload" parent="s1" next="gameLoad"/>
<step id="gameLoad" parent="s3" next="playerSummarization"/>
<step id="playerSummarization" parent="s3"/>
</job>
当一个job不能被restart的时候,只需要将 restartable 属性设置成 'false'.一个job instance和job parameter相关,每次在启动job时传入的参数的值相同,可以认为同一个job instance. 我们可以将运行job的当前时间作为一个参数传入,这样子每次启动都是不同的job instance.
<job id="footballJob" restartable="false">
...
</job>
启动一个restartable=false的job将抛出JobRestartException.
Job job = new SimpleJob(); job.setRestartable(false); JobParameters jobParameters = new JobParameters(); JobExecution firstExecution = jobRepository.createJobExecution(job, jobParameters); jobRepository.saveOrUpdate(firstExecution); try { jobRepository.createJobExecution(job, jobParameters); fail(); } catch (JobRestartException e) { // expected }
上面的测试代码段显示第一次创建一个restartable=false 的job的JobExecution是没有问题的
. 第二次将会抛出JobRestartException
.
我们可以实现job listener接口,在job运行前和运行后加入自己的一些逻辑.
public interface JobExecutionListener { void beforeJob(JobExecution jobExecution); void afterJob(JobExecution jobExecution); }
JobListener
s can be added to a SimpleJob
via the listeners element on the job:
<job id="footballJob">
<step id="playerload" parent="s1" next="gameLoad"/>
<step id="gameLoad" parent="s2" next="playerSummarization"/>
<step id="playerSummarization" parent="s3"/>
<listeners>
<listener ref="sampleListener"/>
</listeners>
</job>
应该注意到不管job正常结束还是异常结束afterJob接口都会被调用. 我们需要从JobExecution获取job的结束状态.
public void afterJob(JobExecution jobExecution){ if( jobExecution.getStatus() == BatchStatus.COMPLETED ){ //job success } else if(jobExecution.getStatus() == BatchStatus.FAILED){ //job failure } }
对应这个接口的 annotations :
-
@BeforeJob
-
@AfterJob
如果一组job
s分享一些类似的但不同的配置.我们可以定义一个父job.类似于class的继承,子job将会结合自身的和父job的元素和属性.
下面的例子, "baseJob"是一个抽象的 Job
定义,只包含了一个listen 列表. Job
"job1" 从"baseJob"中继承了listener列表并和自己的listener列表合并到一起.
<job id="baseJob" abstract="true"> <listeners> <listener ref="listenerOne"/> <listeners> </job> <job id="job1" parent="baseJob"> <step id="step1" parent="standaloneStep"/> <listeners merge="true"> <listener ref="listenerTwo"/> <listeners> </job>
可以配置一个job parameter validator <validator ref="paremetersValidator"/>验证job parameter的正确性. 有个一个默认的实现: DefaultJobParametersValidator,我们可以参考或者使用默认实现验证job parameter.
<job id="job1" parent="baseJob3"> <step id="step1" parent="standaloneStep"/> <validator ref="paremetersValidator"/> </job>
job repository 保存spring batch job的运行信息,对spring batch的自带表进行基本的CRUD操作。 请参考下面的配置: 配置数据源,事务管理器,事务隔离级别,table prefix(可以使用默认).
<job-repository id="jobRepository" data-source="dataSource" transaction-manager="transactionManager" isolation-level-for-create="SERIALIZABLE" table-prefix="BATCH_" max-varchar-length="1000" />
如果使用了命名空间(batch自身的命名空间), 会自动创建事务性的advice. 这能确保元数据的正确性. 默认的事务的隔离级别对create* 方法是SERIALIZABLE,非常严格: READ_COMMITTED 就应该工作的很好; READ_UNCOMMITTED 也可以,如果不是同时启动一个job. 因为调用create*非常短
t, SERIALIZED 不太可能会产生问题, 只要数据库平台支持SERIALIZED 事务级别. 我们也可以重写:
<job-repository id="jobRepository"
isolation-level-for-create="REPEATABLE_READ" />
使用aop命名空间配置:
<aop:config> <aop:advisor pointcut="execution(* org.springframework.batch.core..*Repository+.*(..))"/> <advice-ref="txAdvice" /> </aop:config> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*" /> </tx:attributes> </tx:advice>
默认的spring batch 自带的表都以BATCH_开头,当然我们可以改变这个前缀. table-prefix配置的前缀必须和database中的表的前缀相同.
<job-repository id="jobRepository"
table-prefix="SYSTEM.TEST_" />
spring提供了一个内存版本的job repository. 在做一些测试或者不需要保存job 运行状态的情况下,我们使用内存版的job repository.
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean"> <property name="transactionManager" ref="transactionManager"/> </bean>
内存版的job repository因为不能保存job的运行信息,有很多缺陷,在很多高级应用-跨JVM(跨机器)中不能使用.
最简单的JobLauncher
接口的实现是 SimpleJobLauncher
. 他只需要一个 JobRepository:
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher"> <property name="jobRepository" ref="jobRepository" /> </bean>
一旦获得 JobExecution
,JobExecution将会被传入job的执行方法. 最终返回 JobExecution
给调用者.

一个同步调用的顺序图. 我们可以通过一个scheduler调用spring batch.尽量不要使用http,因为batch运行时间长,会阻塞连接.

我们可以配置一个TaskExecutor实现Sim上图的异步调用:
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher"> <property name="jobRepository" ref="jobRepository" /> <property name="taskExecutor"> <bean class="org.springframework.core.task.SimpleAsyncTaskExecutor" /> </property> </bean>
运行job最少的条件需要一个job和一个job launcher.
调度系统中最常用的一种方式,例如使用quartz,或使用shell脚本.
spring 提供了一个实现类CommandLineJobRunner
.这仅仅是一种从command line启动spring batch的方法. 类CommandLineJobRunner
执行了四个任务:
-
装载
ApplicationContext
-
传递line command 参数给
JobParameters
-
根据参数定位具体的job
-
使用
JobLauncher运行job
.
所有的这些任务都是通过传递的参数完成. 下面是必须的参数:
jobPath jobName 必须作为第一个和第二个参数.作为job paramete的参数必须以'name=value'的格式传递.
bash$
java CommandLineJobRunner endOfDayJob.xml endOfDay schedule.date(date)=2007/05/05
<job id="endOfDay"> <step id="step1" parent="simpleStep" /> </job> <!-- Launcher details removed for clarity --> <beans:bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher" />
通常在web容器中以http的方式调用spring batch job,spring batch job都是以异步的方式执行.

controller是一个spring mvc 的control.. controller 使用JobLauncher
异步启动一个job, JobLauncher立即返回JobExecution
. Job
会继续运行。
@Controller public class JobLauncherController { @Autowired JobLauncher jobLauncher; @Autowired Job job; @RequestMapping("/jobLauncher.html") public void handle() throws Exception{ jobLauncher.run(job, new JobParameters()); } }
到现在为止,已经讨论过JobLauncher 和 JobRepository.

JobLauncher
使用 JobRepository
去创建新的 JobExecution
对象并运行. 在job中后续的Job
和 Step
的实现使用同样的JobRepository
作为基本的CRUD操作. 基本的操作在一些简单的场景中足够了,但是在许多batch job的复杂调度环境中,访问元数据是必须的:

在任何高级的特性之前,最基本的功能室查询repository了解当前的执行状态. 这些功能是由 JobExplorer
接口提供的:
public interface JobExplorer { List<JobInstance> getJobInstances(String jobName, int start, int count); JobExecution getJobExecution(Long executionId); StepExecution getStepExecution(Long jobExecutionId, Long stepExecutionId); JobInstance getJobInstance(Long instanceId); List<JobExecution> getJobExecutions(JobInstance jobInstance); Set<JobExecution> findRunningJobExecutions(String jobName); }
从上面的方法的签名中可以得知, JobExplorer
是 JobRepository的一个只读的版本
, 像 JobRepository
, 它可以很简单的通过一个工厂bean配置:
<bean id="jobExplorer" class="org.spr...JobExplorerFactoryBean" p:dataSource-ref="dataSource" />
带table prefix的配置:
<bean id="jobExplorer" class="org.spr...JobExplorerFactoryBean"
p:dataSource-ref="dataSource" p:tablePrefix="BATCH_" />
JobRegistry 不是必须的, 但是它可以帮助你了解有多少job在spring context中. spring framework提供了一个唯一的实现,配置如下:
<bean id="jobRegistry" class="org.spr...MapJobRegistry" />
有两中方法自动填充job mapping,一种是使用一个spring bean post processor,另一种使用一种注册的有生命周期的组件:
这是一个 bean post-processor 可以注册所有的以创建的job:
<bean id="jobRegistryBeanPostProcessor" class="org.spr...JobRegistryBeanPostProcessor"> <property name="jobRegistry" ref="jobRegistry"/> </bean>
这是一个生命周期组件,创建子contexts并从这些子contexts中注册job。
<bean class="org.spr...AutomaticJobRegistrar"> <property name="applicationContextFactories"> <bean class="org.spr...ClasspathXmlApplicationContextsFactoryBean"> <property name="resources" value="classpath*:/config/job*.xml" /> </bean> </property> <property name="jobLoader"> <bean class="org.spr...DefaultJobLoader"> <property name="jobRegistry" ref="jobRegistry" /> </bean> </property> </bean>
注册器需要两个必要的属性,一个是 ApplicationContextFactory
数组,另外一个是JobLoader
. JobLoader
负责管理子contexts的生命周期,并注在obRegistry中注册job.
ApplicationContextFactory
负责创建子 context,最常用的是使用 ClassPathXmlApplicationContextFactory
. 一个特性之一是它会从父context中拷贝一些配置到子context中.所以不需要再子context中重新定义PropertyPlaceholderConfigurer
或者 AOP 配置,他会继承父context.
sping batch 提供了JobOperator
对batch 操作进行重启,总结,停止操作.
public interface JobOperator { List<Long> getExecutions(long instanceId) throws NoSuchJobInstanceException; List<Long> getJobInstances(String jobName, int start, int count) throws NoSuchJobException; Set<Long> getRunningExecutions(String jobName) throws NoSuchJobException; String getParameters(long executionId) throws NoSuchJobExecutionException; Long start(String jobName, String parameters) throws NoSuchJobException, JobInstanceAlreadyExistsException; Long restart(long executionId) throws JobInstanceAlreadyCompleteException, NoSuchJobExecutionException, NoSuchJobException, JobRestartException; Long startNextInstance(String jobName) throws NoSuchJobException, JobParametersNotFoundException, JobRestartException, JobExecutionAlreadyRunningException, JobInstanceAlreadyCompleteException; boolean stop(long executionId) throws NoSuchJobExecutionException, JobExecutionNotRunningException; String getSummary(long executionId) throws NoSuchJobExecutionException; Map<Long, String> getStepExecutionSummaries(long executionId) throws NoSuchJobExecutionException; Set<String> getJobNames(); }
上面的操作代表了来自不同接口的方法, 比如JobLauncher
, JobRepository
, JobExplorer
, 和 JobRegistry
. 基于这个原因,JobOperator的默认实现SimpleJobOperator有很多依赖:
<bean id="jobOperator" class="org.spr...SimpleJobOperator"> <property name="jobExplorer"> <bean class="org.spr...JobExplorerFactoryBean"> <property name="dataSource" ref="dataSource" /> </bean> </property> <property name="jobRepository" ref="jobRepository" /> <property name="jobRegistry" ref="jobRegistry" /> <property name="jobLauncher" ref="jobLauncher" /> </bean>
JobOperator
中的大多数方法是自解释的. 但是 startNextInstance
总是启动一个新的 Job实例
. 如果当一个job在开始阶段发生了严重的错误,需要再次重启的时候,这个方法非常有用. 不像JobLauncher需要一些不同的job 参数才能重启一个job实例.
startNextInstance
方法将使用 JobParametersIncrementer
试图强制重启一个新的实例:
public interface JobParametersIncrementer { JobParameters getNext(JobParameters parameters); }
JobParametersIncrementer
的含义是给定一个JobParameters
对象通过增加一些必要的值将返回下一个JobParameters
对象。 参考下面的一个例子:
public class SampleIncrementer implements JobParametersIncrementer { public JobParameters getNext(JobParameters parameters) { if (parameters==null || parameters.isEmpty()) { return new JobParametersBuilder().addLong("run.id", 1L).toJobParameters(); } long id = parameters.getLong("run.id",1L) + 1; return new JobParametersBuilder().addLong("run.id", id).toJobParameters(); } }
<job id="footballJob" incrementer="sampleIncrementer">
...
</job>
大多数情况下使用JobOperator
停止一个job:
Set<Long> executions = jobOperator.getRunningExecutions("sampleJob"); jobOperator.stop(executions.iterator().next());
这段代码不能立即停止job运行,当业务逻辑把控制权返回给spring batch framework的时候,他会设置 StepExecution
的状态为 BatchStatus.STOPPED
, 保存它, 然后对 JobExecution
。
状态为 FAILED的job 可以被重启.
状态为ABANDONED
的不能重启. The ABANDONED
也被用在step executions 标记setp execution是可以跳过的,在一个重启的job中. 如果一个job碰到一个step 在上次的执行中被标记为ABANDONED
,它将跳到下一步.
如果线程死了 ("kill -9"
或者其他错误),job 实例不能知道他的运行状态.你必须手动的告诉他失败了或者aborted. 把他的状态修改为 FAILED
或者 ABANDONED
. 如果job不能被重启的话,就把他的状态修改为 FAILED
。