该章节将讲述,如何通过 JAXB 提供的接口(API),将 Java 类对象转换为 XML 文档。
被缓存的JAXBContext,为了性能上的考虑,将会对JAXBContext做缓存,不过缓存使用到了WeakReference,不用担心 GC 问题。
每次在创建JAXBContext实例时,JAXBContext内部都需要维护好Java类和XML之间的映射关系,这个操作十分消耗性能
。不过JAXBContext是线程安全的,可以共享。一种较好的做法是,在程序初始化时,传入所有的Class,在使用时直接调用创建好的JAXBContext实例,而不是在每次使用时创建。
JAXBContext 是线程安全的,但是 Marshaller, Unmarshaller 都不是线程安全的。
在多线程环境下,应该使用类似下面的方式来初识化JAXBContext。
class MyServlet extends HttpServlet {
static final JAXBContext context = initContext();
private static JAXBContext initContext() {
return JAXBContext.newInstance("....", MyServlet.class.getClassLoader());
}
}
同时,在使用JAXB时,可以类似下面的方式获取Unmarshaller/Marshaller。
public void doGet(HttpServletRequest req, HttpServletResponse resp) {
Unmarshaller u = context.createUnmarshaller();
u.unmarshal(...);
}
JAXBContext 提供了四种静态方法,方便使用者直接调用。为什么是四种而不是四个呢?因为它们有很多重载的方法,名称都是相同的,参数不同而已。
应用程序可以通过多种途径获得 JAXBContext 实例,它们拥有相同的函数名,只是参数不同。
使用类名 Xxx.class
创建 JAXB 实例,最常见的方式是传入特定的class.
// 使用类名 Xxx.class
JAXBContext instance = JAXBContext.newInstance(Student.class);
使用包路径加上类名 com.x.xx.Xxx.class
如果存在同名的Java对象,则可以指定完整路径:
// 使用包路径加上类名 com.x.xx.Xxx.class
JAXBContext instance = JAXBContext.newInstance(com.example.bean.Student.class);
多个类之间使用逗号分隔开
如果有多个对象需要注册,则可以:
// 多个类之间使用逗号分隔开
JAXBContext instance = JAXBContext.newInstance(Student.class, Teacher.class);
传入完整包名
或者也可以对某一个package包下所有的对象编组:
// 传入完整包名
JAXBContext instance = JAXBContext.newInstance("com.example.bean");
这时候并不是指定的包中所有的Class都会用来创建JAXBContext。按照JAXB的规范,我们需要在对应的包中创建一个 jaxb.index 文件,然后在其中指定创建JAXBContext时需要用到的Class,每个Class名称占一行。否则,会报错 “com.example.bean” 不包含 ObjectFactory.class 或 jaxb.index
完整包名之间使用冒号分隔开
甚至对多个package包下所有的对象编组:
// 完整包名之间使用冒号分隔开
JAXBContext instance = JAXBContext.newInstance("com.example.bean:com.example.pojo");
创建一个Marshaller对象,用于将Java内容转换为XML数据。
对使用工厂方法手动创建的内容进行编组与unmarshal操作结果的内容进行编组没有本质区别。客户端可以将Java内容编组成java.io.OutputStream或者java.io.Writer的XML数据。编组程序可以生成已经注册号的SAX2事件流,也可以生成一个DOM节点对象。
创建一个可以用来将 XML 数据转换为 java 内容树的 Unmarshaller 对象。 该方法允许将模式中声明的任何全局 XML 元素解组为实例文档的根元素。
创建一个可用于关联/原地解组/编组操作的 Binder 对象。如果不传参数,默认使用W3C DOM创建一个Binder。
javax.xml.bind.JAXB是2.1版本新增的工具类,其中所有的 public 方法均为静态方法,可以直接通过类名调用。它存在的意义就是帮助使用者简单,快速上手JAXB。
有很多重载的方法,方便使用者传入不同类型的参数。使用者不需要关心实现的细节,传入你需要转换的数据,加上需要生成的类,就能得到需要的数据。
public static <T> T unmarshal( URI xml, Class<T> type ) {}
返回值使用的是泛型T,可以得到任意对象
同样有很多重载的方法,方便使用者传入不同类型的参数。
public static void marshal( Object jaxbObject, URL xml ) {}
第一个参数是对象Obj,可以传入任意对象
注:自 JAXB 1.0 以后,验证已经发生了很大的变化。Validator 类已经废弃,成为了一个可选项。这意味着建议您不要使用此类,实际上,根据您的 JAXB 提供者,该类甚至根本不可用。
1、 格式化输出 Marshaller.JAXB_FORMATTED_OUTPUT
默认的序列化操作,结果是正确的,但是显示成一行不便于阅读,可以通过添加属性JAXB_FORMATTED_OUTPUT 来修正:
2、 编码字符集 Marshaller.JAXB_ENCODING
如果需要改输出XML的字符编码,可以添加属性JAXB_ENCODING:
@Test
public void test2() throws JAXBException {
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(Marshaller.JAXB_ENCODING, "ISO-8859-1");
marshaller.marshal(one, System.out);
}
得到的结果:
<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<one>
<name>Test one</name>
</one>
已经不再是默认的UTF-8。
3、 描述文件地址 Marshaller.JAXB_SCHEMA_LOCATION
有时候,程序可能需要指定 xsi:schemaLocation,则可以添加属性JAXB_SCHEMA_LOCATION:
@Test
public void test3() throws JAXBException {
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd");
marshaller.marshal(one, System.out);
}
得到的结果:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<one xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans/spring-beans.xsd">
<name>Test one</name>
</one>
xsi:schemaLocation 其实是 Namespace 为 “http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd” 里的 schemaLocation 属性,它定义了 XML Namespace 和对应的XSD(Xml Schema Definition)文档的位置的关系。在Spring的配置文件中,时常能见到这个属性。
4、声明无文件地址 Marshaller.JAXB_NO_NAMESPACE_SCHEMA_LOCATION
如果没有Namespeace,但是需要使用Schema,就需要用到JAXB_NO_NAMESPACE_SCHEMA_LOCATION:
@Test
public void test4() throws JAXBException {
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(Marshaller.JAXB_NO_NAMESPACE_SCHEMA_LOCATION, "ehcache.xsd");
marshaller.marshal(one, System.out);
}
得到的结果:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<one xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd">
<name>Test one</name>
</one>
5、 取消XML头信息 Marshaller.JAXB_FRAGMENT
JAXB_FRAGMENT 是一个多面手,在不同的输出场景下,表现出不同的效果。
@Test
public void test5_1() throws JAXBException {
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
marshaller.marshal(one, System.out);
}
在这里指定JAXB_FRAGMENT=true,表明结果不再声明XML头信息,得到的结果:
<one>
<name>Test one</name>
</one>
可以看到,第一行的XML声明不见了。
如果使用到了SAX方式,可以发现如下不同:
@Test
public void test5_2() throws JAXBException {
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
// marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
marshaller.marshal(one, new MyContentHandler());
}
输出结果:
startDocument
endDocument
如果将注释的一行代码放开,再次运行程序将不能得到任何输出。JAXB_FRAGMENT的默认值为false,在其他场景下也有不同的表现。
如果要对 Student.java 进行 XML 操作(对象树与 XML 相转换),有如下步骤
1)使用 JAXBContext.newInstance(…) 初始化 JAXBContext
2)创建 Marshaller 或 Unmarshaller 实例
3)对 XML 文档进行操作
以下通过对 Student.java 的 JAXB API 封装做示例,可以通过多种方式对 Student.java 进行 XML 操作,最优的方式是对每个 XML 封装一个 Java Class,如下:
/**
* a single ton object that is responsible for converting XML to a <Sample> object and <Sample> to an XML.
*/
public class SampleXmlSerializer {
// the singleton instance
private volatile static SampleXmlSerializer instance;
// marshaller and unmarshaller
private final Marshaller marshaller; // java to xml
private final Unmarshaller unmarshaller; // xml to java
private SampleXmlSerializer() throws JAXBException {
// create the JAXBContext object only here, to prevent memory leak
JAXBContext jc = JAXBContext.newInstance(Sample.class);
marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
unmarshaller = jc.createUnmarshaller();
}
/**
* @return the singleton's instance (create it if necessary)
* @throws JAXBException if an error occurred while initializing the object
*/
public static SampleXmlSerializer getInstance() throws JAXBException {
if (instance == null) {
synchronized (SampleXmlSerializer.class) {
// double check the reference
if (instance == null) {
instance = new SampleXmlSerializer();
}
}
}
return instance;
}
/**
* serializes a request object to an XML string
* @param request callback request
* @return the given request serialized to an XML string
* @throws JAXBException if an error occurs during marshaling
*/
public String serialize(Sample request) throws JAXBException {
// output string
StringWriter writer = new StringWriter();
// marshal the request
synchronized (marshaller) {
marshaller.marshal(request, writer);
}
return writer.toString();
}
/**
* deserializes a request object from a given XML string
* @param xmlString XML input string
* @return callback request object that was deserialized from the input string
* @throws JAXBException if an error occurs during unmarshalling
* @throws ClassCastException if the deserialized object is not an instance of <Sample>
*/
public Sample deserialize(String xmlString) throws JAXBException {
StringReader reader = new StringReader(xmlString);
JAXBElement<Sample> element;
synchronized (unmarshaller) {
element = (JAXBElement<Sample>) unmarshaller.unmarshal(reader);
}
return element.getValue();
}
}
以上的 XmlSerialier 工具类,使用单实例(Singleton)保证对 Student.java 的 JAXBContext 上下文只运算一次,避免多次调用浪费运算资源;提供 serialize 和 deserialize 方法,对 Student.java 的 XML 序列化和反序列化提供支持。
另外一种方式,使用静态方法对 JAXB 的 API 进行二次封装,该方式不推荐在产品中使用。
/**
* serializes a request object to an XML string
* @param obj
* @return
*/
public static String convertToXml(Object obj) {
StringWriter sw = new StringWriter();
try {
JAXBContext context = JAXBContext.newInstance(obj.getClass());
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(obj, sw);
} catch (JAXBException e) {
e.printStackTrace();
}
return sw.toString();
}
采用同样的方式,可以将 marshaller 后的数据输出到其他位置。
/**
* deserializes a request object from a given XML string
*/
public static Object convertXmlStrToObject(Class clazz, String xmlStr) {
Object xmlObject = null;
try {
JAXBContext context = JAXBContext.newInstance(clazz);
Unmarshaller unmarshaller = context.createUnmarshaller();
StringReader sr = new StringReader(xmlStr);
xmlObject = unmarshaller.unmarshal(sr);
} catch (JAXBException e) {
e.printStackTrace();
}
return xmlObject;
}
支持多种输入源,
上一章:JAXB-2 JAXB Annotation 注解
目录:学习 JAXB
下一章:JAXB-4 Java 类与 XML 的转换