描述

近期与供应商对接的时候,对方的接口还是比较稳定的xml接口,与当下流行的json的便捷性相比还是不太方便,中间我也使用了几种工具类,很难有完全适配的,下文我将讲述我解析xml的历程

初次接触

第一次接触xml接口是在对接物流渠道商的时候,当时他们使用的对接方式为soap格式,刚接触解析起来简直要了老命,什么是soap呢?是基于xml的简易协议,常用于webservie,它有自己的一套编码规则,如下

在这里插入图片描述

这种请求网上解析方式就很少,很多只有解析没有构造,文本也不太全,目前见过写的比较好的解析如下:https://blog.csdn.net/RUANJIAOXIAOZI/article/details/90770534

当然还有xsd模式的xml
在这里插入图片描述

因为这次讲的是xml解析,上面只是举个栗子,让你们体会一下xml的变种有多难😭

这种接口不过分的说,至少十年往上的架构了

使用dom4j/jsoup解析

dom4j应该是最经典解析xml的api了,性能优异,功能强大。但是使用起来还是略为麻烦,有点像用java的jsoup去爬取网页,需要一个一个节点的去找

1
2
3
4
5
6
7
8
9
10
11
12
Dom4j获取xml的三种方式
1.读取xml文件,获得document对象
SAXReader reader = new SAXReader();
Document document = reader.read(new File("test.xml"));

2.直接解析xml形式的文本
String text = "<tag></tag>";
Document document = DocumentHelper.parseText(text);

3.主动创建document对象
Document document = DocumentHelper.createDocument();
Element root = document.addElement("tag");
1
2
3
4
jsoup解析html方式
Document document = Jsoup.parse(html);
Element postList = document.getElementById("post_list");
Elements titleEle = postItem.select(".post_item_body a[class='titlelnk']");

以上这种解析方式,属于所见即所得随时可取,但是往往可读性比较差,如图
在这里插入图片描述

当然如果你不嫌麻烦,jsoup也可以解析xml的😎

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class JosupTest {
public static void main(String[] args) throws IOException {
//1. 获取Document对象,根据xml文档获取
//2. 获取user.xml的path
String path = Objects.requireNonNull(JosupTest.class.getClassLoader().getResource("User.xml")).getPath();
//3. 解析xml文档,加载文档进内存,获取dom树----->Document
Document document = Jsoup.parse(new File(path),"UTF-8");
//4. 获取元素对象Elements(类型为ArrayList)
Elements elements = document.getElementsByTag("name");
//5. 测试获取元素的个数是否符合,xml文件中的个数
System.out.println(elements.size());
//5. 测试获取第一个元素
Element element = elements.get(0);
//5. 测试获取第一个元素的文本内容
String name = element.text();
//5. 测试获取第一个元素的名字是否正确
System.out.println(name);
}
}

利用fastjson进行转换

fastjson和gson这两种解析方式是我使用最多的两种解析方式

gson:快速,高效,代码量少,面向对象,但是相对fastjson和jackjson,它的各方面性能都被碾压

fastjson:性能最高,支持多种类型解析,由于fastjson太侧重性能,对于部分高级特性支持不够,有一部分自定义特性完全偏离了json和js规范,可能导致与其他框架不兼容的bug,并且文档缺失较多,而且代码缺少注释较为晦涩,近几年也出现过一些高危漏洞

如果要使用fastjson解析xml为json格式就需要使用工具类的形式进行转换

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.service.zl.model;

import java.util.List;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
/**
* xml工具类
* @author sleep
* @date 2016-09-13
*/
public class XmlTools {

/**
* String 转 org.dom4j.Document
* @param xml
* @return
* @throws DocumentException
*/
public static Document strToDocument(String xml) throws DocumentException {
return DocumentHelper.parseText(xml);
}

/**
* org.dom4j.Document 转 com.alibaba.fastjson.JSONObject
* @param xml
* @return
* @throws DocumentException
*/
public static JSONObject documentToJSONObject(String xml) throws DocumentException {
return elementToJSONObject(strToDocument(xml).getRootElement());
}

/**
* org.dom4j.Element 转 com.alibaba.fastjson.JSONObject
* @param node
* @return
*/
public static JSONObject elementToJSONObject(Element node) {
JSONObject result = new JSONObject();
// 当前节点的名称、文本内容和属性
List<Attribute> listAttr = node.attributes();// 当前节点的所有属性的list
for (Attribute attr : listAttr) {// 遍历当前节点的所有属性
result.put(attr.getName(), attr.getValue());
}
// 递归遍历当前节点所有的子节点
List<Element> listElement = node.elements();// 所有一级子节点的list
if (!listElement.isEmpty()) {
for (Element e : listElement) {// 遍历所有一级子节点
if (e.attributes().isEmpty() && e.elements().isEmpty()) // 判断一级节点是否有属性和子节点
result.put(e.getName(), e.getTextTrim());// 沒有则将当前节点作为上级节点的属性对待
else {
if (!result.containsKey(e.getName())) // 判断父节点是否存在该一级节点名称的属性
result.put(e.getName(), new JSONArray());// 没有则创建
((JSONArray) result.get(e.getName())).add(elementToJSONObject(e));// 将该一级节点放入该节点名称的属性对应的值中
}
}
}
return result;
}
}

上面工具类可以直接把xml转为json格式,非常方便,但是局限性太大 只能单向解析,所以最好的方式还是建立实体类的方式

jaxb和jackson

jaxb:它是一个业界的标准,是一项可以根据xml生成java类的技术。也可以根据xml实例文档反向生成java对象树的方法,与sax和dom不同,不需要了解xml解析技术,就两种操作java对象转xmlxml转java对象

jackson:它性能介于fastjson和gson之间,但是它是目前最流行的api,规范性高,漏洞也没有fastjson多,还支持json和xml转换,目前市场上最好用的api之一

先来看看jaxb的解析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
@XmlRootElement(name = "ServicesError")
public class TaoBaoBaseRequest implements Serializable {
private String errorCode;

private String errorMessage;

@XmlElement(name = "ErrorCode")
public String getErrorCode() {
return errorCode;
}

public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}

@XmlElement(name = "ErrorMessage")
public String getErrorMessage() {
return errorMessage;
}

public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}

jaxb的解析可以参考这篇文章:https://blog.csdn.net/wn084/article/details/80853587

以下是jackjson的解析

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package com.model.taobao;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.stream.XMLStreamException;
import java.io.IOException;

/**
* ClassName: test <br/>
* Description: <br/>
* date: 2022/1/30 9:08<br/>
*
* @author Administrator<br />
*/
public class test {
public static void main(String[] args) throws IOException, XMLStreamException {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?><DataList Airlines=\"ALL\" Dpt=\"KMG\" Arr=\"JJN\" Date=\"20220301\" Carrier=\"SC\" Cabin = \"U\" Code=\"SC9260\"></DataList>";
DataList dataList1 = xmlToObject(xml, DataList.class);
System.out.println(JSON.toJSONString(dataList1));
}
public static String objectToXml(Object object) throws IOException {
XmlMapper xmlMapper= new XmlMapper();
xmlMapper.setDefaultUseWrapper(false);
/* 字段为null,自动忽略,不再序列化 */
xmlMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
/* 设置转换模式 */
xmlMapper.enable(MapperFeature.USE_STD_BEAN_NAMING);
String resultXml = xmlMapper.writeValueAsString(object);

return resultXml;
}
public static <T> T xmlToObject(String xml,Class<T> clazz) throws IOException, XMLStreamException {
XmlMapper xmlMapper= new XmlMapper();
xmlMapper.setDefaultUseWrapper(false);
/* 字段为null,自动忽略,不再序列化 */
xmlMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
/* 设置转换模式 */
xmlMapper.enable(MapperFeature.USE_STD_BEAN_NAMING);
return xmlMapper.readValue(xml, clazz);
}
}


@JacksonXmlRootElement
class DataList{
@JacksonXmlProperty(isAttribute = true)
private String Airlines;
@JacksonXmlProperty(isAttribute = true)
private String Dpt;
@JacksonXmlProperty(isAttribute = true)
private String Arr;
@JacksonXmlProperty(isAttribute = true)
private String Date;
@JacksonXmlProperty(isAttribute = true)
private String Carrier;
@JacksonXmlProperty(isAttribute = true)
private String Cabin;
@JacksonXmlProperty(isAttribute = true)
private String Code;

public String getAirlines() {
return Airlines;
}

public void setAirlines(String airlines) {
Airlines = airlines;
}

public String getDpt() {
return Dpt;
}

public void setDpt(String dpt) {
Dpt = dpt;
}

public String getArr() {
return Arr;
}

public void setArr(String arr) {
Arr = arr;
}

public String getDate() {
return Date;
}

public void setDate(String date) {
Date = date;
}

public String getCarrier() {
return Carrier;
}

public void setCarrier(String carrier) {
Carrier = carrier;
}

public String getCabin() {
return Cabin;
}

public void setCabin(String cabin) {
Cabin = cabin;
}

public String getCode() {
return Code;
}

public void setCode(String code) {
Code = code;
}

public DataList() {
}
}

里面写了一个工具类:objectToXmlxmlToObject可以转换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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
坑一:大小写问题

@JacksonXmlProperty(localName = "Apid")

private Integer Apid;

private Integer getApid()

{

return this.Apid

}

把注解放在成员变量上面,会解析出两个apid字段,一个是<Apid></Apid>,另一个是<apid><apid>

这是因为Jackson的处理机制会自动从属性方法上获取成员变量名,然而在java中,要么以驼峰命名,要么前两个字母都大写,才能用get方法正确地获取属性,所以使用getApid获取的成员名称就是apid,被jackson解析了出来。又因为成员变量上也加了注解,所以也会被解析。这就造成了xml文件生成了两个apid标签。正确的做法是把注解写到get方法上面

正确写法:

private Integer Apid;

@JacksonXmlProperty(localName = "Apid")

private Integer getApid()

{

return this.Apid

}



坑二:Jackson封装list问题

这个问题排查的时候异常困难……开始以为是封装的问题…………(吐槽一下,这个外包项目使用大量xml交互,但是又用不了webservice就得按照固定格式解析封装xml.....改动也贼困难。。。)……最后还是确定了是jackson的问题

private List<Integer> APID;

@JacksonXmlProperty(localName = "APID")
@JacksonXmlElementWrapper(useWrapping = false)
public List<Integer> getAPID() {
return APID;
}

开始APID这个list一直被包装了两层!正确结果应该是<APID>111</APID>

但是得到的是<APID><APID>111</APID></APID>

问题出在JacksonXmlElementWrapper
如果不指定的话这个值默认是true

总结

目前感觉解析xml的api还是比较多的,针对xml的各种奇奇怪怪的格式,并不是都能兼容到,目前感觉最好用的还是jackson+Lombok能比较好的快速解决问题,当然要注意lombok的侵入性和jdk的版本选择最好的方式去解决,有些方式虽然不方便,但是它就是能解决问题……