前言:
  最近的工作内容跟银行有些交互, 对方提供的数据格式采用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\" ?>\n" +
"<root>\n" +
"\t<header>header_val</header>\n" +
"\t<response>\n" +
"\t\t<key>key_val</key>\n" +
"\t</response>\n" +
"</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\" ?>\n" +
"<root>\n" +
"\t<header>header_val</header>\n" +
"\t<response>\n" +
"\t\t<key>key_val</key>\n" +
"\t</response>\n" +
"</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的底层实现有个深入的了解.

当Jaxb遇到泛型的更多相关文章

  1. Jaxb解析xml准换为javabean

    先说下这个的背景吧,前些日子,有个以前的小同事说刚接触webservice,想解析下xml,记得我学的时候还是dom4j,sax的解析方式,最近看别人的代码用的jaxb的方式,觉得注解起来很简练,所以 ...

  2. 使用JAXB读写xml

    1.注解 在Java模型中的创建与 xml 文件对应的节点和属性需要用注解来表示 @XmlRootElement 作用:将一个Java类映射为一段XML的根节点参数:    name  定义这个根节点 ...

  3. 【转载】在Jersey JAX-RS 处理泛型List等Collection

    在Java中,从1.5开始,我们就可以使用泛型了(generic),这看上去很像C++ Template,但是实际上它们是不同的.在这里我不想过多的描述细节,你可以从Google上搜索一下. 但是,泛 ...

  4. JAXB 专题二(BSP接口实战)

    BSP下单接口 1.xml格式如下 <?xml version="1.0" encoding="utf-8"?> <Request servi ...

  5. JAXB读写xml

    一.注解 在Java模型中的创建与 xml 文件对应的节点和属性需要用注解来表示 @XmlRootElement 作用:将一个Java类映射为一段XML的根节点参数:    name  定义这个根节点 ...

  6. 一起学 Java(三) 集合框架、数据结构、泛型

    一.Java 集合框架 集合框架是一个用来代表和操纵集合的统一架构.所有的集合框架都包含如下内容: 接口:是代表集合的抽象数据类型.接口允许集合独立操纵其代表的细节.在面向对象的语言,接口通常形成一个 ...

  7. .NET面试题系列[8] - 泛型

    “可变性是以一种类型安全的方式,将一个对象作为另一个对象来使用.“ - Jon Skeet .NET面试题系列目录 .NET面试题系列[1] - .NET框架基础知识(1) .NET面试题系列[2] ...

  8. C#4.0泛型的协变,逆变深入剖析

    C#4.0中有一个新特性:协变与逆变.可能很多人在开发过程中不常用到,但是深入的了解他们,肯定是有好处的. 协变和逆变体现在泛型的接口和委托上面,也就是对泛型参数的声明,可以声明为协变,或者逆变.什么 ...

  9. 编写高质量代码:改善Java程序的151个建议(第7章:泛型和反射___建议106~109)

    建议106:动态代理可以使代理模式更加灵活 Java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标类生成代理,避免重复开发.我们知道一个静态代理是通过主题角色(Prox ...

随机推荐

  1. Utils--字符串的帮助类

    Utils--字符串的帮助类 一个关于字符串过滤的工具类,主要时针对username和password的过滤 package com.bw.yun.utils; import java.securit ...

  2. java反序列化漏洞原理研习

    零.Java反序列化漏洞 java的安全问题首屈一指的就是反序列化漏洞,可以执行命令啊,甚至直接getshell,所以趁着这个假期好好研究一下java的反序列化漏洞.另外呢,组里多位大佬对反序列化漏洞 ...

  3. SpringBoot鸡汤(注解集合二)

    1.@NotNull :属性值不为空 2.@Profiles @Configuration @Profile("production") public class Producti ...

  4. summary_20th,Nov 2018

    一. 常量: 相对于变量,不改变的量 规定常量名全部大写(实际还是变量) 二:数值的运算符: 1. 算术运算符 +  和,    - 减,    *乘,       / 除(浮点数结果) // 取整, ...

  5. zabbix3.4.7版本饼图显示问题

    问题描述 最近使用zabbix3.4.7版本,发现监控Linux的主机关联系统自带的Template OS Linux模版之后,磁盘空间饼图显示有问题,出现空白,如图所示 查看之后,确定为自带的Lem ...

  6. 【资料收集】QT学习资料

    几个专栏 Qt学习之路(3):Hello, world!(续) - 豆子空间 - 51CTO技术博客 http://devbean.blog.51cto.com/448512/194137 Qt 学习 ...

  7. python全栈开发笔记---基本数据类型--字符串魔法

    字符串: def capitalize(self, *args, **kwargs) test = "aLxs" v = test.capitalize() #capitalize ...

  8. 【转载】JVM系列三:JVM参数设置、分析

    不管是YGC还是Full GC,GC过程中都会对导致程序运行中中断,正确的选择不同的GC策略,调整JVM.GC的参数,可以极大的减少由于GC工作,而导致的程序运行中断方面的问题,进而适当的提高Java ...

  9. [Leetcode 739]*还有几天会升温 Daily Temperatures

    [题目] Given a list of daily temperatures T, return a list such that, for each day in the input, tells ...

  10. 7系列FPGA远程更新方案-QuickBoot(转)

    reference: http://xilinx.eetrend.com/d6-xilinx/article/2014-04/7009.html reference :  quickboot meth ...