高階元資料用法
到目前為止,我們已經討論了 JobLauncher
和 JobRepository
介面。它們共同代表了 Job 的簡單啟動以及 Batch 域物件的基本 CRUD 操作。

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

JobExplorer
和 JobOperator
介面將在接下來的章節中討論,它們為查詢和控制元資料提供了附加功能。
查詢 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
類似,可以透過使用 Factory 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
的表字首,以允許不同的版本或 Schema。由於 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
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
@Bean
public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
JobRegistryBeanPostProcessor postProcessor = new JobRegistryBeanPostProcessor();
postProcessor.setJobRegistry(jobRegistry);
return postProcessor;
}
以下示例展示瞭如何在 XML 中定義 Job 時包含 JobRegistryBeanPostProcessor
<bean id="jobRegistryBeanPostProcessor" class="org.spr...JobRegistryBeanPostProcessor">
<property name="jobRegistry" ref="jobRegistry"/>
</bean>
雖然並非嚴格必要,但示例中的 Post Processor 已被賦予一個 id
,以便可以將其包含在子上下文(例如,作為父 Bean 定義)中,並使在那裡建立的所有 Job 也自動註冊。
已棄用
從版本 5.2 開始, |
JobRegistrySmartInitializingSingleton
這是一個 SmartInitializingSingleton
,它會在 Job 登錄檔中註冊所有 Singleton Job。
-
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>
AutomaticJobRegistrar
這是一個生命週期元件,它建立子上下文並在 Job 建立時從這些上下文中註冊 Job。這樣做的一個優點是,雖然子上下文中的 Job 名稱在登錄檔中仍然需要全域性唯一,但它們的依賴項可以具有“自然”名稱。例如,你可以建立一組 XML 配置檔案,每個檔案只有一個 Job,但它們都對具有相同 Bean 名稱(例如 reader
)的 ItemReader
有不同的定義。如果所有這些檔案都匯入到同一個上下文中,Reader 的定義就會衝突並相互覆蓋,但有了 Automatic Registrar,這就可以避免。這使得整合從應用程式不同模組貢獻的 Job 變得更加容易。
-
Java
-
XML
以下示例展示瞭如何在 Java 中定義 Job 時包含 AutomaticJobRegistrar
@Bean
public AutomaticJobRegistrar registrar() {
AutomaticJobRegistrar registrar = new AutomaticJobRegistrar();
registrar.setJobLoader(jobLoader());
registrar.setApplicationContextFactories(applicationContextFactories());
registrar.afterPropertiesSet();
return registrar;
}
以下示例展示瞭如何在 XML 中定義 Job 時包含 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>
該 Registrar 有兩個強制屬性:一個 ApplicationContextFactory
陣列(在前面的示例中從方便的 Factory Bean 建立)和一個 JobLoader
。JobLoader
負責管理子上下文的生命週期並在 JobRegistry
中註冊 Job。
ApplicationContextFactory
負責建立子上下文。最常見的用法是(如前面的示例所示)使用 ClassPathXmlApplicationContextFactory
。該 Factory 的一個特點是,預設情況下,它會將父上下文的一些配置複製到子上下文。因此,例如,只要子上下文的配置與父上下文相同,你就不需要在子上下文中重新定義 PropertyPlaceholderConfigurer
或 AOP 配置。
你可以將 AutomaticJobRegistrar
與 JobRegistryBeanPostProcessor
結合使用(前提是你也使用了 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();
}
前面的操作代表了來自許多不同介面的方法,例如 JobLauncher
、JobRepository
、JobExplorer
和 JobRegistry
。因此,提供的 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
無法知道,因為在程序死亡之前沒有人告訴它。你必須手動告訴它你知道執行失敗了或者應該被視為已中止(將其狀態更改為 FAILED
或 ABANDONED
)。這是一個業務決策,無法自動化。只有當 Job 是可重新啟動的且你知道重新啟動資料有效時,才將其狀態更改為 FAILED
。