在 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://localhost: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 社区所有即将举行的活动。

查看全部