使用对象-XML映射器编组XML

简介

本章描述 Spring 的对象-XML 映射支持。对象-XML 映射(简称 O-X 映射)是将 XML 文档与对象进行相互转换的操作。此转换过程也称为 XML 编组(XML Marshalling)或 XML 序列化(XML Serialization)。本章交替使用这些术语。

在 O-X 映射领域中,marshaller(编组器)负责将对象(图)序列化为 XML。类似地,unmarshaller(解组器)将 XML 反序列化为对象图。此 XML 可以采用 DOM 文档、输入或输出流或 SAX handler 的形式。

使用 Spring 处理 O/X 映射需求的一些优势包括:

配置简单

Spring 的 bean factory 使配置 marshaller 变得简单,无需构建 JAXB context、JiBX binding factory 等。您可以像配置应用上下文中的任何其他 bean 一样配置 marshaller。此外,许多 marshaller 支持基于 XML 命名空间的配置,使配置更加简单。

接口一致

Spring 的 O-X 映射通过两个全局接口进行操作:MarshallerUnmarshaller。这些抽象使您能够相对轻松地切换 O-X 映射框架,而无需对执行编组的类进行很少或无需更改。这种方法还有一个额外的好处,即可以通过一种非侵入式的方式进行 XML 编组,采用混合搭配的方法(例如,一些编组使用 JAXB 执行,一些使用 XStream 执行),让您可以使用每种技术的优势。

异常层级一致

Spring 将底层 O-X 映射工具的异常转换为其自己的异常层级,其中 XmlMappingException 是根异常。这些运行时异常会包装原始异常,以便不会丢失任何信息。

MarshallerUnmarshaller

简介中所述,marshaller 将对象序列化为 XML,unmarshaller 将 XML 流反序列化为对象。本节描述用于此目的的两个 Spring 接口。

理解 Marshaller

Spring 将所有编组操作抽象到 org.springframework.oxm.Marshaller 接口之后,其主要方法如下:

public interface Marshaller {

	/**
	 * Marshal the object graph with the given root into the provided Result.
	 */
	void marshal(Object graph, Result result) throws XmlMappingException, IOException;
}

Marshaller 接口有一个主要方法,它将给定的对象编组到给定的 javax.xml.transform.Result。Result 是一个标记接口,它基本上代表了一个 XML 输出抽象。具体实现封装了各种 XML 表示形式,如下表所示:

Result 实现 封装的 XML 表示形式

DOMResult

org.w3c.dom.Node

SAXResult

org.xml.sax.ContentHandler

StreamResult

java.io.Filejava.io.OutputStreamjava.io.Writer

尽管 marshal() 方法接受一个普通对象作为其第一个参数,但大多数 Marshaller 实现无法处理任意对象。相反,对象类必须在映射文件中进行映射,用注解标记,向 marshaller 注册,或者具有公共基类。请参阅本章后面的部分,了解您的 O-X 技术如何管理此事。

理解 Unmarshaller

类似于 Marshaller,我们有 org.springframework.oxm.Unmarshaller 接口,其列表如下所示:

public interface Unmarshaller {

	/**
	 * Unmarshal the given provided Source into an object graph.
	 */
	Object unmarshal(Source source) throws XmlMappingException, IOException;
}

此接口也有一个方法,该方法从给定的 javax.xml.transform.Source(一个 XML 输入抽象)读取并返回读取的对象。与 Result 一样,Source 是一个标记接口,具有三个具体实现。每个实现都封装了不同的 XML 表示形式,如下表所示:

Source 实现 封装的 XML 表示形式

DOMSource

org.w3c.dom.Node

SAXSource

org.xml.sax.InputSourceorg.xml.sax.XMLReader

StreamSource

java.io.Filejava.io.InputStreamjava.io.Reader

即使有两个单独的编组接口(MarshallerUnmarshaller),Spring-WS 中的所有实现都在一个类中实现两者。这意味着您可以在 applicationContext.xml 中配置一个 marshaller 类,并将其同时用作 marshaller 和 unmarshaller。

理解 XmlMappingException

Spring 将底层 O-X 映射工具的异常转换为其自己的异常层级,其中 XmlMappingException 是根异常。这些运行时异常会包装原始异常,以便不会丢失任何信息。

此外,MarshallingFailureExceptionUnmarshallingFailureException 区分了编组和解组操作,尽管底层 O-X 映射工具并未这样做。

O-X 映射异常层级如下图所示:

oxm exceptions

使用 MarshallerUnmarshaller

您可以在各种情况下使用 Spring 的 OXM。在以下示例中,我们使用它将 Spring 管理的应用设置编组为 XML 文件。在以下示例中,我们使用一个简单的 JavaBean 表示设置:

  • Java

  • Kotlin

public class Settings {

	private boolean fooEnabled;

	public boolean isFooEnabled() {
		return fooEnabled;
	}

	public void setFooEnabled(boolean fooEnabled) {
		this.fooEnabled = fooEnabled;
	}
}
class Settings {
	var isFooEnabled: Boolean = false
}

应用类使用此 bean 存储其设置。除了一个 main 方法之外,该类还有两个方法:saveSettings() 将设置 bean 保存到名为 settings.xml 的文件,loadSettings() 再次加载这些设置。以下 main() 方法构建 Spring 应用上下文并调用这两个方法:

  • Java

  • Kotlin

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.Unmarshaller;

public class Application {

	private static final String FILE_NAME = "settings.xml";
	private Settings settings = new Settings();
	private Marshaller marshaller;
	private Unmarshaller unmarshaller;

	public void setMarshaller(Marshaller marshaller) {
		this.marshaller = marshaller;
	}

	public void setUnmarshaller(Unmarshaller unmarshaller) {
		this.unmarshaller = unmarshaller;
	}

	public void saveSettings() throws IOException {
		try (FileOutputStream os = new FileOutputStream(FILE_NAME)) {
			this.marshaller.marshal(settings, new StreamResult(os));
		}
	}

	public void loadSettings() throws IOException {
		try (FileInputStream is = new FileInputStream(FILE_NAME)) {
			this.settings = (Settings) this.unmarshaller.unmarshal(new StreamSource(is));
		}
	}

	public static void main(String[] args) throws IOException {
		ApplicationContext appContext =
				new ClassPathXmlApplicationContext("applicationContext.xml");
		Application application = (Application) appContext.getBean("application");
		application.saveSettings();
		application.loadSettings();
	}
}
class Application {

	lateinit var marshaller: Marshaller

	lateinit var unmarshaller: Unmarshaller

	fun saveSettings() {
		FileOutputStream(FILE_NAME).use { outputStream -> marshaller.marshal(settings, StreamResult(outputStream)) }
	}

	fun loadSettings() {
		FileInputStream(FILE_NAME).use { inputStream -> settings = unmarshaller.unmarshal(StreamSource(inputStream)) as Settings }
	}
}

private const val FILE_NAME = "settings.xml"

fun main(args: Array<String>) {
	val appContext = ClassPathXmlApplicationContext("applicationContext.xml")
	val application = appContext.getBean("application") as Application
	application.saveSettings()
	application.loadSettings()
}

Application 需要同时设置 marshallerunmarshaller 属性。我们可以使用以下 applicationContext.xml 来完成:

<beans>
	<bean id="application" class="Application">
		<property name="marshaller" ref="xstreamMarshaller" />
		<property name="unmarshaller" ref="xstreamMarshaller" />
	</bean>
	<bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"/>
</beans>

此应用上下文使用 XStream,但我们可以使用本章后面描述的任何其他 marshaller 实例。请注意,默认情况下,XStream 不需要任何进一步配置,因此 bean 定义相当简单。还要注意,XStreamMarshaller 同时实现了 MarshallerUnmarshaller 接口,因此我们可以在应用的 marshallerunmarshaller 属性中引用同一个 xstreamMarshaller bean。

此示例应用生成以下 settings.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<settings foo-enabled="false"/>

XML 配置命名空间

您可以使用 OXM 命名空间中的标签更简洁地配置 marshaller。要使这些标签可用,您必须首先在 XML 配置文件的前言中引用相应的 schema。以下示例展示了如何操作:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:oxm="http://www.springframework.org/schema/oxm" (1)
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/oxm
		https://www.springframework.org/schema/oxm/spring-oxm.xsd"> (2)
1 引用 oxm schema。
2 指定 oxm schema 位置。

此 schema 提供以下元素:

每个标签在其各自 marshaller 的部分中进行解释。例如,JAXB2 marshaller 的配置可能类似于以下内容:

<oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/>

JAXB

JAXB binding compiler 将 W3C XML Schema 转换为一个或多个 Java 类、一个 jaxb.properties 文件,以及可能的一些资源文件。JAXB 还提供了一种从带注解的 Java 类生成 schema 的方法。

Spring 支持 JAXB 2.0 API 作为 XML 编组策略,遵循MarshallerUnmarshaller 中描述的 MarshallerUnmarshaller 接口。相应的集成类位于 org.springframework.oxm.jaxb 包中。

使用 Jaxb2Marshaller

Jaxb2Marshaller 类实现了 Spring 的 MarshallerUnmarshaller 两个接口。它需要一个 context path 才能操作。您可以通过设置 contextPath 属性来设置 context path。context path 是一个包含 schema 派生类的以冒号分隔的 Java 包名列表。它还提供了一个 classesToBeBound 属性,允许您设置 marshaller 支持的类数组。通过向 bean 指定一个或多个 schema 资源来执行 schema 验证,如下例所示:

<beans>
	<bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
		<property name="classesToBeBound">
			<list>
				<value>org.springframework.oxm.jaxb.Flight</value>
				<value>org.springframework.oxm.jaxb.Flights</value>
			</list>
		</property>
		<property name="schema" value="classpath:org/springframework/oxm/schema.xsd"/>
	</bean>

	...

</beans>

XML 配置命名空间

jaxb2-marshaller 元素配置一个 org.springframework.oxm.jaxb.Jaxb2Marshaller,如下例所示:

<oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/>

此外,您可以使用 class-to-be-bound 子元素提供要绑定到 marshaller 的类列表:

<oxm:jaxb2-marshaller id="marshaller">
	<oxm:class-to-be-bound name="org.springframework.ws.samples.airline.schema.Airport"/>
	<oxm:class-to-be-bound name="org.springframework.ws.samples.airline.schema.Flight"/>
	...
</oxm:jaxb2-marshaller>

下表描述了可用的属性:

属性 描述 是否必须

id

marshaller 的 ID

contextPath

JAXB Context path

JiBX

JiBX 框架提供了一个类似于 Hibernate 为 ORM 提供解决方案:一个 binding definition 定义了 Java 对象如何转换为 XML 或从 XML 转换的规则。准备好 binding 并编译类后,JiBX binding compiler 会增强类文件并添加处理类实例与 XML 相互转换的代码。

有关 JiBX 的更多信息,请参阅JiBX 网站。Spring 集成类位于 org.springframework.oxm.jibx 包中。

使用 JibxMarshaller

JibxMarshaller 类同时实现了 MarshallerUnmarshaller 接口。为了操作,它需要编组的类的名称,您可以使用 targetClass 属性进行设置。您可以选择通过设置 bindingName 属性来设置 binding 名称。在以下示例中,我们绑定了 Flights 类:

<beans>
	<bean id="jibxFlightsMarshaller" class="org.springframework.oxm.jibx.JibxMarshaller">
		<property name="targetClass">org.springframework.oxm.jibx.Flights</property>
	</bean>
	...
</beans>

一个 JibxMarshaller 配置用于单个类。如果您想编组多个类,则必须配置多个具有不同 targetClass 属性值的 JibxMarshaller 实例。

XML 配置命名空间

jibx-marshaller 标签配置一个 org.springframework.oxm.jibx.JibxMarshaller,如下例所示:

<oxm:jibx-marshaller id="marshaller" target-class="org.springframework.ws.samples.airline.schema.Flight"/>

下表描述了可用的属性:

属性 描述 是否必须

id

marshaller 的 ID

target-class

此 marshaller 的目标类

bindingName

此 marshaller 使用的 binding 名称

XStream

XStream 是一个简单的库,用于将对象序列化为 XML 并再反向转换。它不需要任何映射并生成干净的 XML。

有关 XStream 的更多信息,请参阅XStream 网站。Spring 集成类位于 org.springframework.oxm.xstream 包中。

使用 XStreamMarshaller

XStreamMarshaller 不需要任何配置,可以直接在应用上下文中配置。要进一步自定义 XML,您可以设置一个 alias map,它由映射到类的字符串别名组成,如下例所示:

<beans>
	<bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
		<property name="aliases">
			<props>
				<prop key="Flight">org.springframework.oxm.xstream.Flight</prop>
			</props>
		</property>
	</bean>
	...
</beans>

默认情况下,XStream 允许任意类被解组,这可能导致不安全的 Java 序列化效应。因此,我们不建议使用 XStreamMarshaller 解组来自外部源(即 Web)的 XML,因为这可能导致安全漏洞。

如果您选择使用 XStreamMarshaller 解组来自外部源的 XML,请在 XStreamMarshaller 上设置 supportedClasses 属性,如下例所示:

<bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
	<property name="supportedClasses" value="org.springframework.oxm.xstream.Flight"/>
	...
</bean>

这样做可以确保只有已注册的类才有资格进行解组。

此外,您可以注册 custom converters 以确保只有您支持的类可以被解组。您可能希望将 CatchAllConverter 作为列表中的最后一个 converter,此外还包括明确支持应支持的领域类的 converter。这样一来,具有较低优先级和可能存在安全漏洞的默认 XStream converter 将不会被调用。

请注意,XStream 是一个 XML 序列化库,而不是数据绑定库。因此,它的命名空间支持有限。结果,它不太适合在 Web Services 中使用。