本文基于spring batch reference 第四章 

Configuring and Running a Job



在spring batch之一 域模型中我们讨论了spring batch 的原型.




springbatch默认重启一次restart springbatch启动job_Source


4.1. 配置一个job


配置一个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>


'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>


4.1.1. 重启能力


当一个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.


4.1.2. 解析job execution


我们可以实现job listener接口,在job运行前和运行后加入自己的一些逻辑.


public interface JobExecutionListener {    void beforeJob(JobExecution jobExecution);

    void afterJob(JobExecution jobExecution);

}


JobListeners 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


4.1.3. 从一个父job中继承


如果一组jobs分享一些类似的但不同的配置.我们可以定义一个父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>


4.1.4. job 参数验证.


DefaultJobParametersValidator,我们可以参考或者使用默认实现验证job parameter. 


<job id="job1" parent="baseJob3">    <step id="step1" parent="standaloneStep"/>
    <validator ref="paremetersValidator"/>
</job>


4.2. 配置 JobRepository


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"
/>



4.3.1. job repository的事务配置


如果使用了命名空间(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>


4.3.2. 改变table 前缀


BATCH_开头,当然我们可以改变这个前缀. table-prefix配置的前缀必须和database中的表的前缀相同.

<job-repository id="jobRepository"
                table-prefix="SYSTEM.TEST_" />


4.3.3. 在内存中的Job Repository


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(跨机器)中不能使用.


4.4. 配置一个 JobLauncher


最简单的JobLauncher 接口的实现是 SimpleJobLauncher. 他只需要一个JobRepository:


<bean id="jobLauncher"      class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
    <property name="jobRepository" ref="jobRepository" />
</bean>

一旦获得 JobExecution ,JobExecution将会被传入job的执行方法. 最终返回 JobExecution 给调用者.


springbatch默认重启一次restart springbatch启动job_spring_02


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


springbatch默认重启一次restart springbatch启动job_命名空间_03


我们可以配置一个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>


4.5. 运行job


运行job最少的条件需要一个job和一个job launcher. 


4.5.1. 从命令行运行job


调度系统中最常用的一种方式,例如使用quartz,或使用shell脚本.


4.5.1.1. The CommandLineJobRunner


 spring 提供了一个实现类CommandLineJobRunner.这仅仅是一种从command line启动spring batch的方法.  类CommandLineJobRunner 执行了四个任务:


  • 装载 ApplicationContext
  • 传递line command 参数给JobParameters
  • 根据参数定位具体的job
  • 使用JobLauncher运行job.


所有的这些任务都是通过传递的参数完成. 下面是必须的参数:


Table 4.1. CommandLineJobRunner 参数


jobPath

spring application context 的xml文件路径

jobName

job 的名称


jobPath jobName 必须作为第一个和第二个参数.作为job paramete的参数必须以'name=value'的格式传递.

bash$


<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" />


4.5.1.2. 退出代码


一个ExitStatus将会作为JobExecution的一部分从JobLauncher返回. CommandLineJobRunner使用一个ExitCodeMapper接口将string转换为int类型:


public interface ExitCodeMapper { public int intValue(String exitCode);}


ExitCodeMapper 的默认实现是 SimpleJvmExitCodeMapper,0 代表结束, 1 代表通用错误, 2  代表job调用者错误. 


4.5.2. 在web容器中运行job


通常在web容器中以http的方式调用spring batch job,spring batch job都是以异步的方式执行.


springbatch默认重启一次restart springbatch启动job_spring_04


controller是一个spring mvc 的control.. controller 使用JobLauncher 异步启动一个job, JobLauncher立即返回JobExecution. Job 会继续运行。 


@Controllerpublic class JobLauncherController {    @Autowired    JobLauncher jobLauncher;    @Autowired
    Job job;

    @RequestMapping("/jobLauncher.html")
    public void handle() throws Exception{
        jobLauncher.run(job, new JobParameters());
    }
}


4.6. 高级的元数据的使用


到现在为止,已经讨论过JobLauncher 和 JobRepository. 


springbatch默认重启一次restart springbatch启动job_spring_05


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


springbatch默认重启一次restart springbatch启动job_framework_06


4.6.1. Querying the Repository


在任何高级的特性之前,最基本的功能室查询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_" />


4.6.2. JobRegistry


JobRegistry 不是必须的, 但是它可以帮助你了解有多少job在spring context中.  spring framework提供了一个唯一的实现,配置如下:


<bean id="jobRegistry" class="org.spr...MapJobRegistry" />


有两中方法自动填充job mapping,一种是使用一个spring bean post processor,另一种使用一种注册的有生命周期的组件:


4.6.2.1. JobRegistryBeanPostProcessor


这是一个 bean post-processor 可以注册所有的以创建的job:


<bean id="jobRegistryBeanPostProcessor" class="org.spr...JobRegistryBeanPostProcessor"> <property name="jobRegistry" ref="jobRegistry"/></bean>


4.6.2.2. AutomaticJobRegistrar


这是一个生命周期组件,创建子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.


4.6.3. JobOperator


 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();

}


上面的操作代表了来自不同接口的方法, 比如JobLauncherJobRepositoryJobExplorer, 和 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>

4.6.4. JobParametersIncrementer


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>


4.6.5. 停止一个job


大多数情况下使用JobOperator 停止一个job:


Set<Long> executions = jobOperator.getRunningExecutions("sampleJob");jobOperator.stop(executions.iterator().next());


这段代码不能立即停止job运行,当业务逻辑把控制权返回给spring batch framework的时候,他会设置 StepExecution 的状态为 BatchStatus.STOPPED, 保存它, 然后对 JobExecution 。


4.6.6. Aborting a Job


状态为 FAILED的job 可以被重启. 状态为ABANDONED 的不能重启. The ABANDONED 也被用在step executions 标记setp execution是可以跳过的,在一个重启的job中. 如果一个job碰到一个step 在上次的执行中被标记为ABANDONED ,它将跳到下一步.

如果线程死了 ("kill -9" 或者其他错误),job 实例不能知道他的运行状态.你必须手动的告诉他失败了或者aborted. 把他的状态修改为 FAILED 或者 ABANDONED . 如果job不能被重启的话,就把他的状态修改为 FAILED