XSD-8 使用 XJC 生成的代码

8 使用 XJC 生成的代码

每次在创建 JAXBContext 实例时,JAXBContext 内部都需要维护好 Java 类和 XML 之间的映射关系,这个操作十分消耗性能。不过JAXBContext是线程安全的,可以共享。一种较好的做法是,在程序初始化时,传入所有的 Class,在使用时直接调用创建好的 JAXBContext 实例,而不是在每次使用时创建。
被缓存的 JAXBContext,为了性能上的考虑,将会对 JAXBContext 做缓存,不过缓存使用到了WeakReference,不用担心 GC 问题。

初识化 JAXBContext

在多线程环境下,应该使用类似下面的方式来初识化 JAXBContext。

/**
 * 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 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();
    }
}

其中,

  • ObjectFactory 指由 xjc 生成的 ObjectFactory.java 类;
  • Sample class 是指 XSD 文档中 <element> 对应 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 <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();
    }

在反序列化前验证 XML 文档

为什么要验证 XML 文档
前文提要,XSD 可以使用标签 <simpleType> 对基础数据类型(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 <Sample>
     */
    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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值