在 SpringSource Application Platform 中运行 Spring Batch 作业

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

在本文中,我将向您展示如何在 SpringSource Application Platform 中运行 Spring Batch 作业。我之前为 JavaOne 做了一个小演示,然后在伦敦 Spring 用户组再次进行了演示,我认为分享一下会很有用。示例代码 在此处

Bundle(捆绑包)

首先,我们将快速浏览一下示例代码中的 Bundle。现在,或者在安装了一些 Bundle 之后,随时启动服务器。

Bundle: hsql-server

这个 Bundle 对于开发和测试很有用。它所做的只是以服务器模式启动一个 HSQLDB 实例,这样您就可以连接到它并使用 SQL 语句检查数据库。您只需将其拖放到 Servers View 中的 Platform Server 实例中即可。请先执行此操作,因为 Platform 会记住安装 Bundle 的顺序,并按该顺序启动它们。必须先启动此 Bundle,因为其他 Bundle 将尝试连接到数据库服务器。

Bundle 的配置位于META-INF/spring/module-context.xml(这对于 Platform Bundle 是约定俗成的)- Spring DM 会从以下位置拾取所有 XML 文件META-INF/spring。此 Bundle 仅使用 Spring 来配置和启动 HSQL 服务器的实例。

有一个集成测试可用于检查配置。

Eclipse 项目还包含一个用于 HSQL Swing 客户端的启动配置,因此您可以在 GUI 中查看数据库内容。启动它,并使用同一项目中的META-INF/batch-hsql.properties中提供的属性连接到服务器实例(url=jdbc:hsqldb:hsql://:9005/samples)。

Bundle: data-source

此 Bundle 是一个配置 Bundle,它公开一个用于javx.sql.DataSource的 OSGi 服务。接下来将其放入服务器。有一个简单的集成测试可用于检查配置 - 它只是获取一个数据源连接并断言它不为 null。

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照常。这是 Spring Batch 示例中simple-job-launcher-context.xml的一个精简版本。它只需要定义将导出的 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-sourceBundle 公开的 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 的上下文是分开的。这允许我们为模块上下文编写集成测试,而无需部署到 Platform。因此,我们有

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

  @Autowired
  private JobLauncher jobLauncher;

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

}

测试加载上下文,添加本地数据源定义以替换 OSGi 数据源(参见JobLauncherIntegrationTests-context.xml),然后断言作业启动器可用。您可以像往常一样直接从 Eclipse 运行测试。

SimpleJobLauncherBean

除了上面公开 OSGi 容器中服务的配置之外,此 Bundle 还导出一个包。查看MANIFEST.MF:
...
Export-Package: com.springsource.consulting.batch.support
...

如果您查看此包,您会找到一个便捷类,其他 Bundle 可以使用它来启动作业(SimpleJobLauncherBean)。SimpleJobLauncherBean是一个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”)已在上面的数据库查询中看到,表明该步骤已执行(一次)。所有步骤所做的就是从平面文件读取数据,将行转换为域对象,然后将它们写入 stdout。您可以通过检查 Platform 主目录中的跟踪日志来查看结果,例如,如果您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 事件,它就可以非常灵活地扩展。这些是我们可能会在 Platform 2.0 版本中看到的 Batch Personality 功能。如果您对行为和操作员所需的功能有任何想法,请通过评论本文来帮助我们。

我们可以通过登录 Equinox 控制台来验证作业 Bundle 的状态。如果您转到命令行并键入telnet localhost 2401您应该会看到 Platform 的命令提示符。

osgi>

键入“ss”并按 Enter 键,您将看到已安装的 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 是作业启动器,并且它是活动的。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 的足球作业示例)。您可以以与 hello-world 作业相同的方式启动和重新启动它。

如果您刚刚尝试过,您可能会发现第二次及后续启动时,数据库中没有任何变化。这是预期的,因为您重新启动了一个成功完成的作业实例,所以它不会再次处理数据。事实上,一个异常是由JobLauncher抛出的,被SimpleJobLauncherBean捕获并记录(因此它会出现在跟踪日志中)。

设置工作区

SpringSource Application Platform

如果您以某种方式错过了发布公告,或者还没有时间尝试(也许您认为它只与 Web 应用程序有关),这里有一些链接可以帮助您入门

先决条件

要遵循示例并运行示例代码,您将需要以下部分或全部内容。我使用了所有这些,因此使用它们可能会获得最顺畅的体验。

安装 SpringSource Eclipse 工具后,您需要创建一个服务器实例。转到 File->New->Other... 并找到 Server->Server。选择 SpringSource 和下面的服务器类型,然后使用浏览对话框查找 Platform 安装。

下载依赖项

我们都在热切地等待 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。这是可选的,但可以简化开发并进行调试。
  • 将直接依赖项复制到 Platformbundles/usr目录。严格来说,您只需要复制那些尚未在bundles/ext中的。在命令行(使用合适的操作系统)中,您可以执行
    $ find ~/.m2/repository -name \*.jar -exec cp {} bundles/usr \;
    
  • 您可能需要对 Bundle 项目中的部分或全部MANIFEST.MF文件进行“虚假编辑”以强制工具刷新。
  • 将 activeByDefault 标志切换回 false。

无需重启 Eclipse 或其他任何操作。“Bundle Dependencies”类路径容器应包含您刚刚下载的运行时依赖项。当 Problems 视图中的所有 Eclipse 错误(红色的边距标记)都消失后,我们就准备好了。

我非常乐意听到人们有一个更好的方法来做到这一点。其他人已经发展了其他方法,但对我来说似乎都不太方便。实际上,编写一个命令行 Maven 目标很容易,但我还没有见过那个。

Beta5 更新

使用 beta5,您不需要“查找和复制”步骤,因为 platform.config 允许您将本地 Maven 存储库指向作为依赖项源,而不是 bundles/usr。

原则上,您也完全不需要运行时依赖项的 Maven 本地存储库。您可以打开 Platform 运行时(在 Servers 视图中右键单击并选择 Open),然后直接浏览并下载依赖项到bundles/usr。目前唯一的缺点是(工具团队正在努力改进它),它没有提供任何传递依赖项的视图 - 您必须明确知道需要哪些 Bundle。对于本博客的示例,这很容易,因为 MANIFESTs 都已完全指定了依赖项。当您不确定它们是什么并且必须从头开始创建 MANIFEST 时,情况就更糟了。为此,我目前仍在使用 Q4E。

结束

磨刀不误砍柴工,Platform 是一个非常丰富的环境,所以您可以确定我在这里展示的并不是在 Platform 中运行作业的唯一方法。希望它是一个好的起点。

Application Platform 1.0 发布的大部分重点都在 Web 层,虽然这显然是必不可少的(并且非常棘手),但还有其他事情需要处理。2.0 版本将具有特定的批处理相关功能(Batch Personality),因此我们现在所做的一切都将有助于充实该版本的关键功能需求。因此,如果您有机会尝试一下,并有一些建设性的意见,特别是关于操作方面的,当我们开始构建 Batch Personality 时,它们将非常有用。

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有