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 資源是對應於單個記錄的“片段”的集合,如下圖所示

XML Input
圖 1. XML 輸入

在上述場景中,'trade' 標籤被定義為“根元素”。'<trade>' 和 '</trade>' 之間的所有內容被視為一個“片段”。Spring Batch 使用物件/XML 對映 (OXM) 將片段繫結到物件。但是,Spring Batch 不依賴於任何特定的 XML 繫結技術。典型的用法是委託給 Spring OXM,它為最流行的 OXM 技術提供了統一的抽象。對 Spring OXM 的依賴是可選的,如果需要,您可以選擇實現 Spring Batch 特定的介面。OXM 支援的技術之間的關係如下圖所示

OXM Binding
圖 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 的解組器

Java 配置
@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 的解組器

XML 配置
<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 中描述別名

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 中描述別名

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 工具為每個片段生成的 StartDocumentEndDocument 事件。

  • Java

  • XML

以下 Java 示例使用 MarshallingEventWriterSerializer

Java 配置
@Bean
public StaxEventItemWriter itemWriter(Resource outputResource) {
	return new StaxEventItemWriterBuilder<Trade>()
			.name("tradesWriter")
			.marshaller(tradeMarshaller())
			.resource(outputResource)
			.rootTagName("trade")
			.overwriteOutput(true)
			.build();

}

以下 XML 示例使用 MarshallingEventWriterSerializer

XML 配置
<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

Java 配置
@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

XML 配置
<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);