使用对象-XML映射器进行XML序列化
引言
本章介绍了Spring的对象-XML映射(Object-XML Mapping,简称O-X Mapping)功能。对象-XML映射是指将XML文档转换为对象或将对象转换为XML文档的过程。这一转换过程也被称为XML编组(XML Marshalling)或XML序列化(XML Serialization)。在本章中,我们将这些术语互换使用。
在O-X映射领域,marshaller负责将对象(图)序列化为XML。同样地,unmarshaller则将XML反序列化为对象图。这种XML可以是DOM文档的形式,也可以是输入或输出流的形式,或者是SAX处理程序的形式。
使用Spring进行O/X映射的一些好处包括:
配置便捷性
Spring的bean工厂使得配置序列化器(marshalers)变得非常简单,无需构建JAXB上下文、JiBX绑定工厂等。你可以像配置应用程序上下文中的其他bean一样来配置这些序列化器。此外,对于许多序列化器来说,还支持基于XML命名空间的配置方式,进一步简化了配置过程。
一致接口
Spring的O-X映射通过两个全局接口进行操作:Marshaller和Unmarshaller。这些抽象层使得你可以相对容易地切换O-X映射框架,而几乎不需要对进行序列化的类进行任何修改。这种方法的另一个好处是,它可以以一种非侵入性的方式实现混合使用多种XML序列化技术(例如,部分序列化使用JAXB,部分使用XStream),从而让你能够充分利用每种技术的优势。
一致的异常层次结构
Spring提供了从底层O-X映射工具的异常到其自身异常层次结构的转换功能,其中XmlMappingException作为根异常。这些运行时异常会包装原始异常,以确保不会丢失任何信息。
Marshaller 和 Unmarshaller
如介绍中所述,序列化器(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。结果是一个标记接口(tagging interface),它基本上代表了一种XML输出抽象。具体的实现会封装各种XML表示形式,如下表所示:
| 结果实现 | 包装的XML表示形式 |
|---|---|
DOMResult | org.w3c.dom.Node |
SAXResult | org.xml.sax.ContentHandler |
StreamResult | java.io.File, java.io.OutputStream, 或 java.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表示形式,如下表所示:
| 源实现 | 扼裹的XML表示形式 |
|---|---|
DOMSource | org.w3c.dom.Node |
SAXSource | org.xml.sax.InputSource, 和 org.xml.sax.XMLReader |
StreamSource | java.io.File, java.io.InputStream, 或 java.io.Reader |
尽管有两个独立的序列化接口(Marshaller和Unmarshaller),但在Spring-WS中,所有实现都在一个类中同时实现了这两个接口。这意味着你可以在applicationContext.xml中配置一个序列化类,并将其既作为序列化器(marshaller)也作为反序列化器(unmarshaller)来使用。
理解 XmlMappingException
Spring将底层O-X映射工具抛出的异常转换为它自己的异常层次结构,其中XmlMappingException作为根异常。这些运行时异常会封装原始异常,从而确保不会丢失任何信息。
此外,MarshallingFailureException和UnmarshallingFailureException在marshalling(序列化)和unmarshalling(反序列化)操作之间进行了区分,尽管底层的O-X映射工具并没有这样做。
O-X映射异常层次结构如下图所示:

使用 Marshaller 和 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()将设置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 需要设置 marshaller 和 unmarshaller 两个属性。我们可以通过以下 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同时实现了Marshaller和Unmarshaller接口,因此我们可以在应用程序的marshaller和unmarshaller属性中都引用xstreamMarshaler 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>
参考
oxm模式。指定
oxm模式的位置。
该模式提供了以下元素:
每个标签都在其对应的marshaller部分中有解释。例如,JAXB2 marshaller的配置可能如下所示:
<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序列化策略,遵循在Marshaller and Unmarshaller中描述的Marshaller和Unmarshaller接口。相应的集成类位于org.springframework.oxm.jaxb包中。
使用 Jaxb2Marshaller
Jaxb2Marshaller 类实现了 Spring 的 Marshaller 和 Unmarshaller 接口。它需要一个上下文路径(context path)才能运行。你可以通过设置 contextPath 属性来指定这个上下文路径。上下文路径是一系列用冒号分隔的 Java 包名,这些包名中包含了模式(schema)派生的类。该类还提供了一个 classesToBeBound 属性,允许你设置marshaller支持的一组类。如以下示例所示,通过向 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 配置命名空间
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 | 序列化器的ID | 否 |
contextPath | JAXB上下文路径 | 否 |
JiBX
JiBX框架提供了一种与Hibernate提供的ORM解决方案类似的方案:绑定定义(binding definition)规定了如何将Java对象转换为XML或从XML转换回Java对象的规则。在准备好绑定配置并编译相关类之后,JiBX绑定编译器(binding compiler)会对类文件进行优化,并添加额外的代码来处理这些类的实例从XML到Java或从Java到XML的转换过程。
有关JiBX的更多信息,请访问JiBX网站。Spring集成类位于org.springframework.oxm.jibx包中。
使用 JibxMarshaller
JibxMarshaller 类同时实现了 Marshaller 和 Unmarshaller 接口。为了使其能够正常工作,需要指定要进行序列化的类的名称,该名称可以通过 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 是为单个类配置的。如果你想对多个类进行序列化(marshaling),则必须配置多个具有不同 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 | 打包器的ID | 否 |
target-class | 该打包器的目标类 | 是 |
bindingName | 该打包器使用的绑定名称 | 否 |
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从外部来源(即网络)反序列化XML,因为这样可能会引发安全漏洞。
如果您选择使用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服务中使用。