XML 与 javabean 的转换

XML 可以说是一种被时代淘汰的数据传输格式,毕竟相比较 JSON,其语法,表现形式,以及第三方类库的支持,都要略逊一筹,但最近在对接一些老接口时,主要还是以 XML 为主,而翻阅相关的文档以及博客,没看到很好的文章介绍如何使用 xml 进行数据传输,所以简单写下此文,做一下记录。内心多多少少还是会抵制对接如此老旧的接口,不过生活还是要继续。

Code First

先上一段代码,展示一下如何封装,讲解放到后面

一个典型的对接方提供的 XML 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ORDER>
<ORDER_NO>10086</ORDER_NO>
<TOTAL_PRICE>3.14</TOTAL_PRICE>
<CREATE_TIME>2017-08-26 03:39:30</CREATE_TIME>
<ORDER_ITEMS>
<ORDER_ITEM>
<GOODS_NAME> 德芙 </GOODS_NAME>
<NUM>3</NUM>
</ORDER_ITEM>
<ORDER_ITEM>
<GOODS_NAME> 旺仔 </GOODS_NAME>
<NUM>10</NUM>
</ORDER_ITEM>
</ORDER_ITEMS>
</ORDER>

而我们要对应的实体类,则应当如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@XmlRootElement(name = "ORDER")// <1>
@XmlAccessorType(XmlAccessType.FIELD)// <1>
public class Order {

@XmlElement(name = "ORDER_NO")// <1>
private String orderNo;

@XmlElement(name = "TOTAL_PRICE")
private BigDecimal totalPrice;

@XmlElement(name = "CREATE_TIME")
@XmlJavaTypeAdapter(DateAdapter.class) // <2>
private Date createTime;

@XmlElementWrapper(name = "ORDER_ITEMS") // <3>
@XmlElement(name = "ORDER_ITEM")
private List<OrderItem> orderItems;

}
1
2
3
4
5
6
7
8
9
10
@XmlAccessorType(XmlAccessType.FIELD)
public class OrderItem {

@XmlElement(name = "GOODS_NAME")
private String goodsName;

@XmlElement(name = "NUM")
private Integer num;

}

我举的这个示例基本包含一般情况下所有可能出现的需求

<1> 常用注解 XmlRootElement,XmlAccessorType,XmlElement

<2> 日期转换的适配器注解

<3> 如何在 XML 中设置集合

在介绍这三点之前,先给出转换的工具类

转换工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class XML {

public static String toXmlString(Object obj) {
String result;
try {
JAXBContext context = JAXBContext.newInstance(obj.getClass());
Marshaller marshaller = context.createMarshaller();
StringWriter writer = new StringWriter();
marshaller.marshal(obj, writer);
result = writer.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
return result;
}

public static <T> T parseObject(String input, Class<T> claaz) {
Object result;
try {
JAXBContext context = JAXBContext.newInstance(claaz);
Unmarshaller unmarshaller = context.createUnmarshaller();
result = unmarshaller.unmarshal(new StringReader(input));
} catch (Exception e) {
throw new RuntimeException(e);
}
return (T) result;
}

}

JSON 工具类中,笔者习惯于使用 fastjson,所以干脆连同工具类类名命名和方法命名都按照了它的风格,只有两个方法。

注解的介绍

给实体类加上注解,再使用工具类,就可以实现实体和 XML 的相互转换了。那么前面提到的三个注意点中的相关注解分别代表了什么含义呢?

  • @XmlRootElement

    作用域:类

    代表一个 XML 对象的根节点,常使用 name 属性来可以指定生成 XML 之后的具体名称

  • @XmlElement

    作用域:字段,方法,参数(不常用)

    代表一个 XML 对象的普通界点信息,常使用 name 属性来指定生成 XML 之后的具体名称。需要注意与 @XmlAccessorType 搭配使用时,有一些注意点,见下

  • @XmlAccessorType

    作用域:类,包(不常用)

    告诉解析器,在解析 XML 时要如何获取类的字段属性,有 4 个枚举类型:

    枚举类型 访问方式
    XmlAccessType.FIELD 成员变量
    XmlAccessType.PROPERTY public getter,setter
    XmlAccessType.PUBLIC_MEMBER(默认) public getter,setter+public 成员变量
    XmlAccessType.NONE 必须显示指定 @XmlElement

    我们上述的例子中,使用的方式是在类上配置 @XmlAccessorType(XmlAccessType.FIELD),基于成员变量访问属性,并且,在每一个成员变量之上都显示指定了 name=xxx;而如果配置 @XmlAccessorType(XmlAccessType.PUBLIC_MEMBER) 即默认配置,则你需要将 @XmlElement 注解写在 getter 方法上, 笔者比较习惯例子中的写法。需要注意点的一点是,如果 @XmlAccessorType 与 @XmlElement 的配置不对应,很容易触发自动的转换方式,会导致某个节点出现两次的异常。

  • @XmlJavaTypeAdapter

    作用域:字段, 方法, 类, 包, 参数(前三者常用)

    java 内置的 xml 日期转换类不能满足我们的需求(可以动手试试看默认日期的格式是什么),以及遇到自定义的类,需要配置转换器,就可以使用这个注解,@XmlJavaTypeAdapter 注解接收一个自定义的 Adapter,需要继承自 XmlAdapter<ValueType,BoundType> 抽象类,一个常用的日期转化适配器如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class DateAdapter extends XmlAdapter<String, Date> {

    static ThreadLocal<DateFormat> sdf ;

    static {
    sdf =new ThreadLocal<DateFormat>() {
    @Override
    protected DateFormat initialValue() {
    return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    }
    };
    }

    @Override
    public Date unmarshal(String v) throws Exception {
    return sdf.get().parse(v);
    }

    @Override
    public String marshal(Date v) throws Exception {
    return sdf.get().format(v);
    }
    }

    使用 Adapter 的弊端也很明显,一个适配器只能对应一个日期的格式,在实际开发中我们往往会将日期区分成天维度的日期和秒维度的日期,不能像大多数 JSON 那样拥有灵活的注解,如果有读者有想到好的解决方案,欢迎跟我沟通。涉及到日期格式转化,时刻不要忘记 SimpleDateFormat 线程不安全这一点。

  • @XmlElementWrapper

    XML 中表示集合时,在最外层通常会有一个 Xxxs 或者 XxxList 这样的标签,可以通过 @XmlElementWrapper 实现,其中 name 就代表额外添加的包裹信息是什么, 如上文的 OrderItems。

一些其他的转换工具类

我们主要任务是实现 XML 字符串和 javabean 之间转换,不是解析 XML,所以 dom4j 一类的类库不用考虑。熟悉 spring 的人会了解到一点,spring 其实已经封装了 xml 转换相关的类,即 org.springframework.oxm.jaxb.Jaxb2Marshaller 这个类,他的顶层接口是 org.springframework.oxm.Marshallerorg.springframework.oxm.UnMarshaller。而在 java 规范中,也存在同名的接口:javax.xml.bind.Marshaller,javax.xml.bind.UnMarshaller,这点在使用中需要注意下。笔者的建议是,这种数据格式转换操作,应当尽量引入最少的依赖。所以使用 javax 的类库下的相关方法进行封装。上述的工具类,仅仅只需要引入 javax 包,即可使用了。非常方便、

XML 与 javabean 的转换

https://www.cnkirito.moe/javabean-xml/

作者

徐靖峰

发布于

2017-08-26

更新于

2021-04-07

许可协议


Kirito的技术分享
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×