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

在上述场景中,'trade' 标签被定义为“根元素”。'<trade>' 和 '</trade>' 之间的所有内容被视为一个“片段”。Spring Batch 使用对象/XML 映射 (OXM) 将片段绑定到对象。但是,Spring Batch 不依赖于任何特定的 XML 绑定技术。典型的用法是委托给 Spring OXM,它为最流行的 OXM 技术提供了统一的抽象。对 Spring OXM 的依赖是可选的,如果需要,您可以选择实现 Spring Batch 特定的接口。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/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.item.xml.StaxEventItemReader">
<property name="fragmentRootElementName" value="trade" />
<property name="resource" value="org/springframework/batch/item/xml/domain/trades.xml" />
<property name="unmarshaller" ref="tradeMarshaller" />
</bean>
请注意,在此示例中,我们选择了使用 XStreamMarshaller
,它接受一个作为 Map 传入的别名,Map 的第一个键值对是片段的名称(即根元素)和要绑定的对象类型。然后,类似于 FieldSet
,Map 中以键值对形式描述了映射到对象类型中字段的其他元素的名称。在配置文件中,我们可以使用 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 对象。
总而言之,此过程类似于以下 Java 代码,它使用了 Spring 配置提供的注入
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
、一个 marshaller 和一个 rootTagName
。Java 对象被传递给 marshaller(通常是一个标准的 Spring OXM Marshaller),marshaller 使用一个自定义事件写入器将内容写入 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.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 相同的 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 相同的 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);