Jaxb处理泛型,转化成xml字符串

摘要:
Javax。xml格式。绑定JAXBException:classcom。测验测试$EchoBody及其任何超类在此上下文中都是未知的。

前言:
  最近的工作内容跟银行有些交互, 对方提供的数据格式采用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);
    }
}

免责声明:文章转载自《Jaxb处理泛型,转化成xml字符串》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇IDEA:Application Server was not connected before run configuration stop, reason: Unable to ping 1099VS2019启用Docker支持的坑下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

CSS 属性选择器的深入挖掘

CSS 属性选择器,可以通过已经存在的属性名或属性值匹配元素。 属性选择器是在 CSS2 中引入的并且在 CSS3 中得到了很好拓展。本文将会比较全面的介绍属性选择器,尽可能的去挖掘这个选择器在不同场景下的不同用法。 简单的语法介绍 [attr]:该选择器选择包含 attr 属性的所有元素,不论 attr 的值为何。 [attr=val]:该选择器仅选...

JS解析XML文件和XML字符串

详细内容请点击 JS解析XML文件 复制代码<script type='text/javascript'>loadXML = function(xmlFile){var xmlDoc=null;//判断浏览器的类型//支持IE浏览器if(!window.DOMParser && window.ActiveXObject){var...

【java】之Method和Field反射获取和设置属性值

packagecom.javaluna.reflect; importjava.lang.reflect.Field; importjava.lang.reflect.Method; importorg.junit.Test; public classReflectDemo01{ @Test public void test...

【Android】缩略图Thumbnails

在Android,多媒体文件(视频和图片)都是有缩略图的,在很多应用中,我们需要获取这些缩略图。比如最近在做一个类似相册的应用,需要扫描相册里面的图片,然后获取其缩略图,使用GridView去展示缩略图,当点击之后,我们需要获取其原始图,所以相关的需求如下: 1)获取缩略图(一个问题是:是否所有的图片以及视频都有缩略图?); 2)将缩略图和原始图关联起来;...

(三)Java 高级特性

第一章 集合框架 集合框架是为表示和操作集合而规定的一种统一的标准系结构。集合框架都包含三个块内容对外的接口、接口的实现和集合运算的算法。 接口:表示集合的抽象数据类型,如Collection、List、Set、Map、Iterator。 实现:集合框架中接口的具体实现,如ArrayList、LinkedList、HashMap、HashSet。 算法:...

OM模块功能&amp;amp;API详解

(一)销售订单概述 1.1   与车间模块关系 当使用ATO类型订单时,订单管理模块会直接在车间模块中产生任务 1.2   与库存模块关系 在销售订单中使用的物料,单位等信息均来自库存模块,在订单执行过程中,按订单保留及销售发运等功能也会对库存模块起作用 1.3   与应收模块关系 销售完成后,订单管理模块会在应收接口中产生INVOICE信息,影响应收...