每次在创建 JAXBContext 实例时,JAXBContext 内部都需要维护好 Java 类和 XML 之间的映射关系,这个操作十分消耗性能
。不过JAXBContext是线程安全的,可以共享。一种较好的做法是,在程序初始化时,传入所有的 Class
,在使用时直接调用创建好的 JAXBContext 实例,而不是在每次使用时创建。
被缓存的 JAXBContext,为了性能上的考虑,将会对 JAXBContext 做缓存,不过缓存使用到了WeakReference,不用担心 GC 问题。
在多线程环境下,应该使用类似下面的方式来初识化 JAXBContext。
/**
* a single ton object that is responsible for converting XML to a object and 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 final Unmarshaller unmarshallerH; // xml to java with validator
// validation event collector for xsd
// If the XML data validation fails, an UnmarshalException (from javax.xml.bind) is thrown.
// create your own error messages, you can pass a ValidationEventCollector to the unmarshaller which will store validation events into it so that you can retrieve an event and query its individual attributes.
private final ValidationEventCollector validationEventCollector;
// Object factory
private final ObjectFactory factory = new ObjectFactory();
// xsd schema file path
private final String xsdPath = "src/main/resources/config/employee.xsd";
private SampleXmlSerializer() throws JAXBException {
// create the JAXBContext object only here, to prevent memory leak
JAXBContext jc = JAXBContext.newInstance(ObjectFactory.class);
marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
unmarshaller = jc.createUnmarshaller();
unmarshallerH = loadUnmarshallerH(xsdPath);
validationEventCollector = new ValidationEventCollector();
}
}
其中,
对应 Java Bean Class;创建单实例静态函数,使用缓存的 JAXBContext 实例。
/**
* @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;
}
JAXBContext 是线程安全的,但是 Marshaller, Unmarshaller 都不是线程安全的。在多线程环境下,应该使用类似下面的 synchronized 同步关键字来序列化对象树和反序列化 XML 文档。
序列化对象树 serialize 函数
/**
* 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 {
// wrap the object in a JAXB element to serialize it
JAXBElement<Sample> element = factory.createSample(request);
// output string
StringWriter writer = new StringWriter();
// marshal the request
synchronized (marshaller) {
marshaller.marshal(element, writer);
}
return writer.toString();
}
反序列化 XML 文档 deserialize 函数
/**
* 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
*/
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();
}
为什么要验证 XML 文档
前文提要,XSD 可以使用标签
对基础数据类型(String, Integer, Date, …)进行限定,例如:
定义了带有一个限定且名为 “password” 的元素。其值必须精确到 8 个字符:
<xs:element name="password">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:length value="8"/>
xs:restriction>
xs:simpleType>
xs:element>
这些限定信息在 xjc 工具进行 xsd 转换 java 时,无法被添加到 JAXB 注解(Annotation),生成的 Java 代码类似如下:
@XmlElement(name = "password")
private String password;
前文提及:
即使文档的形式良好,仍然不能保证它们不会包含错误,
并且这些错误可能会产生严重的后果。
请考虑下面的情况:
您订购的了 5 打激光打印机,而不是 5 台。
通过 XML Schema,大部分这样的错误会被您的验证软件捕获到。
在数据流的某些关键节点/接口
,不仅需要 XML 的序列化与反序列化功能,更需要验证请求数据是否遵循限定,给后续的数据流/业务
提供受信任
的数据源。
初始化带验证器(validator)的解析实例
使用 ValidationEventCollector 根据 XSD 验证 XML 文档,参阅:JAXB - Validate Document before It is Unmarshalled
/**
* @return Unmarshaller instance with xsd schema
*/
private Unmarshaller loadUnmarshallerH(String xsdPath) {
Schema mySchema;
// create this schema object by setting up a schema factory for the schema language of your choice.
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
String filePath = xsdPath;
File file = new File(filePath);
Unmarshaller u = null;
try {
// create the Schema object by calling the factory's method newSchema
// throw SAXException if fail
mySchema = sf.newSchema(file);
JAXBContext jc = JAXBContext.newInstance(ObjectFactory.class);
u = jc.createUnmarshaller();
// After the Unmarshaller object has been established, you pass it the schema.
u.setSchema(mySchema);
// create your own error messages
u.setEventHandler(validationEventCollector);
} catch (SAXException saxe) {
// ...(error handling)
saxe.printStackTrace();
} catch (JAXBException e) {
e.printStackTrace();
}
return u;
}
验证器报表(validator report)
编写 deserializeH 函数,用带有 XSD 限定验证器,解析符合 xsd 结构的 XML 文档;
/**
* validate & 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
*/
public Sample deserializeH(String xmlString) throws JAXBException {
// no unmarshaller available
if (unmarshallerH == null) return null;
StringReader reader = new StringReader(xmlString);
JAXBElement<Sample> element;
synchronized (unmarshallerH) {
try {
element = (JAXBElement<Sample>) unmarshallerH.unmarshal(reader);
} finally {
if (validationEventCollector != null && validationEventCollector.hasEvents()) {
// XML Schema (xsd) validate report
for (ValidationEvent ve : validationEventCollector.getEvents()) {
String msg = ve.getMessage();
ValidationEventLocator vel = ve.getLocator();
int line = vel.getLineNumber();
int column = vel.getColumnNumber();
System.err.println(xmlString + ": " + line + "." + column + ": " + msg);
}
}
}
}
return element.getValue();
}
}
上一章:XSD-7 使用 XSD 实现与 XML 的交互
目录:学习 JAXB
下一章:XSD-9 Maven + XSD