zoukankan      html  css  js  c++  java
  • Jaxb处理泛型,转化成xml字符串

    前言:
      最近的工作内容跟银行有些交互, 对方提供的数据格式采用xml(不是预期的json/protobuf). 为了开发方便, 需要借助jaxb来实现xml和java对象之间的映射. 它还是有点像jackson, 通过简单的注解配置, 就能轻松实现json和java对象的互转. 不过笔者在java类中引入泛型时, 还是踩了不少jaxb的坑, 这边做下笔记.

    实现的目标:
      交互的数据格式和协议遵循通用的设计, 由header和body构成.
      请求的数据格式如下:

    <?xml version="1.0" encoding="UTF-8" ?>
    <root>
        <!-- 请求头 -->
        <header></header>
        <request>
            <!-- 具体的请求参数, 根据接口而定 -->
        </request>
    </root>
    

      响应的数据格式如下:

    <?xml version="1.0" encoding="UTF-8" ?>
    <root>
        <!-- 响应头 -->
        <header></header>
        <response>
            <!-- 具体的响应结果, 根据接口而定 -->
        </response>
    </root>
    

      header信息头相对固定, 而具体的request/response取决于具体的业务接口, 在进行对象映射中, 我们也是针对body消息体进行泛型化.

    请求类抽象和测试代码:
      针对请求的数据格式, 我们可以轻易的设计如下类结构:

    // *) 请求类(模板)
    @Getter
    @Setter
    @ToString
    public class Req<T> {
        private String header;
        private T value;
    }
     
    // *) 具体的实体请求
    @Getter
    @Setter
    @ToString
    @AllArgsConstructor
    @NoArgsConstructor
    public class EchoBody {
        private String key;
    }
    

      注: 这边的注解Getter/Setter/ToString等皆是lombok的注解.
      测试代码如下:

    public static void main(String[] args) {
     
        Req<EchoBody> req = new Req<EchoBody>();
        req.setHeader("header");
        req.setValue(new EchoBody("key"));
     
        try {
            StringWriter sw = new StringWriter();
            JAXBContext context = JAXBContext.newInstance(req.getClass());
            Marshaller marshaller = context.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.marshal(req, sw);
            System.out.println(sw.toString());
        } catch (JAXBException e) {
            e.printStackTrace();
        }
     
    }
    

      注: 该代码主要测试对象到xml的转换是否顺利.

    演进和迭代:
      先来看第一版本, 引入jaxb注解, 同时省略lombok注解.

    @XmlRootElement(name="root")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Req<T> {
        @XmlElement(name="header",required = true)
        private String header;
        @XmlElement(name="request", required = true)
        private T value;
    }
     
    @XmlRootElement(name="request")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class EchoBody {
        @XmlElement(name="key", required = true)
        private String key;
    }
    

      运行测试的结果如下:

    javax.xml.bind.MarshalException
     - with linked exception:
    [com.sun.istack.internal.SAXException2: class com.test.Test$EchoBody以及其任何超类对此上下文都是未知的。
    javax.xml.bind.JAXBException: class com.test.Test$EchoBody以及其任何超类对此上下文都是未知的。]
        at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:311)
        at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:236)
        at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:116)
        at com.test.Test.main(Test.java:55)`
    

      来首战遇到一些小挫折, 通过百度得知需要借助@XmlSeeAlso类规避该问题.
      修改代码如下:

    @XmlRootElement(name="root")
    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlSeeAlso({EchoBody.class})
    public class Req<T> {
        @XmlElement(name="header",required = true)
        private String header;
        @XmlElement(name="request", required = true)
        private T value;
    }
     
    @XmlRootElement(name="request")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class EchoBody {
        @XmlElement(name="key", required = true)
        private String key;
    }
    

      运行后的输出结果如下:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <root>
        <header>header</header>
        <request xsi:type="echoBody" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <key>key</key>
        </request>
    </root>
    

      看来非常的成功, 但是request标签里包含了xsi:type和xmlns:xsi这些属性, 能否把这些信息去除, 网上查阅得知, 借助@XmlAnyElement(lax = true)来达到目的, 再次修改版本.

    @XmlRootElement(name="root")
    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlSeeAlso({EchoBody.class})
    public class Req<T> {
        @XmlElement(name="header",required = true)
        private String header;
        @XmlAnyElement(lax = true)
        private T value;
    }
     
    @XmlRootElement(name="request")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class EchoBody {
        @XmlElement(name="key", required = true)
        private String key;
    }
    

      这次的结果可以称得上完美(perfect):

    
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <root>
        <header>header</header>
        <request>
            <key>key</key>
        </request>
    </root>
    

      

    响应类抽象和测试代码:
      有了请求类的顺利结果, 我们在设计响应类也是有迹可循.
      响应类的代码如下:

    @Getter
    @Setter
    @ToString
    public class Res<T> {
        private String header;
        private T value;
    }
     
    @Getter
    @Setter
    @ToString
    @AllArgsConstructor
    @NoArgsConstructor
    public class EchoAck {
        private String value;
    }
     
     
    @Getter
    @Setter
    @ToString
    @AllArgsConstructor
    @NoArgsConstructor
    public class HelloAck {
        private String key;
    }
    

      注: 这边暂时隐去jaxb的注解, 剩下的都是lombok注解.
      测试用例代码如下:

    public static void main(String[] args) {
     
        String xml = "" +
                "<?xml version="1.0" encoding="UTF-8" ?>
    " +
                "<root>
    " +
                "	<header>header_val</header>
    " +
                "	<response>
    " +
                "		<key>key_val</key>
    " +
                "	</response>
    " +
                "</root>";
        Res<HelloAck> res = new Res<HelloAck>();
     
        try {
            JAXBContext jc = JAXBContext.newInstance(res.getClass());
            Unmarshaller unmar = jc.createUnmarshaller();
            Res<HelloAck> r =  (Res<HelloAck>)unmar.unmarshal(new StringReader(xml));
            System.out.println(r);
        } catch (JAXBException e) {
            e.printStackTrace();
        }
     
    }
    

      

    演进和迭代:
      添加jaxb注解, 隐去lombok注解, 大致如下:

    @XmlRootElement(name="root")
    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlSeeAlso({HelloAck.class, EchoAck.class})
    public class Res<T> {
        @XmlElement(name="header",required = true)
        private String header;
        @XmlAnyElement(lax = true)
        private T value;
    }
     
    @XmlRootElement(name="response")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class EchoAck {
        @XmlElement(name="value", required = true)
        private String value;
    }
     
    @XmlRootElement(name="response")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class HelloAck {
        @XmlElement(name="key", required = true)
        private String key;
    }
    

      运行的如下:

    Res(header=header_val, value=EchoAck(value=null))
    

      这边需要的注意的是, 代码中指定反解的类是HelloAck, 但是这边反解的类却是EchoAck. 由此可见, jaxb在xml到对象转换时, 其泛型类的选取存在问题(猜测java泛型在编译时类型被擦去, 反射不能确定具体那个类).
      针对这种情况, 一个好的建议是, 单独引入实体类(wrapper), 网友的做法也是类似, 只是没有给出直接的理由.

    @Getter
    @Setter
    @ToString
    @XmlTransient   // 抽象基类改为注解XmlTransient, 切记
    @XmlAccessorType(XmlAccessType.FIELD)
    public abstract class Res<T> {
        @XmlElement(name="header",required = true)
        private String header;
        @XmlAnyElement(lax = true)
        private T value;
    }
     
    @Getter
    @Setter
    @ToString
    @AllArgsConstructor
    @NoArgsConstructor
    @XmlRootElement(name="response")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class EchoAck {
        @XmlElement(name="value", required = true)
        private String value;
    }
     
     
    @Getter
    @Setter
    @ToString
    @AllArgsConstructor
    @NoArgsConstructor
    @XmlRootElement(name="response")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class HelloAck {
        @XmlElement(name="key", required = true)
        private String key;
    }
     
    @Getter
    @Setter
    @ToString(callSuper = true)
    @XmlRootElement(name="root")
    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlSeeAlso({HelloAck.class})
    public class HelloRes extends Res<HelloAck> {
    }
    

      修改测试代码:

    public static void main(String[] args) {
     
        String xml = "" +
                "<?xml version="1.0" encoding="UTF-8" ?>
    " +
                "<root>
    " +
                "	<header>header_val</header>
    " +
                "	<response>
    " +
                "		<key>key_val</key>
    " +
                "	</response>
    " +
                "</root>";
        HelloRes res = new HelloRes();
     
        try {
            JAXBContext jc = JAXBContext.newInstance(HelloRes.class);
            Unmarshaller unmar = jc.createUnmarshaller();
            HelloRes r =  (HelloRes)unmar.unmarshal(new StringReader(xml));
            System.out.println(r);
        } catch (JAXBException e) {
            e.printStackTrace();
        }
     
    

      运行结果如下:

    HelloRes(super=Res(header=header_val, value=HelloAck(key=key_val)))
    

      符合预期, 这边的做法就是wrap一个泛型类, 姑且可以理解为在编译前指定类, 避免反射出偏差.

    总结:
      总的来说jaxb在涉及泛型时, 还是有一些坑的, 这边总结了一下. 不过总的来说, 知其然不知其所以然, 希翼后面能够对jaxb的底层实现有个深入的了解.

    最后附上一个工具类

    
    import javax.xml.bind.JAXBContext;
    import javax.xml.bind.JAXBException;
    import javax.xml.bind.Marshaller;
    import javax.xml.bind.Unmarshaller;
    import java.io.*;
    
    /**
     * @author hp
     * @date 2020/2/13
     */
    public class XmlUtils {
    
    
        private static String DEFAULT_CHARSET = "Unicode";
    
        public static String toXml(Object model) throws JAXBException, IOException {
            ByteArrayOutputStream output = new ByteArrayOutputStream(1024);
            marshal(model, output);
            output.flush();
            return new String(output.toByteArray(), DEFAULT_CHARSET);
        }
    
        public static String toXml(Object model, boolean isFormatOut) throws JAXBException, IOException {
            ByteArrayOutputStream output = new ByteArrayOutputStream(1024);
            marshal(model, output, isFormatOut);
            output.flush();
            return new String(output.toByteArray(), DEFAULT_CHARSET);
        }
    
        public static void marshal(Object model, OutputStream output) throws JAXBException {
            JAXBContext jaxbContext = JAXBContext.newInstance(model.getClass());
            Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
            jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            jaxbMarshaller.setProperty(Marshaller.JAXB_ENCODING, DEFAULT_CHARSET);
            jaxbMarshaller.marshal(model, output);
        }
    
        public static void marshal(Object model, OutputStream output, boolean isFormatOut) throws JAXBException {
            JAXBContext jaxbContext = JAXBContext.newInstance(model.getClass());
            Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
            jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, isFormatOut);
            jaxbMarshaller.setProperty(Marshaller.JAXB_ENCODING, DEFAULT_CHARSET);
            jaxbMarshaller.marshal(model, output);
        }
    
        public static <T> T parseXml(Class<T> clazz, String xml) throws JAXBException, IOException {
            byte[] buf = xml.getBytes(DEFAULT_CHARSET);
            ByteArrayInputStream input = new ByteArrayInputStream(buf, 0, buf.length);
            return unmarshal(clazz, input);
        }
    
        @SuppressWarnings("unchecked")
        public static <T> T unmarshal(Class<T> clazz, InputStream input) throws JAXBException {
            JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
            Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
            return (T) jaxbUnmarshaller.unmarshal(input);
        }
    
        public static void saveXmlToFile(Object model, String filename) throws FileNotFoundException, JAXBException {
            FileOutputStream fos = new FileOutputStream(filename);
            marshal(model, fos);
        }
    
        public static void saveXmlToFile(Object model, File file) throws FileNotFoundException, JAXBException {
            FileOutputStream fos = new FileOutputStream(file);
            marshal(model, fos);
        }
    
        public static <T> T loadXmlFromFile(Class<T> clazz, String filename) throws FileNotFoundException, JAXBException {
            return unmarshal(clazz, new FileInputStream(filename));
        }
    
        public static <T> T loadXmlFromFile(Class<T> clazz, File file) throws FileNotFoundException, JAXBException {
            return unmarshal(clazz, new FileInputStream(file));
        }
    
        public static <T> T loadXmlFromFile(Class<T> clazz, InputStream is) throws JAXBException {
            return unmarshal(clazz, is);
        }
    }
    
    
  • 相关阅读:
    机器学习与深度学习资料
    JVM调优实战
    Spark on Yarn下JVM的OOM问题及解决方式
    Centos环境下部署游戏服务器-简介
    新华网,要厚道
    物联网操作系统在运营商领域推广的理论分析
    Android基础之Activity launchMode详解
    《高效程序员的修炼》读后感
    Java科普之算法剖析
    Java科普之基础知识回顾
  • 原文地址:https://www.cnblogs.com/lameclimber/p/12305576.html
Copyright © 2011-2022 走看看