高階元資料用法

到目前為止,我們已經討論了 JobLauncherJobRepository 介面。它們共同代表了 Job 的簡單啟動以及 Batch 域物件的基本 CRUD 操作。

Job Repository
圖 1. Job Repository

JobLauncher 使用 JobRepository 建立新的 JobExecution 物件並執行它們。JobStep 實現稍後會在 Job 執行期間使用同一個 JobRepository 對這些執行進行基本更新。基本操作足以應對簡單的場景。然而,在包含數百個批處理 Job 和複雜排程要求的大型批處理環境中,需要更高階的元資料訪問。

Job Repository Advanced
圖 2. 高階 Job Repository 訪問

JobExplorerJobOperator 介面將在接下來的章節中討論,它們為查詢和控制元資料提供了附加功能。

查詢 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);
}

從其方法簽名可以看出,JobExplorerJobRepository 的只讀版本,與 JobRepository 類似,可以透過使用 Factory Bean 輕鬆配置。

  • Java

  • XML

以下示例展示瞭如何在 Java 中配置 JobExplorer

Java 配置
...
// 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

XML 配置
<bean id="jobExplorer" class="org.spr...JobExplorerFactoryBean"
      p:dataSource-ref="dataSource" />

在本章前面,我們提到可以修改 JobRepository 的表字首,以允許不同的版本或 Schema。由於 JobExplorer 使用相同的表,因此也需要設定字首的功能。

  • Java

  • XML

以下示例展示瞭如何在 Java 中為 JobExplorer 設定表字首

Java 配置
...
// 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 設定表字首

XML 配置
<bean id="jobExplorer" class="org.spr...JobExplorerFactoryBean"
		p:tablePrefix="SYSTEM."/>

JobRegistry

JobRegistry(及其父介面 JobLocator)不是必需的,但如果你想跟蹤上下文中可用的 Job,它會很有用。當 Job 在其他地方(例如子上下文中)建立時,它也適用於在應用程式上下文中集中收集 Job。你還可以使用自定義 JobRegistry 實現來操作已註冊 Job 的名稱和其他屬性。框架僅提供了一種實現,它基於 Job 名稱到 Job 例項的簡單 Map。

  • 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 中定義 Job 時包含 JobRegistry

<bean id="jobRegistry" class="org.springframework.batch.core.configuration.support.MapJobRegistry" />

你可以透過以下方式之一填充 JobRegistry:使用 Bean Post Processor,或者使用 Smart Initializing Singleton,或者使用 Registrar Lifecycle Component。接下來的章節將描述這些機制。

JobRegistryBeanPostProcessor

這是一個 Bean Post Processor,可以在 Job 建立時註冊它們。

  • Java

  • XML

以下示例展示瞭如何在 Java 中定義 Job 時包含 JobRegistryBeanPostProcessor

Java 配置
@Bean
public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
    JobRegistryBeanPostProcessor postProcessor = new JobRegistryBeanPostProcessor();
    postProcessor.setJobRegistry(jobRegistry);
    return postProcessor;
}

以下示例展示瞭如何在 XML 中定義 Job 時包含 JobRegistryBeanPostProcessor

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

雖然並非嚴格必要,但示例中的 Post Processor 已被賦予一個 id,以便可以將其包含在子上下文(例如,作為父 Bean 定義)中,並使在那裡建立的所有 Job 也自動註冊。

已棄用

從版本 5.2 開始,JobRegistryBeanPostProcessor 類已被棄用,取而代之的是 JobRegistrySmartInitializingSingleton,請參閱 JobRegistrySmartInitializingSingleton

JobRegistrySmartInitializingSingleton

這是一個 SmartInitializingSingleton,它會在 Job 登錄檔中註冊所有 Singleton Job。

  • Java

  • XML

以下示例展示瞭如何在 Java 中定義 JobRegistrySmartInitializingSingleton

Java 配置
@Bean
public JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton(JobRegistry jobRegistry) {
    return new JobRegistrySmartInitializingSingleton(jobRegistry);
}

以下示例展示瞭如何在 XML 中定義 JobRegistrySmartInitializingSingleton

XML 配置
<bean class="org.springframework.batch.core.configuration.support.JobRegistrySmartInitializingSingleton">
    <property name="jobRegistry" ref="jobRegistry" />
</bean>

AutomaticJobRegistrar

這是一個生命週期元件,它建立子上下文並在 Job 建立時從這些上下文中註冊 Job。這樣做的一個優點是,雖然子上下文中的 Job 名稱在登錄檔中仍然需要全域性唯一,但它們的依賴項可以具有“自然”名稱。例如,你可以建立一組 XML 配置檔案,每個檔案只有一個 Job,但它們都對具有相同 Bean 名稱(例如 reader)的 ItemReader 有不同的定義。如果所有這些檔案都匯入到同一個上下文中,Reader 的定義就會衝突並相互覆蓋,但有了 Automatic Registrar,這就可以避免。這使得整合從應用程式不同模組貢獻的 Job 變得更加容易。

  • Java

  • XML

以下示例展示瞭如何在 Java 中定義 Job 時包含 AutomaticJobRegistrar

Java 配置
@Bean
public AutomaticJobRegistrar registrar() {

    AutomaticJobRegistrar registrar = new AutomaticJobRegistrar();
    registrar.setJobLoader(jobLoader());
    registrar.setApplicationContextFactories(applicationContextFactories());
    registrar.afterPropertiesSet();
    return registrar;

}

以下示例展示瞭如何在 XML 中定義 Job 時包含 AutomaticJobRegistrar

XML 配置
<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>

該 Registrar 有兩個強制屬性:一個 ApplicationContextFactory 陣列(在前面的示例中從方便的 Factory Bean 建立)和一個 JobLoaderJobLoader 負責管理子上下文的生命週期並在 JobRegistry 中註冊 Job。

ApplicationContextFactory 負責建立子上下文。最常見的用法是(如前面的示例所示)使用 ClassPathXmlApplicationContextFactory。該 Factory 的一個特點是,預設情況下,它會將父上下文的一些配置複製到子上下文。因此,例如,只要子上下文的配置與父上下文相同,你就不需要在子上下文中重新定義 PropertyPlaceholderConfigurer 或 AOP 配置。

你可以將 AutomaticJobRegistrarJobRegistryBeanPostProcessor 結合使用(前提是你也使用了 DefaultJobLoader)。例如,如果在主父上下文和子位置中都定義了 Job,這可能是可取的。

JobOperator

如前所述,JobRepository 提供了元資料的 CRUD 操作,而 JobExplorer 提供了元資料的只讀操作。然而,當這些操作結合使用以執行常見的監控任務時(例如停止、重啟或彙總 Job,這通常由批處理操作員完成),它們最有益處。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();

}

前面的操作代表了來自許多不同介面的方法,例如 JobLauncherJobRepositoryJobExplorerJobRegistry。因此,提供的 JobOperator 實現(SimpleJobOperator)具有許多依賴項。

  • Java

  • XML

以下示例展示了 Java 中 SimpleJobOperator 的典型 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;
 }

以下示例展示了 XML 中 SimpleJobOperator 的典型 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 註解會自動在應用程式上下文中註冊一個 Job Operator Bean。

如果你在 Job Repository 上設定了表字首,請不要忘記在 Job Explorer 上也設定。

JobParametersIncrementer

JobOperator 中的大多數方法都是不言自明的,你可以在 介面的 Javadoc 中找到更詳細的解釋。然而,startNextInstance 方法值得注意。此方法總是啟動 Job 的一個新例項。如果在 JobExecution 中存在嚴重問題並且需要從頭開始重新啟動 Job,這會非常有用。與 JobLauncher 不同(JobLauncher 需要一個新的 JobParameters 物件來觸發新的 JobInstance),如果引數與之前任何一組引數不同,startNextInstance 方法會使用與 Job 關聯的 JobParametersIncrementer 來強制 Job 建立一個新例項。

public interface JobParametersIncrementer {

    JobParameters getNext(JobParameters parameters);

}

JobParametersIncrementer 的契約是,給定一個 JobParameters 物件,它透過遞增其中包含的任何必要值來返回“下一個” JobParameters 物件。此策略很有用,因為框架無法知道對 JobParameters 進行哪些更改會使其成為“下一個”例項。例如,如果 JobParameters 中唯一的值是日期,並且應該建立下一個例項,那麼該值應該遞增一天還是一週(例如,如果 Job 是每週執行的)?同樣也可以用於任何有助於標識 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 中定義的 Job,你可以透過 Builder 中提供的 incrementer 方法將遞增器與 Job 關聯,如下所示

@Bean
public Job footballJob(JobRepository jobRepository) {
    return new JobBuilder("footballJob", jobRepository)
    				 .incrementer(sampleIncrementer())
    				 ...
                     .build();
}

對於在 XML 中定義的 Job,你可以透過名稱空間中的 incrementer 屬性將遞增器與 Job 關聯,如下所示

<job id="footballJob" incrementer="sampleIncrementer">
    ...
</job>

停止 Job

JobOperator 最常見的用例之一是優雅地停止 Job

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

關機不是立即的,因為無法強制立即關機,特別是如果執行當前位於框架無法控制的開發人員程式碼中,例如業務服務。但是,一旦控制權返回給框架,它會將當前 StepExecution 的狀態設定為 BatchStatus.STOPPED,儲存它,並在完成之前對 JobExecution 進行相同的操作。

中止 Job

狀態為 FAILED 的 Job 執行可以重新啟動(如果 Job 是可重新啟動的)。狀態為 ABANDONED 的 Job 執行無法由框架重新啟動。ABANDONED 狀態也用於 Step 執行中,以便在重新啟動的 Job 執行中將其標記為可跳過。如果一個 Job 正在執行並且遇到一個在之前失敗的 Job 執行中被標記為 ABANDONED 的 Step,它將(根據 Job Flow 定義和 Step 執行的 Exit Status 確定)繼續執行下一個 Step。

如果程序死亡(kill -9 或伺服器故障),Job 當然沒有在執行,但 JobRepository 無法知道,因為在程序死亡之前沒有人告訴它。你必須手動告訴它你知道執行失敗了或者應該被視為已中止(將其狀態更改為 FAILEDABANDONED)。這是一個業務決策,無法自動化。只有當 Job 是可重新啟動的且你知道重新啟動資料有效時,才將其狀態更改為 FAILED