在 SpringSource Application Platform 中运行 Spring Batch 作业

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

在本文中,我将向您展示如何在 SpringSource Application Platform 中运行 Spring Batch 作业。我曾为 JavaOne 和伦敦 Spring 用户组演示过此功能的早期版本,我认为分享它会很有帮助。示例代码在此

捆绑包

首先,我们将快速浏览示例代码中的捆绑包。现在启动服务器,或者在安装一些捆绑包后的任何时间启动。

捆绑包:hsql-server

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

捆绑包配置位于META-INF/spring/module-context.xml(这对于 Platform 捆绑包来说是惯例) - Spring DM 从META-INF/spring中获取所有 XML 文件。此捆绑包仅使用 Spring 配置并启动 HSQL Server 实例。

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

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

捆绑包:data-source

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

捆绑包:data-source-initializer

这是在测试环境中使用的另一个方便的捆绑包。它的作用是拆除数据库并重新安装其余捆绑包所需的表(批处理元数据以及作业本身的一些业务表)。当您将此捆绑包安装到服务器中时,它将添加表,这些表随后应显示在 HSQL Swing GUI 中。一旦安装一次,您可以将其删除(右键单击“服务器”视图中的捆绑包,然后选择“删除”)。

捆绑包:job-launcher

这是第一个包含一些 Spring Batch 依赖项的捆绑包。它主要是一个配置捆绑包,为将实际运行作业的其他捆绑包公开服务(JobLauncher、JobRepository、TransactionManager)。安装后,它可以一直存在并提供服务。

捆绑包配置位于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捆绑包公开的 OSGi 服务。要查看如何导入该引用以及如何将本地服务公开到 OSGi 服务注册表,我们可以查看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 中以正常方式直接运行测试。

TheSimpleJobLauncherBean

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

如果您查看此包,您会发现一个便利类,其他捆绑包可以使用它来启动作业(SimpleJobLauncherBean)。TheSimpleJobLauncherBeanApplicationListener,这意味着任何包含其中一个的 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);
}

启动作业的计划是为每个作业简单地创建一个捆绑包,并让它定义其中一个SimpleJobLauncherBean实例。

捆绑包:hello-world

这是一个非常简单的作业捆绑包。它具有更大作业的所有功能(来自文件的文件输入和到数据库的文件输出),但使用非常简单的域模型和非常小的数据集。

将捆绑包放入正在运行的 Server 实例中。它启动速度很快,并且由于作业的范围很小,您将立即在批处理元数据中看到效果。在 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”)已在上面的数据库查询中看到,表明该步骤已执行(一次)。所有步骤所做的就是从平面文件读取数据,将行转换为域对象,并将它们写入标准输出。您可以通过检查 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]

如果您愿意,您可以再次运行作业,只需编辑捆绑包中的一个文件(例如清单或一个 Spring 配置文件)并保存即可。工具将获取更改并重新部署捆绑包。此作业的设置方式是在每次执行时都使用一组新的参数(使用时间戳)开始,因此它应该始终成功运行。

作业结束

作业完成后,无论成功与否,我们都希望将系统置于一个指示发生情况的状态。有许多可能的方法可以做到这一点,但我们真正需要的只是一个可工具化的状态,以便可以通知操作员并采取必要的任何措施(例如重新启动失败的作业或重新参数化下一个运行的成功作业)。操作员可以是人类或系统功能。

为了指示作业结束,SimpleJobLauncherBean只需获取封闭的 OSGiBundle实例并停止它。这是一个非常简单的模型,但它具有 API 定义明确且 OSGi 平台普遍支持的优点。原则上,只要容器(SpringSource Application Platform)可以捕获这些捆绑包事件,就可以非常灵活地扩展它。这些是我们可能在 Platform 2.0 版本的 Batch Personality 中看到的特性。如果您对行为应该是什么以及操作员需要哪些功能有任何想法,请通过评论本文来帮助我们。

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

osgi>

键入“ss”并按回车键,您将看到已安装捆绑包的列表

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 的捆绑包是作业启动器,并且处于活动状态。ID 为 99 的捆绑包是 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>

作业捆绑包已恢复到已解析状态,但它已再次执行作业,您可以从 HSQL GUI 或跟踪日志中验证这一点。

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

作业的输入文件

Hello world 具有打包在捆绑包中的固定输入文件。有时这并不合适,在实践中,找到位于文件系统上的输入文件是很常见的。另一方面,使用基于热部署捆绑包的部署模型,也许将输入文件与作业执行一起打包不是一个坏主意 - 捆绑包的占用空间可以非常小,并且它包含一个完美的审核记录,准确记录了执行的内容。评论会很有趣。

捆绑包:football-job

示例代码中还有另一个作业捆绑包,它是一个更真实的业务应用程序(它是来自 Spring Batch 的足球作业示例)。您可以像 hello-world 作业一样启动和重新启动它。

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

设置工作区

SpringSource 应用平台

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

先决条件

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

安装 SpringSource Eclipse 工具后,您需要创建一个服务器实例。转到文件->新建->其他...并找到服务器->服务器。选择 SpringSource 和下面的服务器类型,并使用浏览对话框找到平台安装位置。

下载依赖项

我们都期待着 SpringSource Eclipse 工具能够自动下载和安装依赖项。如果在您阅读本文时该功能不可用,那么您可以按照我的方式进行操作,如果您愿意的话。以下是我所做的
  • (可选)从一个空的本地 Maven 存储库开始(删除 ~/.m2/repository,或在 settings.xml 中指向一个新位置)
  • 在第一次安装捆绑包之前,打开项目的 pom.xml 并找到元素,其 ID 为“shell”。
  • 将 activeByDefault 标志更改为 true,并在 Q4E 下载依赖项时等待。
  • 使用 Q4E 可视化工具检查依赖项(右键单击项目并选择 Maven2->分析依赖项(或可视化依赖项)。您只需要执行此操作即可查看传递依赖项是什么。(您也可以使用$ mvn dependency:tree在命令行上。)
  • 此时,我总是右键单击项目并选择 Maven2->获取源 JAR。这是可选的,但可以简化开发并启用调试。
  • 将直接依赖项复制到平台bundles/usr目录。严格来说,您只需要复制那些不在bundles/ext中的依赖项。在命令行(使用合理的 OS)上,您可以执行
    $ find ~/.m2/repository -name \*.jar -exec cp {} bundles/usr \;
    
  • 您可能需要对捆绑包项目中的部分或全部MANIFEST.MF文件进行“伪编辑”以强制工具刷新。
  • 将 activeByDefault 标志切换回 false。

无需重新启动 Eclipse 或任何其他操作。“捆绑包依赖项”类路径容器应该包含您刚刚下载的运行时依赖项。当“问题”视图(愤怒的红色边距标记)中的所有 Eclipse 错误消失后,我们就可以开始了。

我很乐意听到那些有更好方法的人的意见。其他人已经发展出其他方法,但对我来说,没有一个看起来很方便。实际上,一个命令行 Maven 目标很容易编写,但我还没有看到那个。

Beta5 更新

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

从原则上讲,您根本不需要 Maven 本地存储库来存储运行时依赖项。您可以打开平台运行时(在“服务器”视图中右键单击并打开),然后浏览并直接下载依赖项到bundles/usr。目前唯一的弱点(工具团队正在努力改进这一点)是它没有提供任何传递依赖项的视图 - 您必须明确知道需要哪些捆绑包。对于此博客的示例,这很容易,因为所有清单都已完全指定了依赖项。当您不确定它们是什么并且必须从头创建清单时,情况会更复杂。为此,我现在仍在使用 Q4E。

结束语

总有多种方法可以达到目的,平台是一个非常丰富的环境,因此您可以确定我在这里展示的并不是在平台上运行作业的唯一方法。希望这是一个良好的起点。

应用平台 1.0 版本的许多重点都放在 Web 层上,虽然这显然是必不可少的(而且交付起来非常棘手),但还有其他事情需要处理。2.0 版本将具有特定于批处理的功能(批处理个性),因此我们现在所做的任何事情都有助于完善该版本的特性需求。因此,如果您有机会尝试一下并提出一些建设性的意见,特别是关于操作方面的意见,它们在我们开始构建批处理个性时会派上用场。

获取 Spring 新闻通讯

保持与 Spring 新闻通讯的联系

订阅

领先一步

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

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

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

查看全部