抢先一步
VMware 提供培训和认证,助你突飞猛进。
了解更多在本文中,我将展示如何在 SpringSource 应用平台中运行 Spring Batch 作业。我曾在 JavaOne 上演示过这个的早期版本,后来又在伦敦 Spring 用户组演示过,觉得这可能是个值得分享的好东西。示例代码请点击这里。
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 的配置在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 中以常规方式运行测试。
...
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 拖放到正在运行的服务器实例中。它启动得相当快,由于作业范围很小,你会立即在批处理元数据中看到效果。在 HSQL Swing GUI 中,你可以执行一些 SQL,例如:
SELECT * FROM BATCH_STEP_EXECUTION
并看到结果,类似这样:
STEP_EXECUTION_ID | VERSION | STEP_NAME | ... | STATUS | ... |
---|---|---|---|---|---|
0 | 4 | helloWorldStep | ... | 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_ID | VERSION | STEP_NAME | ... | STATUS | ... |
---|---|---|---|---|---|
0 | 4 | helloWorldStep | ... | COMPLETED | ... |
1 | 4 | helloWorldStep | ... | COMPLETED | ... |
2 | 4 | helloWorldStep | ... | COMPLETED | ... |
如果你刚刚尝试了这一点,你可能会发现第二次及后续启动时,数据库中没有任何变化。这是预期的,因为你重新启动了一个已成功完成的作业实例,所以它不会再次处理数据。实际上,JobLauncher抛出了一个异常,被SimpleJobLauncherBean捕获并记录了(因此它显示在跟踪日志中)。
安装 SpringSource Eclipse 工具后,你需要创建一个服务器实例。转到 File->New->Other... 并找到 Server->Server。选择 SpringSource 和其下的服务器类型,并使用浏览对话框找到平台安装位置。
$ find ~/.m2/repository -name \*.jar -exec cp {} bundles/usr \;
无须重启 Eclipse 或其他任何东西。“Bundle Dependencies”类路径容器应该包含你刚刚下载的运行时依赖项。当 Problems 视图中所有的 Eclipse 错误(愤怒的红色标记)都消失后,我们就可以开始了。
我很高兴听到有更好方法的人分享经验。其他人已经发展出了其他方法,但在我看来都不太方便。实际上,一个命令行 Maven 目标会很容易编写,但我还没有看到这样的。
原则上,你根本不需要 Maven 本地仓库来获取运行时依赖项。你可以打开平台运行时(在 Servers 视图中右键单击并选择 Open),然后直接浏览并下载依赖项到bundles/usr。目前唯一的缺点(工具团队正在努力改进这一点)是它不提供任何传递性依赖项的视图——你必须明确知道需要哪些 bundle。对于本博客的示例,这很容易,因为所有的 MANIFESTs 都已经完整地指定了依赖项。当你不知道它们是什么并且必须从头创建 MANIFEST 时,这就更困难了。为此,我目前仍然使用 Q4E。
应用平台 1.0 版本的大部分重点在于 Web 层,虽然这显然是必不可少(且非常棘手)的功能,但还有其他更重要的事情要做。2.0 版本将具有特定的批处理相关功能(一个 Batch Personality),因此我们现在所做的任何事情都将有助于充实该版本的特性需求。因此,如果你有机会尝试一下并有一些建设性的意见,特别是关于操作方面的,它们将在我们开始构建 Batch Personality 时派上用场。