|
对于最新稳定版本,请使用 Spring Batch 文档 6.0.3! |
高级元数据用法
到目前为止,已经讨论了 JobLauncher 和 JobRepository 接口。它们共同代表了作业的简单启动以及批处理领域对象的基本 CRUD 操作:
JobLauncher 使用
JobRepository 来创建新的
JobExecution 对象并运行它们。
Job 和 Step 的实现
随后在 Job 运行期间,对相同执行使用相同的 JobRepository 进行基本更新。
基本操作足以满足简单场景的需求。然而,在拥有数百个批处理作业且调度需求复杂的大型批处理环境中,需要更高级的元数据访问能力:
JobExplorer 和
JobOperator 接口(将在接下来的章节中讨论)提供了用于查询和控制元数据的额外功能。
查询仓库
在任何高级功能之前,最基本的需求是能够查询存储库以获取现有的执行记录。此功能由 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 轻松配置。
-
Java
-
XML
以下示例展示了如何在Java中配置一个JobExplorer:
...
// This would reside in your DefaultBatchConfiguration extension
@Bean
public JobExplorer jobExplorer() throws Exception {
JobExplorerFactoryBean factoryBean = new JobExplorerFactoryBean();
factoryBean.setDataSource(this.dataSource);
return factoryBean.getObject();
}
...
以下示例展示了如何在 XML 中配置一个 JobExplorer:
<bean id="jobExplorer" class="org.spr...JobExplorerFactoryBean"
p:dataSource-ref="dataSource" />
本章前文中,我们提到您可以修改JobRepository的表前缀,以支持不同的版本或模式。由于JobExplorer使用相同的表,因此它也需要具备设置前缀的能力。
-
Java
-
XML
以下示例展示了如何在 Java 中为 JobExplorer 设置表前缀:
...
// This would reside in your DefaultBatchConfiguration extension
@Bean
public JobExplorer jobExplorer() throws Exception {
JobExplorerFactoryBean factoryBean = new JobExplorerFactoryBean();
factoryBean.setDataSource(this.dataSource);
factoryBean.setTablePrefix("SYSTEM.");
return factoryBean.getObject();
}
...
以下示例展示了如何在 XML 中为 JobExplorer 设置表前缀:
<bean id="jobExplorer" class="org.spr...JobExplorerFactoryBean"
p:tablePrefix="SYSTEM."/>
作业注册表
JobRegistry(及其父接口JobLocator)并非强制要求,但如果您希望跟踪上下文中可用的作业,它会非常有用。当作业在其他地方(例如在子上下文中)创建时,它还有助于在应用程序上下文中集中收集这些作业。您还可以使用自定义的JobRegistry实现来操作已注册作业的名称和其他属性。
框架仅提供一种实现,该实现基于从作业名称到作业实例的简单映射。
-
Java
-
XML
当使用 @EnableBatchProcessing 时,系统会为您提供一个 JobRegistry。
以下示例展示如何配置您自己的 JobRegistry:
...
// This is already provided via the @EnableBatchProcessing but can be customized via
// overriding the bean in the DefaultBatchConfiguration
@Override
@Bean
public JobRegistry jobRegistry() throws Exception {
return new MapJobRegistry();
}
...
以下示例展示了如何在 XML 中定义作业时包含一个 JobRegistry:
<bean id="jobRegistry" class="org.springframework.batch.core.configuration.support.MapJobRegistry" />
您可以通过以下方式填充 JobRegistry:使用 Bean 后置处理器,或使用智能初始化单例,或使用注册器生命周期组件。接下来的章节将描述这些机制。
JobRegistryBeanPostProcessor
这是一个 Bean 后处理器,可以在创建所有作业时进行注册。
-
Java
-
XML
以下示例展示了如何为在 Java 中定义的作业包含 JobRegistryBeanPostProcessor:
@Bean
public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
JobRegistryBeanPostProcessor postProcessor = new JobRegistryBeanPostProcessor();
postProcessor.setJobRegistry(jobRegistry);
return postProcessor;
}
以下示例展示了如何为在 XML 中定义的作业包含 JobRegistryBeanPostProcessor:
<bean id="jobRegistryBeanPostProcessor" class="org.spr...JobRegistryBeanPostProcessor">
<property name="jobRegistry" ref="jobRegistry"/>
</bean>
虽然这并非严格必要,但示例中的后处理器已被赋予一个id,以便它可以被包含在子上下文中(例如,作为父 Bean 定义),并使在那里创建的所有作业也能自动注册。
|
弃用
自 5.2 版本起, |
JobRegistrySmartInitializingSingleton
这是一个 SmartInitializingSingleton,用于在作业注册表中注册所有单例作业。
-
Java
-
XML
以下示例展示了如何在 Java 中定义一个 JobRegistrySmartInitializingSingleton:
@Bean
public JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton(JobRegistry jobRegistry) {
return new JobRegistrySmartInitializingSingleton(jobRegistry);
}
以下示例展示了如何在 XML 中定义一个 JobRegistrySmartInitializingSingleton:
<bean class="org.springframework.batch.core.configuration.support.JobRegistrySmartInitializingSingleton">
<property name="jobRegistry" ref="jobRegistry" />
</bean>
自动作业注册器
这是一个生命周期组件,用于创建子上下文,并在这些子上下文创建时从中注册作业。这样做的一大优势是:虽然子上下文中的作业名称在注册表中仍需保持全局唯一,但它们的依赖项可以使用“自然”的名称。例如,您可以创建一组 XML 配置文件,每个文件仅包含一个作业,但都定义了具有相同 Bean 名称(如 reader)的不同 ItemReader。如果将所有这些文件导入到同一个上下文中,读取器定义将会发生冲突并相互覆盖;而使用自动注册器则可以避免这一问题。这使得集成来自应用程序不同模块的贡献作业变得更加容易。
-
Java
-
XML
以下示例展示了如何为在 Java 中定义的作业包含 AutomaticJobRegistrar:
@Bean
public AutomaticJobRegistrar registrar() {
AutomaticJobRegistrar registrar = new AutomaticJobRegistrar();
registrar.setJobLoader(jobLoader());
registrar.setApplicationContextFactories(applicationContextFactories());
registrar.afterPropertiesSet();
return registrar;
}
以下示例展示了如何在 XML 中定义作业时包含 AutomaticJobRegistrar:
<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(在前面的示例中通过一个便捷的工厂 Bean 创建)组成的数组,以及一个JobLoader。JobLoader 负责管理子上下文的生命周期,并在JobRegistry中注册作业。
ApplicationContextFactory 负责创建子上下文。最常见的用法(如前面的示例所示)是使用ClassPathXmlApplicationContextFactory。该工厂的一个特性是,默认情况下,它会将父上下文的部分配置复制到子上下文中。因此,例如,如果子上下文中的PropertyPlaceholderConfigurer或 AOP 配置与父上下文相同,则无需在子上下文中重新定义它们。
您可以将 AutomaticJobRegistrar 与 JobRegistryBeanPostProcessor 结合使用(只要您同时也使用 DefaultJobLoader)。
例如,如果在主父上下文以及子位置中都定义了作业,则可能需要这样做。
作业操作员
如前所述,JobRepository 提供对元数据的 CRUD 操作,而 JobExplorer 提供对元数据的只读操作。然而,当将这些操作结合使用时,它们在执行常见的监控任务(例如停止、重启或汇总作业)时最为有用,这通常是批处理操作员所执行的操作。Spring Batch 在 JobOperator 接口中提供了此类操作:
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)具有许多依赖项。
-
Java
-
XML
以下示例展示了 SimpleJobOperator 在 Java 中的典型 Bean 定义:
/**
* All injected dependencies for this bean are provided by the @EnableBatchProcessing
* infrastructure out of the box.
*/
@Bean
public SimpleJobOperator jobOperator(JobExplorer jobExplorer,
JobRepository jobRepository,
JobRegistry jobRegistry,
JobLauncher jobLauncher) {
SimpleJobOperator jobOperator = new SimpleJobOperator();
jobOperator.setJobExplorer(jobExplorer);
jobOperator.setJobRepository(jobRepository);
jobOperator.setJobRegistry(jobRegistry);
jobOperator.setJobLauncher(jobLauncher);
return jobOperator;
}
以下示例展示了 SimpleJobOperator 在 XML 中的典型 Bean 定义:
<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>
自 5.0 版本起,@EnableBatchProcessing 注解会自动在应用上下文中注册一个作业操作器 Bean。
| 如果您在作业存储库上设置了表前缀,请别忘了也在作业浏览器上进行设置。 |
JobParametersIncrementer
JobOperator 上的大多数方法都是不言自明的,您可以在 接口的 Javadoc 中找到更详细的解释。然而,startNextInstance 方法值得注意。此方法始终启动一个新的 Job 实例。如果 JobExecution 中出现严重问题,并且需要从头开始重新启动 Job,这将非常有用。与 JobLauncher(需要一个新的 JobParameters 对象来触发新的 JobInstance)不同,如果参数与之前的任何参数集不同,startNextInstance 方法会使用绑定到 Job 的 JobParametersIncrementer 强制将 Job 设为新实例:
public interface JobParametersIncrementer {
JobParameters getNext(JobParameters parameters);
}
JobParametersIncrementer 的约定是:给定一个 JobParameters 对象,它会通过递增其中任何必要的值来返回“下一个”JobParameters 对象。此策略非常有用,因为框架无法得知对 JobParameters 进行哪些更改才能使其成为“下一个”实例。例如,如果 JobParameters 中唯一的值是一个日期,并且需要创建下一个实例,那么该值应该增加一天还是一周(例如,如果作业是每周运行的)?对于任何用于标识 Job 的数值值,情况也是如此,如下例所示:
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();
}
}
在此示例中,键为 run.id 的值用于区分 JobInstances。如果传入的 JobParameters 为 null,则可以假定 Job 此前从未运行过,因此可返回其初始状态。否则,将获取旧值,将其加一后返回。
-
Java
-
XML
对于在 Java 中定义的作业,您可以通过构建器中提供的 incrementer 方法将增量器与 Job 关联,如下所示:
@Bean
public Job footballJob(JobRepository jobRepository) {
return new JobBuilder("footballJob", jobRepository)
.incrementer(sampleIncrementer())
...
.build();
}
对于在 XML 中定义的作业,您可以通过命名空间中的 incrementer 属性将增量器与 Job 关联,如下所示:
<job id="footballJob" incrementer="sampleIncrementer">
...
</job>
停止作业
JobOperator 最常见的用例之一是优雅地停止一个
Job:
Set<Long> executions = jobOperator.getRunningExecutions("sampleJob");
jobOperator.stop(executions.iterator().next());
关闭操作并非立即执行,因为无法强制立即关闭,尤其是当执行流程当前处于框架无法控制的开发者代码中时(例如业务服务)。然而,一旦控制权返回到框架,它会将当前 StepExecution 的状态设置为 BatchStatus.STOPPED 并保存,然后在结束前对 JobExecution 执行相同操作。
中止任务
状态为 FAILED 的作业执行可以重新启动(如果该 Job 是可重启的)。状态为 ABANDONED 的作业执行无法由框架重新启动。ABANDONED 状态也用于步骤执行中,以标记它们在重启的作业执行中可被跳过。如果作业正在运行并遇到在上一次失败的作业执行中被标记为 ABANDONED 的步骤,它将跳转到下一步(具体由作业流定义和步骤执行退出状态决定)。
如果进程已终止(kill -9 或服务器故障),则该作业当然不再运行,但 JobRepository 无法得知此情况,因为在进程终止前无人通知它。您必须手动告知系统:您已知该执行要么失败,要么应被视为已中止(将其状态更改为 FAILED 或 ABANDONED)。这是一个业务决策,无法自动化处理。仅当作业可重启且您确认重启数据有效时,才将状态更改为 FAILED。