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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章