Tag:jaxb Java cdata
Category:Java基础组件
Article From:https://www.cnblogs.com/mumuxinfei/p/9121773.html

 

Preface

  JaxbIt’s really a big tool for mapping and interchanging XML and Java objects. But there are some small pits when dealing with CData content blocks. This article provides a solution to see if you can solve the problem of CData elegantly.

Conventional practice

  The most common practice on the Internet is to make use of the combination of XmlAdapter and CharacterEscapeHandler (sun’s API).
  First, define the CDataAdapter class for object type transformation.

public class CDataAdapter extends XmlAdapter<String, String> {

    @Override
    public String unmarshal(String v) throws Exception {
        return v;
    }

    @Override
    public String marshal(String v) throws Exception {
        return new StringBuilder("<![CDATA[").append(v).append("]]>").toString();
    }

}

  It acts on the attribute variable with the help of annotation XmlJavaTypeAdapter, such as the following class object:

@XmlRootElement(name="root")
public static class TNode {
        
     @XmlJavaTypeAdapter(value=CDataAdapter.class)
     @XmlElement(name="text", required = true)
     private String text;
        
}

  When using Marshaller to XML text, the result is as follows:

<root>
    <text><![CDATA[Li Leiai Han Meimei]]> < /text>< /root>

  This is actually different from what we expected. What we really want is the following:

<root>
    <text><![CDATA[Li Leiai Han Meimei]]> < /text>< /root>

  The essential reason is that Jaxb will escape the character’&lt, ‘,’&gt,’ by default. In order to solve this problem, CharacterEscapeHandler is gorgeous.

import com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler;

marshaller.setProperty(
    "com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler",
    new CharacterEscapeHandler() {
        @Override
        public void escape(char[] ch, int start, int length, boolean isAttVal, Writer writer) 
                throws IOException {
            writer.write(ch, start, length);
        }
    }
);

  The test results perfectly solve the problem. Then the following problems are slightly embarrassed. When using Maven to compile and package, the following mistakes will be encountered:

[ERROR] Compilation failure
[ERROR] The package com.sun.xml.internal.bind.marshaller does not exist.

  JavaIn engineering development, it is generally not recommended to call the internal API directly (starting with com.sun).

Improvement program:

  Reference a lot of net friends of the blog, the general idea is the same, is the use of heavy load XMLStreamWriter class implementation. More real practice is to reload the writeCharacters method, in the CData mark (<! [CDATA[)]]>) when you wrap the text, you choose to call the writeCData function, which can be roughly explained in the following code:

public class CDataXMLStreamWriter implements XMLStreamWriter {

    // *) Overloading writeCharacters, in case of CDATA tag, calls the writeCData method instead.@OverridePublic void writeCharacters (StringText) throws XMLStreamException {If (text.startsWith ("<! [CDATA[") & & text.endsWIth ("]]>")) {WriteCData (text.substring (9, text.length () - 3));} else {WriteCharacters (text);}}/ / *) demo use}

  The real practice is not to adopt the complete scheme of implementing XmlStreamWriter interface, but to adopt proxy mode.

private static class CDataHandler implements InvocationHandler {
    // *) A separate intercepting writeCharacters (String) methodPrivate static Method gWriteCharactersMethod = null;Static {Try {GWriteCharactersMethod = XMLStreamWriter.class.getDeClaredMethod ("writeCharacters", String.class);} catch (NoSuchMethodException E) {E.printStackTrace ();}}Private XMLStreamWriter writer;Public CDataHandleR (XMLStreamWriter writer) {This.writer = writer;}@OverridePublic Object invOke (Object proxy, Method method, Object[] args) throws Throwable {If (gWriteCharactersMethOd.equals (method)) {String text = (String) args[0];/ *) when the CDATA tag is encountered, the call is transferredWriteCData methodIf (text = null & & text.startsWith ("< [CDATA[") & & texT.endsWith ("]]>")) {Writer.writeCData (text.substring (9, text.length () - 3));Return null;}}Return method.invoke (writer, args);}}

  The specific Marshaller code snippets are as follows:

public static <T> String mapToXmlWithCData(T obj) {

    try {

        StringWriter writer = new StringWriter();
        XMLStreamWriter streamWriter = XMLOutputFactory.newInstance()
                .createXMLStreamWriter(writer);
        // *) Use the dynamic proxy mode to adjust the streamWriter function.XMLStreamWriter cdataStreamWriter = (XMLStreamWriter) Proxy.NewProxyInstance (StreamWriter.getClass ().GetClassLoader (),StreamWrIter.getClass ().GetInterfaces (),New CDataHandler (streamWriter));JAXBContext JC = JAXBContext.newInstance (obj.getClass ());Marshaller marshaller = jc.createMArshaller ();Marshaller.setProperty (Marshaller.JAXB_FORMATTED_OUTPUT, true);MarshalLer.setProperty (Marshaller.JAXB_ENCODING, "UTF-8");Marshaller.marshal (obj, cdataStreamWrit)Er);Return writer.toString ();} catch (JAXBException E) {E.printStackTrace ();} catch (XMLStreamException E) {E.printStackTrace ();}Return null;}

  The results of the test perfectly solve the CData problem (functional implementation + bypassing the sun API), but there is a little flaw in this, which is the alignment problem, the code is not controlled to align.

Alignment improvement

  This side needs the help of Transformer class, and the idea is to format the final XML text.

// *) Formatting the XML textPublic static String indentFormat (String XML) {Try {TransformerFactory fActory = TransformerFactory.newInstance ();Transformer transformer = factory.newTransformer ();Transformer.setOutputProperty (OutputKeys.INDENT, "yes");Transformer.setOutputProPerty ("{http://xml.apache.org/xslt}indent-amount", "4");StringWriter formattedStringWriter= new StringWriter ();Transformer.transform (New StreamSource (New StringReader (XML)).New StreamResult (formattedStringWriter));Return formattedStringWriter.toString ();} catch (TransformerException E) {}Return null;}

  

A complete solution

  Here, put all the above code in complete:

import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

// *) XmlAdapterClass, modify class fields to achieve the goal of automatically adding CDATA markup.Public static class CDataAdapter extends XmlAdapter< String, String>{@OverridePublic String unmarshal (String V) throws Exception {Return V;}@OverridePublic String Marshal (String V) throws Exception {Return new StringBuilder("<! [CDATA[").Append (V).Append ("]]>").toString ();}}/ *) dynamic proxyPrivateStatic class CDataHandler implements InvocationHandler {Private static Method gWriteCharacterSMethod = null;Static {Try {GWriteCharactersMethod = XMLStreamWriter.clasS.getDeclaredMethod ("writeCharacters", String.class);} catch (NoSuchMeThodException E) {E.printStackTrace ();}}Private XMLStreamWriter wriTer;Public CDataHandler (XMLStreamWriter writer) {This.writer = writer;}@OvErridePublic Object invoke (Object proxy, Method method, Object[] args) throws Throwable {If (gWriteCharactersMethod.equals (method)) {String text = (String) args[0];If (text = null & & text.startsWith ("< [CDATA[") & & text.endsWith ("&").) {Writer.writeCData (text.substring (9, text.length () - 3));ReturnNull;}}Return method.invoke (writer, args);}}/ / *) generation of XMLPubLIC static < T> String mapToXmlWithCData (T obj, Boolean formatted) {Try {StringWriter writer = new StringWriter ();XMLStreamWriter streamWriter = XMLOutputFactory.newInstaNce ().createXMLStreamWriter (writer);/ / * use the dynamic proxy mode to adjust the streamWriter function.XMLStreamWriter cdataStreamWriter = (XMLStreamWriter) Proxy.newProxyInstance (StreamWriter.getClass ().GetClassLoader (),StreamWriter.getClass ().GetInterfaces (),New CDataHandler (streamWriter));JAXBContext JC = JAXBContext.newInstance (obj.getClass ());Marshaller marshaller = jc.createMarshaller ();Marshaller.SetProperty (Marshaller.JAXB_ENCODING, "UTF-8");Marshaller.marshal (obj, cdataStreamWriter);/ / *) alignment difference processingIf (formatted) {Return indentFormat (writer.toString ());} else {Return writer.toString ();}} catch (JAXBException E) {E.printStackTrace ();} catch (XMLStreamException E) {E.printStackTrace ();}Return null;}/ *) XML text alignmentPublic static String indentFormat (String XML) {Try {TransformerFactory factory = TransformerFactory.newInstance ();Transformer transformer = faCtory.newTransformer ();/ / *) open the alignment switchTransformer.setOutputProperty (OutputKeys.INDENT"Yes");/ / *) ignore the XML declaration header informationTransformer.setOutputProperty (OutputKeys.OMIT_XML_DECLAR)ATION, "yes");Transformer.setOutputProperty (OutputKeys.ENCODING, "UTF-8");TransforMer.setOutputProperty ("{http://xml.apache.org/xslt}indent-amount", "4");StringWriter formaTtedStringWriter = new StringWriter ();Transformer.transform (New StreamSource (New StringRead)Er (XML)),New StreamResult (formattedStringWriter));Return "<? XML versioN=\ "1.0\" encoding=\ "UTF-8\"? > \n ""+ formattedStringWriter.toString ();} catch(TransformerException E) {}Return null;}

  Write specific test cases:

@NoArgsConstructor
@AllArgsConstructor
@XmlRootElement(name="root")
public static class TNode {
    @XmlElement(name="key", required = true)
    private String key;

    @XmlJavaTypeAdapter(value=CDataAdapter.class)
    @XmlElement(name="text", required = true)
    private String text;
}

public static void main(String[] args) {
    TNode node = new TNode("key", "Li Leiai Han Meimei ");String XML = mapToXmlWithCData (node, true);System.out.println (XML);}

  The results of the test output are as follows:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <key>key</key>
    <text><![CDATA[Li Leiai Han Meimei]]> < /text>< /root>

 

summary

  In general, the improved scheme circumvents the compilation restrictions of sun API. At the same time, it can satisfy previous functional requirements. It is worth encouraging a little.

 
Link of this Article: How Jaxb handles CData gracefully

Leave a Reply

Your email address will not be published. Required fields are marked *