XML 项目读取器与写入器
Spring Batch 为读取 XML 记录并将其映射到 Java 对象,以及将 Java 对象写入为 XML 记录提供了事务性基础设施。
流式 XML 的约束
StAX API 用于 I/O,因为其他标准 XML 解析 API 不符合批处理要求(DOM 一次性将整个输入加载到内存中,而 SAX 通过仅允许用户提供回调来控制解析过程)。
我们需要考虑Spring Batch中XML输入和输出的工作原理。首先,有几个概念与文件读写不同,但在Spring Batch XML处理中是通用的。在XML处理中,我们不再处理需要标记化的记录行(FieldSet实例),而是假设XML资源是由对应单个记录的"片段"组成的集合,如下图所示:

图 1. XML 输入
在上述场景中,'trade' 标签被定义为'根元素'。位于 '<trade>' 和 '</trade>' 之间的所有内容被视为一个'片段'。Spring Batch 使用对象/XML 映射 (OXM) 将片段绑定到对象。然而,Spring Batch 并不绑定于任何特定的 XML 绑定技术。通常的做法是委托给 Spring OXM,它为最流行的 OXM 技术提供了统一的抽象。对 Spring OXM 的依赖是可选的,如果需要,您也可以选择实现 Spring Batch 特定的接口。OXM 支持的技术之间的关系如下图所示:

图 2. OXM 绑定
在介绍了 OXM 以及如何使用 XML 片段来表示记录之后,我们现在可以更仔细地研究读取器和写入器。
StaxEventItemReader
StaxEventItemReader 的配置为处理来自 XML 输入流的记录提供了典型设置。首先,考虑以下一组 StaxEventItemReader 可以处理的 XML 记录:
<?xml version="1.0" encoding="UTF-8"?>
<records>
<trade xmlns="https://springframework.org/batch/sample/io/oxm/domain">
<isin>XYZ0001</isin>
<quantity>5</quantity>
<price>11.39</price>
<customer>Customer1</customer>
</trade>
<trade xmlns="https://springframework.org/batch/sample/io/oxm/domain">
<isin>XYZ0002</isin>
<quantity>2</quantity>
<price>72.99</price>
<customer>Customer2c</customer>
</trade>
<trade xmlns="https://springframework.org/batch/sample/io/oxm/domain">
<isin>XYZ0003</isin>
<quantity>9</quantity>
<price>99.99</price>
<customer>Customer3</customer>
</trade>
</records>
为了处理 XML 记录,需要满足以下条件:
-
根元素名称:构成待映射对象的片段的根元素名称。示例配置中展示了此值为 trade。
-
资源:表示待读取文件的 Spring Resource。
-
Unmarshaller:由 Spring OXM 提供的解组工具,用于将 XML 片段映射到对象。
- Java
- XML
以下示例展示了如何在 Java 中定义一个 StaxEventItemReader,该读取器使用名为 trade 的根元素、资源路径为 data/iosample/input/input.xml,以及一个名为 tradeMarshaller 的反编组器:
@Bean
public StaxEventItemReader itemReader() {
return new StaxEventItemReaderBuilder<Trade>()
.name("itemReader")
.resource(new FileSystemResource("org/springframework/batch/infrastructure/item/xml/domain/trades.xml"))
.addFragmentRootElements("trade")
.unmarshaller(tradeMarshaller())
.build();
}
以下示例展示了如何在 XML 中定义一个 StaxEventItemReader,该读取器使用名为 trade 的根元素、资源路径为 data/iosample/input/input.xml,以及一个名为 tradeMarshaller 的反编组器:
<bean id="itemReader" class="org.springframework.batch.infrastructure.item.xml.StaxEventItemReader">
<property name="fragmentRootElementName" value="trade" />
<property name="resource" value="org/springframework/batch/infrastructure/item/xml/domain/trades.xml" />
<property name="unmarshaller" ref="tradeMarshaller" />
</bean>
请注意,在此示例中,我们选择使用 XStreamMarshaller,它接受一个以映射形式传入的别名,其中第一个键值对是片段(即根元素)的名称和要绑定的对象类型。然后,类似于 FieldSet,映射到对象类型内字段的其他元素的名称在映射中描述为键/值对。在配置文件中,我们可以使用 Spring 配置工具来描述所需的别名。
- Java
- XML
以下示例展示了如何在 Java 中描述别名:
@Bean
public XStreamMarshaller tradeMarshaller() {
Map<String, Class> aliases = new HashMap<>();
aliases.put("trade", Trade.class);
aliases.put("price", BigDecimal.class);
aliases.put("isin", String.class);
aliases.put("customer", String.class);
aliases.put("quantity", Long.class);
XStreamMarshaller marshaller = new XStreamMarshaller();
marshaller.setAliases(aliases);
return marshaller;
}
以下示例展示了如何在 XML 中描述别名:
<bean id="tradeMarshaller"
class="org.springframework.oxm.xstream.XStreamMarshaller">
<property name="aliases">
<util:map id="aliases">
<entry key="trade"
value="org.springframework.batch.samples.domain.trade.Trade" />
<entry key="price" value="java.math.BigDecimal" />
<entry key="isin" value="java.lang.String" />
<entry key="customer" value="java.lang.String" />
<entry key="quantity" value="java.lang.Long" />
</util:map>
</property>
</bean>
在输入时,读取器会持续读取XML资源,直到识别出即将开始的新片段。默认情况下,读取器通过匹配元素名称来识别新片段的开始。读取器会从该片段创建一个独立的XML文档,并将该文档传递给反序列化器(通常是Spring OXM Unmarshaller的包装器),以将XML映射为Java对象。
总而言之,该过程类似于以下使用Spring配置提供的注入的Java代码:
StaxEventItemReader<Trade> xmlStaxEventItemReader = new StaxEventItemReader<>();
Resource resource = new ByteArrayResource(xmlResource.getBytes());
Map aliases = new HashMap();
aliases.put("trade","org.springframework.batch.samples.domain.trade.Trade");
aliases.put("price","java.math.BigDecimal");
aliases.put("customer","java.lang.String");
aliases.put("isin","java.lang.String");
aliases.put("quantity","java.lang.Long");
XStreamMarshaller unmarshaller = new XStreamMarshaller();
unmarshaller.setAliases(aliases);
xmlStaxEventItemReader.setUnmarshaller(unmarshaller);
xmlStaxEventItemReader.setResource(resource);
xmlStaxEventItemReader.setFragmentRootElementName("trade");
xmlStaxEventItemReader.open(new ExecutionContext());
boolean hasNext = true;
Trade trade = null;
while (hasNext) {
trade = xmlStaxEventItemReader.read();
if (trade == null) {
hasNext = false;
}
else {
System.out.println(trade);
}
}
StaxEventItemWriter
输出与输入对称工作。StaxEventItemWriter 需要一个 Resource、一个编组器和一个 rootTagName。Java 对象被传递给编组器(通常是标准的 Spring OXM 编组器),该编组器通过使用自定义事件写入器写入 Resource,该写入器会过滤 OXM 工具为每个片段生成的 StartDocument 和 EndDocument 事件。
- Java
- XML
以下 Java 示例使用了 MarshallingEventWriterSerializer:
@Bean
public StaxEventItemWriter itemWriter(Resource outputResource) {
return new StaxEventItemWriterBuilder<Trade>()
.name("tradesWriter")
.marshaller(tradeMarshaller())
.resource(outputResource)
.rootTagName("trade")
.overwriteOutput(true)
.build();
}
以下 XML 示例使用了 MarshallingEventWriterSerializer:
<bean id="itemWriter" class="org.springframework.batch.infrastructure.item.xml.StaxEventItemWriter">
<property name="resource" ref="outputResource" />
<property name="marshaller" ref="tradeMarshaller" />
<property name="rootTagName" value="trade" />
<property name="overwriteOutput" value="true" />
</bean>
前面的配置设置了三个必需的属性,并设置了可选的 overwriteOutput=true 属性,本章前面提到过该属性用于指定是否可以覆盖现有文件。
- Java
- XML
以下 Java 示例使用了与本章前面展示的读取示例中相同的 marshaller:
@Bean
public XStreamMarshaller customerCreditMarshaller() {
XStreamMarshaller marshaller = new XStreamMarshaller();
Map<String, Class> aliases = new HashMap<>();
aliases.put("trade", Trade.class);
aliases.put("price", BigDecimal.class);
aliases.put("isin", String.class);
aliases.put("customer", String.class);
aliases.put("quantity", Long.class);
marshaller.setAliases(aliases);
return marshaller;
}
以下 XML 示例使用了与本章前面展示的读取示例中相同的 marshaller:
<bean id="customerCreditMarshaller"
class="org.springframework.oxm.xstream.XStreamMarshaller">
<property name="aliases">
<util:map id="aliases">
<entry key="customer"
value="org.springframework.batch.samples.domain.trade.Trade" />
<entry key="price" value="java.math.BigDecimal" />
<entry key="isin" value="java.lang.String" />
<entry key="customer" value="java.lang.String" />
<entry key="quantity" value="java.lang.Long" />
</util:map>
</property>
</bean>
总结一下,用一个Java示例来说明,以下代码展示了讨论的所有要点,演示了如何以编程方式设置所需的属性:
FileSystemResource resource = new FileSystemResource("data/outputFile.xml")
Map aliases = new HashMap();
aliases.put("trade","org.springframework.batch.samples.domain.trade.Trade");
aliases.put("price","java.math.BigDecimal");
aliases.put("customer","java.lang.String");
aliases.put("isin","java.lang.String");
aliases.put("quantity","java.lang.Long");
Marshaller marshaller = new XStreamMarshaller();
marshaller.setAliases(aliases);
StaxEventItemWriter staxItemWriter =
new StaxEventItemWriterBuilder<Trade>()
.name("tradesWriter")
.marshaller(marshaller)
.resource(resource)
.rootTagName("trade")
.overwriteOutput(true)
.build();
staxItemWriter.afterPropertiesSet();
ExecutionContext executionContext = new ExecutionContext();
staxItemWriter.open(executionContext);
Trade trade = new Trade();
trade.setPrice(11.39);
trade.setIsin("XYZ0001");
trade.setQuantity(5L);
trade.setCustomer("Customer1");
staxItemWriter.write(trade);