在 SpringSource 應用平臺中執行 Spring Batch 作業

工程 | Dave Syer | 2008 年 5 月 30 日 | ...

在本文中,我將展示如何在 SpringSource 應用平臺中執行 Spring Batch 作業。我曾在 JavaOne 上演示過這個的早期版本,後來又在倫敦 Spring 使用者組演示過,覺得這可能是個值得分享的好東西。示例程式碼請點選這裡

Bundle(模組)

首先,我們快速瀏覽一下示例程式碼中的 bundle。現在或在安裝了一些 bundle 之後隨時啟動伺服器。

Bundle:hsql-server

這個 bundle 在開發和測試時非常有用。它所做的就是以伺服器模式啟動 HSQLDB 例項,這樣你就可以連線到它並使用 SQL 語句檢查資料庫。你可以直接將其拖放到 Servers View 中的 Platform Server 例項中。首先執行此操作,因為平臺會記住 bundle 的安裝順序,並按照該順序啟動它們。此 bundle 必須首先啟動,因為其他 bundle 會嘗試連線到資料庫伺服器。

bundle 的配置在META-INF/spring/module-context.xml(這對於平臺 bundle 來說是約定俗成的)—— Spring DM 會從META-INF/spring中載入所有 XML 檔案。這個 bundle 只是使用 Spring 配置並啟動 HSQL Server 例項。

有一個整合測試可以用來檢查配置。

Eclipse 專案還包含一個 HSQL Swing 客戶端的啟動配置,這樣你就可以在 GUI 中檢視資料庫內容。啟動它,並使用META-INF/batch-hsql.properties同一專案中的屬性(url=jdbc:hsqldb:hsql://:9005/samples)連線到伺服器例項。

Bundle:data-source

這個 bundle 是一個配置 bundle,它為一個javx.sql.DataSource暴露了一個 OSGi 服務。接下來將其拖放到伺服器中。有一個簡單的整合測試可以用來檢查配置——它只是從資料來源獲取連線並斷言它不為空。

Bundle:data-source-initializer

這也在測試環境中是一個方便的 bundle。它的作用是清除資料庫並重新安裝其餘 bundle 所需的表(批處理元資料以及作業本身的業務表)。當你將此 bundle 安裝到伺服器時,它將新增這些表,這些表隨後應該會顯示在 HSQL Swing GUI 中。安裝一次後,你就可以將其移除(在 Server View 中右鍵單擊 bundle 並選擇 Remove)。

Bundle:job-launcher

這是第一個包含一些 Spring Batch 依賴項的 bundle。它主要是一個配置 bundle,為實際執行作業的其他 bundle 暴露服務(JobLauncher、JobRepository、TransactionManager)。安裝後,它就可以在那裡提供服務。

bundle 的配置在META-INF/spring/module-context.xml像往常一樣。它是simple-job-launcher-context.xml從 Spring Batch 示例中剝離出來的版本。它只需要定義將要匯出的 bean,即

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

<bean id="jobRepository"
    class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="databaseType" value="hsql" />
</bean>
	
<bean id="transactionManager" 
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
</bean>

唯一的其他配置是JobRepository的事務通知(在 Spring Batch 1.1 中不需要)。dataSource 引用來自data-source上面 bundle 暴露的 OSGi 服務。要檢視該引用如何匯入以及本地服務如何暴露給 OSGi Service Registry,我們可以檢視META-INF/spring/osgi-context.xml:

<reference id="dataSource" interface="javax.sql.DataSource" />

<service ref="jobLauncher"
  interface="org.springframework.batch.core.launch.JobLauncher" />
<service ref="jobRepository"
  interface="org.springframework.batch.core.repository.JobRepository" />
<service ref="transactionManager"
  interface="org.springframework.transaction.PlatformTransactionManager" />

這只是 Spring DM 的直接用法。重要的是模組上下文與 OSGi 特定的上下文是分開的。這使我們能夠為模組上下文編寫整合測試,而無需部署到平臺。因此我們有

@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class JobLauncherIntegrationTests {

  @Autowired
  private JobLauncher jobLauncher;

  @Test
  public void testLaunchJob() throws Exception {
    assertNotNull(jobLauncher);
  }

}

測試會載入上下文,新增一個本地資料來源定義來替換 OSGi 中的那個(參見JobLauncherIntegrationTests-context.xml),然後斷言 job launcher 可用。你可以直接從 Eclipse 中以常規方式執行測試。

TheSimpleJobLauncherBean

除了上面在 OSGi 容器中暴露服務的配置外,這個 bundle 還匯出一個包。檢視MANIFEST.MF:
...
Export-Package: com.springsource.consulting.batch.support
...

如果你檢視這個包,你會發現一個便利類,其他 bundle 可以用它來啟動作業(SimpleJobLauncherBean)。TheSimpleJobLauncherBean是一個ApplicationListener這意味著任何包含它的 SpringApplicationContext在啟動時(載入上下文時)都會嘗試啟動作業。它透過監聽一個ContextRefreshedEvent並嘗試啟動作業來做到這一點。

try {
  jobLauncher.run(job, converter.getJobParameters(parameters));
} catch (JobExecutionAlreadyRunningException e) {
  logger.error("This job is already running", e);
} catch (JobInstanceAlreadyCompleteException e) {
  logger.info("This job is already complete.  "
    + "Maybe you need to change the input parameters?", e);
} catch (JobRestartException e) {
  logger.error("Unspecified restart exception", e);
}

啟動作業的計劃是為每個作業建立一個 bundle,並讓其定義一個這樣的SimpleJobLauncherBean例項。

Bundle:hello-world

這是一個非常簡單的作業 bundle。它具有大型作業的所有功能(從檔案輸入並輸出到資料庫),但使用非常簡單的領域模型和非常小的資料集。

將此 bundle 拖放到正在執行的伺服器例項中。它啟動得相當快,由於作業範圍很小,你會立即在批處理元資料中看到效果。在 HSQL Swing GUI 中,你可以執行一些 SQL,例如:

SELECT * FROM BATCH_STEP_EXECUTION

並看到結果,類似這樣:

STEP_EXECUTION_IDVERSIONSTEP_NAME...STATUS...
04helloWorldStep...COMPLETED...

這表明作業已執行(併成功完成)。該步驟的配置在META-INF/spring/module-context.xml:

<bean
	class="com.springsource.consulting.batch.support.SimpleJobLauncherBean">
	<constructor-arg ref="jobLauncher" />
	<constructor-arg ref="helloWorld" />
	<property name="parameters" value="launch.timestamp=${launch.timestamp}"/>
</bean>

<bean id="helloWorld" parent="simpleJob">
	<property name="steps">
		<bean parent="simpleStep" id="helloWorldStep">
			<property name="commitInterval" value="100" />
			<property name="itemReader">
    ...
			</property>
			<property name="itemWriter">
    ...
			</property>
		</bean>
	</property>
</bean>

從上面你可以看到,我們有一個常規的 Spring Batch 作業配置(稱為 "helloWorld"),它只有一個步驟。步驟 ID("helloWorldStep")已在上面的資料庫查詢中看到,表明該步驟已執行(一次)。該步驟所做的只是從一個平面檔案中讀取資料,將行轉換為領域物件,並將它們寫入標準輸出。你可以透過檢查平臺主目錄中的跟蹤日誌來檢視結果,例如,如果你執行tail -f serviceability/trace/trace.log | grep -i hello你應該會看到

[2008-05-30 15:57:04.140] platform-dm-11              
  com.springsource.consulting.batch.hello.MessageWriter.unknown 
  I Message: [Hello World]
[2008-05-30 15:57:04.140] platform-dm-11              
  com.springsource.consulting.batch.hello.MessageWriter.unknown 
  I Message: [Hello Small World]

如果你願意,只需編輯 bundle 中的一個檔案(例如 MANIFEST 或其中一個 Spring 配置檔案)並儲存,就可以再次執行作業。工具會檢測到更改並重新部署 bundle。這個作業的設定方式使得每次執行都以一組新的引數開始(使用時間戳),因此它應該總是成功執行。

作業的結束

作業完成後,無論成功與否,我們都希望系統處於一種能夠指示發生了什麼的狀態。有許多可能的方法可以做到這一點,但我們真正需要的是一種可工具化的方法,以便通知操作員,並採取任何必要的行動(例如重新啟動失敗的作業或為下一次執行重新設定成功作業的引數)。操作員可以是人,也可以是系統功能。

為了指示作業的結束,SimpleJobLauncherBean簡單地獲取了包含它的 OSGiBundle例項,並停止它。這是一個相當簡單的模型,但其優點是 API 定義明確,並且得到 OSGi 平臺的普遍支援。只要容器 (SpringSource Application Platform) 能夠捕獲這些 bundle 事件,原則上就可以非常靈活地擴充套件它。這些功能可能會在平臺 2.0 版本的 Batch Personality 中看到。如果您對應該有的行為以及操作員需要哪些功能有任何想法,請透過對本文發表評論來幫助我們。

我們可以透過登入 Equinox 控制檯來驗證作業 bundle 的狀態。如果你進入命令列並輸入telnet localhost 2401你應該會看到平臺命令列提示符

osgi>

輸入 "ss" 並按回車,你將看到已安裝 bundle 的列表

osgi> ss

Framework is launched.

id      State       Bundle
...
86      RESOLVED    org.springframework.batch.infrastructure_1.0.0
87      RESOLVED    org.springframework.batch.core_1.0.0
88      RESOLVED    com.springsource.org.apache.commons.lang_2.4.0
97      ACTIVE      job.launcher_1.0.0
99      RESOLVED    hello.world_1.0.0

osgi>

因此,ID 為 97 的 bundle 是 job launcher,它處於活動狀態。ID 為 99 的 bundle 是 hello world 作業(在你那裡 ID 可能不同),它已解析,但未處於活動狀態,因為它在作業執行完成後被停止了。

你可以從 OSGi 命令列再次重啟作業

osgi> start 99

osgi> ss

Framework is launched.

id      State       Bundle
...
86      RESOLVED    org.springframework.batch.infrastructure_1.0.0
87      RESOLVED    org.springframework.batch.core_1.0.0
88      RESOLVED    com.springsource.org.apache.commons.lang_2.4.0
97      ACTIVE      job.launcher_1.0.0
99      RESOLVED    hello.world_1.0.0

osgi>

作業 bundle 回到了已解析狀態,但它再次執行了作業,你可以像之前一樣透過 HSQL GUI 或跟蹤日誌進行驗證。

STEP_EXECUTION_IDVERSIONSTEP_NAME...STATUS...
04helloWorldStep...COMPLETED...
14helloWorldStep...COMPLETED...
24helloWorldStep...COMPLETED...

作業的輸入檔案

Hello world 作業的輸入檔案固定打包在 bundle 內部。有時這可能不太合適,實際中常見的是輸入檔案位於檔案系統上。另一方面,對於基於熱部署 bundle 的部署模型,也許將輸入檔案與作業執行打包在一起也不是個壞主意——bundle 的佔用空間可以非常小,並且它包含了對具體執行內容的完整審計記錄。評論會很有趣。

Bundle:football-job

示例程式碼中還有另一個作業 bundle,它是一個更真實的業務應用(它是 Spring Batch 中的 football 作業示例)。你可以像啟動 hello-world 作業一樣啟動和重新啟動它。

如果你剛剛嘗試了這一點,你可能會發現第二次及後續啟動時,資料庫中沒有任何變化。這是預期的,因為你重新啟動了一個已成功完成的作業例項,所以它不會再次處理資料。實際上,JobLauncher丟擲了一個異常,被SimpleJobLauncherBean捕獲並記錄了(因此它顯示在跟蹤日誌中)。

設定工作區

SpringSource 應用平臺

如果你不知何故錯過了釋出公告,或者還沒有時間嘗試(也許你認為它只與 Web 應用有關),這裡有一些連結可以幫助你入門

前提條件

要按照示例並執行示例程式碼,你需要以下部分或全部內容。我全部都使用了,所以最流暢的體驗可能來自使用所有這些。

安裝 SpringSource Eclipse 工具後,你需要建立一個伺服器例項。轉到 File->New->Other... 並找到 Server->Server。選擇 SpringSource 和其下的伺服器型別,並使用瀏覽對話方塊找到平臺安裝位置。

下載依賴項

我們都滿懷期待地等待 SpringSource Eclipse 工具提供自動下載和安裝依賴項的功能。如果你閱讀本文時該功能尚未提供,那麼如果你願意,可以按照我的方式進行。以下是我所做的:
  • (可選)從一個空的本地 Maven 倉庫開始(刪除 ~/.m2/repository,或在 settings.xml 中指向一個新位置)
  • 在首次安裝 bundle 之前,開啟專案的 pom.xml 檔案並找到id 為 "shell" 的元素。
  • 將 activeByDefault 標誌更改為 true,然後等待 Q4E 下載依賴項。
  • 使用 Q4E 視覺化工具檢查依賴項(右鍵單擊專案並選擇 Maven2->Analyse Dependencies (或 Visualize Dependencies))。你只需這樣做就可以檢視傳遞性依賴項。(你也可以在命令列中使用$ mvn dependency:tree命令)。
  • 在這一點上,我總是還會右鍵單擊專案並選擇 Maven2->Fetch Source JARs。這是可選的,但會使開發更容易,除錯也成為可能。
  • 將直接依賴項複製到平臺的bundles/usr目錄。嚴格來說,你只需要複製那些不在bundles/ext中的依賴項。在命令列中(使用合理的作業系統),你可以執行
    $ find ~/.m2/repository -name \*.jar -exec cp {} bundles/usr \;
    
  • 你可能需要對部分或全部MANIFEST.MFbundle 專案中的檔案進行“偽編輯”,以強制工具重新整理。
  • 將 activeByDefault 標誌切換回 false。

無須重啟 Eclipse 或其他任何東西。“Bundle Dependencies”類路徑容器應該包含你剛剛下載的執行時依賴項。當 Problems 檢視中所有的 Eclipse 錯誤(憤怒的紅色標記)都消失後,我們就可以開始了。

我很高興聽到有更好方法的人分享經驗。其他人已經發展出了其他方法,但在我看來都不太方便。實際上,一個命令列 Maven 目標會很容易編寫,但我還沒有看到這樣的。

Beta5 更新

在 Beta5 中,你不需要“查詢並複製”的步驟,因為 platform.config 允許你將本地 Maven 倉庫指定為依賴項來源,而不是 bundles/usr。

原則上,你根本不需要 Maven 本地倉庫來獲取執行時依賴項。你可以開啟平臺執行時(在 Servers 檢視中右鍵單擊並選擇 Open),然後直接瀏覽並下載依賴項到bundles/usr。目前唯一的缺點(工具團隊正在努力改進這一點)是它不提供任何傳遞性依賴項的檢視——你必須明確知道需要哪些 bundle。對於本部落格的示例,這很容易,因為所有的 MANIFESTs 都已經完整地指定了依賴項。當你不知道它們是什麼並且必須從頭建立 MANIFEST 時,這就更困難了。為此,我目前仍然使用 Q4E。

總結

常言道條條大路通羅馬,而平臺是一個非常豐富的環境,因此你可以確定我在這裡展示的並不是在平臺中執行作業的唯一方法。希望這是一個好的起點。

應用平臺 1.0 版本的大部分重點在於 Web 層,雖然這顯然是必不可少(且非常棘手)的功能,但還有其他更重要的事情要做。2.0 版本將具有特定的批處理相關功能(一個 Batch Personality),因此我們現在所做的任何事情都將有助於充實該版本的特性需求。因此,如果你有機會嘗試一下並有一些建設性的意見,特別是關於操作方面的,它們將在我們開始構建 Batch Personality 時派上用場。

獲取 Spring 新聞通訊

保持與 Spring 新聞通訊的聯絡

訂閱

搶先一步

VMware 提供培訓和認證,助你突飛猛進。

瞭解更多

獲取支援

Tanzu Spring 透過一個簡單的訂閱即可提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視全部