Marshalling XML by Using Object-XML Mappers

Introduction

本章介绍 Spring 的对象 XML 映射支持。对象 XML 映射(简称 O-X 映射)是指 XML 文档对象之间的转换过程。这个转换过程也称为 XML 序列化或 XML 序列转换。本章将这些术语替换使用。

对于 O-X 映射领域,一个序列化器负责将一个对象(图形)序列化为 XML。类似地,一个解串器将 XML 解串为一个对象图形。这个 XML 可以作为 DOM 文档、输入或输出流,或一个 SAX 处理器。

在 O/X 映射中使用 Spring 的一些好处是:

Ease of configuration

Spring 的 bean 工厂很容易配置序列化器,而无需构建 JAXB 上下文、JiBX 绑定工厂等。你可以像在应用程序上下文中配置任何其他 bean 那样配置序列化器。此外,基于 XML 命名空间的配置可用于多个序列化器,使配置过程变得更简单。

Consistent Interfaces

Spring 的 O-X 映射通过两个全局接口进行操作:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/oxm/Marshaller.html[Marshaller] 和 Unmarshaller。这些抽象使你能够相对容易地切换 O-X 映射框架,几乎不需要对执行编组的类做任何更改。这种方法还有一个好处,就是可以混合匹配来进行 XML 编组(例如,使用 JAXB 执行部分编组,使用 XStream 执行其它部分编组),且不会产生干扰,让你可以使用每种技术的优势。

Consistent Exception Hierarchy

Spring 提供将基础 O-X 映射工具中异常内容转换为自身的异常层级,其中 XmlMappingException 为根异常。这些运行时异常会包装原始异常,确保不会丢失任何信息。

Marshaller and Unmarshaller

introduction 所述,转换器将对象序列化为 XML,而解组器将 XML 流反序列化为对象。本节介绍用于此目的的两个 Spring 接口。

Understanding 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。结果是一个标记接口,基本表示一个 XML 输出抽象。具体实现对不同的 XML 表示进行包装,如下表所示:

Result implementation Wraps XML representation

DOMResult

org.w3c.dom.Node

SAXResult

org.xml.sax.ContentHandler

StreamResult

java.io.File, java.io.OutputStream, or java.io.Writer

尽管 marshal() 方法将纯对象作为其第一个参数,但大多数 Marshaller 实现都无法处理任意对象。相反,对象类必须在映射文件中映射、用注释标记、向编组器注册或具有通用基类。请参阅本章后面的章节,以确定您的 O-X 技术如何管理此项工作。

Understanding 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 implementation Wraps XML representation

DOMSource

org.w3c.dom.Node

SAXSource

org.xml.sax.InputSource, and org.xml.sax.XMLReader

StreamSource

java.io.File, java.io.InputStream, or java.io.Reader

尽管有两个单独的编排接口(MarshallerUnmarshaller),Spring-WS 中的所有实现都在一个类中实现了这两个接口。这意味着你可以接线到一个序列化器类,并在 applicationContext.xml 中将它同时用作序列化器和解串器。

Understanding XmlMappingException

Spring 将基础 O-X 映射工具中异常内容转换为自身的异常层级,其中 XmlMappingException 为根异常。这些运行时异常会包装原始异常,确保不会丢失任何信息。

此外,MarshallingFailureExceptionUnmarshallingFailureException 对编排和解串操作进行了区别,尽管基础 O-X 映射工具并没有这样做。

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

oxm exceptions

Using Marshaller and Unmarshaller

您可以在多种情况下使用 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() 将 settings 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,但是我们也可以使用本章后面介绍的任何其他序列化器实例。请注意,默认情况下,XStream 不需要进一步配置,所以 bean 定义非常简单。另请注意,XStreamMarshaller 实现了 MarshallerUnmarshaller,因此我们可以在 marshallerunmarshaller 的应用程序属性中引用 xstreamMarshaller bean。

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

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

XML Configuration Namespace

您可以使用 OXM 命名空间中的标签更简洁地配置编组器。为了让这些标签可用,您必须首先在 XML 配置文件的序言中引用适当的架构。以下示例展示了如何执行此操作:

<?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 Reference the oxm schema.
2 指定 oxm 模式位置。

该架构让以下元素可用:

每个标签在各自编组器的部分中进行了解释。然而,作为示例,JAXB2 编组器的配置可能类似于以下内容:

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

JAXB

JAXB 绑定编译器将 W3C XML 架构转换为一个或多个 Java 类、一个 jaxb.properties 文件以及一些资源文件。JAXB 还提供一种从带注释的 Java 类中生成架构的方式。

Spring 支持 JAXB 2.0 API 作为 XML 编组策略,遵循 MarshallerUnmarshaller 中描述的接口,分别为 xref:data-access/oxm.adoc#oxm-marshaller-unmarshaller[MarshallerUnmarshaller。相应的集成类位于 org.springframework.oxm.jaxb 包中。

Using Jaxb2Marshaller

Jaxb2Marshaller 类实现了 Spring 的 MarshallerUnmarshaller 接口。它需要一个上下文路径才能运行。您可以通过设置 contextPath 属性来设置上下文路径。上下文路径由包含架构派生类的、以冒号分隔的 Java 包名列表组成。它还提供了一个 classesToBeBound 属性,供您设置一个由编组器支持的类数组。通过向 Bean 指定一个或多个架构资源执行架构验证,如下例所示:

<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 Configuration Namespace

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

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

或者,您可以使用 class-to-be-bound 子元素提供要绑定到编组器的类列表:

<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>

下表描述了可用的属性:

Attribute Description Required

id

编组器 ID

No

contextPath

The JAXB Context path

No

JiBX

JiBX 框架提供了一个类似于 Hibernate 为 ORM 提供的解决方案:绑定定义定义了如何将您的 Java 对象转换为 XML 或从 XML 转换 Java 对象的规则。准备绑定并编译类之后,一个 JiBX 绑定编译器会增强类文件并将代码添加到从类实例或转换为 XML 的操作处理中。

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

Using JibxMarshaller

JibxMarshaller 类实现了 MarshallerUnmarshaller 接口。要运行,它需要要编组成的类的名称,您可以使用 targetClass 属性进行设置。或者,您可以通过设置 bindingName 属性设置绑定名称。在以下示例中,我们绑定了 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 Configuration Namespace

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

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

下表描述了可用的属性:

Attribute Description Required

id

编组器 ID

No

target-class

此编组器的目标类

Yes

bindingName

此编组器使用的绑定名称

No

XStream

XStream 是一用于将对象序列化成 XML 并再次返回对象的简单库。它不需要任何映射并会生成简洁的 XML。

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

Using XStreamMarshaller

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

<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 从外部来源取消编组 XML(即 Web),因为这可能导致安全漏洞。 如果你选择使用 XStreamMarshaller 从外部源处解组 XML,请设置 XStreamMarshaller 上的 supportedClasses 属性,如下例所示:

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

这样做可确保仅已注册的类有资格进行解组。 此外,你可以注册 自定义转换器,以确保存证仅能反编组你支持的类。你可能希望将 CatchAllConverter 添加为列表中的最后一个转换器,除了明确支持应该支持的领域类的转换器之外。结果,具有较低优先级的默认 XStream 转换器和可能的安全漏洞不会被调用。

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