Jakarta EE 系列(四)XML Binding (JAXB) 规范与实践总结

规范

The Jakarta XML Binding provides an API and tools that automate the mapping between XML documents and Java objects.

规范文档:https://jakarta.ee/specifications/xml-binding/

实现

官方认证实现:https://eclipse-ee4j.github.io/jaxb-ri/

依赖下载:https://mvnrepository.com/artifact/com.sun.xml.bind/jaxb-ri,将传递依赖:

1
2
3
4
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</dependency>

示例

本文演示如何生成和解析 XML,主要涉及以下常用注解,更多注解可以自行尝试:

1
2
3
4
@XmlRootElement
@XmlElement
@XmlAttribute
@XmlValue

实体类如下:

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
// @Setter 会报错 com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 4 counts of IllegalAnnotationExceptions
@Getter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@XmlRootElement(name = "head")
class Head {
@XmlElement(name = "trade_code")
private String tradeCode;

@XmlElement(name = "trade_date")
private String tradeDate;

@XmlElement(name = "trade_time")
private String tradeTime;

@XmlElement(name = "serial_no")
private String serialNo;

@XmlElement(name = "parent")
private Parent parent;
}

@Getter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@XmlRootElement
class Parent {
@XmlElement
private List<Child> child;
}

@Getter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
class Child {
@XmlAttribute
private String key;

@XmlValue
private String value;
}

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@Slf4j
public class XmlUtils {

private static final Map<String, JAXBContext> JAXB_CONTEXT_MAP = new HashMap<>();

/**
* 传入一个对象,生成对应的 XML
*/
public static <T> String toXml(T t) {
if (t == null) {
log.warn("[XmlUtils toXml] params is null");
return null;
}

JAXBContext jaxbContext = getJAXBContext(t.getClass());
Marshaller jaxbMarshaller;
try (StringWriter stringWriter = new StringWriter()) {
jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
jaxbMarshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
jaxbMarshaller.marshal(t, stringWriter);
return stringWriter.toString();
} catch (JAXBException | IOException e) {
log.error("[XmlUtils toXml] exception", e);
}
return null;
}

/**
* 将 XML 解析成指定对象
*/
public static <T> T unXml(String xml, Class<T> clazz) {
if (xml == null || xml.isEmpty()) {
log.warn("[XmlUtils unXml] xml is null");
return null;
}

JAXBContext jaxbContext = getJAXBContext(clazz);
try {
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
StringReader reader = new StringReader(xml);
@SuppressWarnings("unchecked")
T unmarshal = (T) unmarshaller.unmarshal(reader);
return unmarshal;
} catch (JAXBException e) {
log.error("[XmlUtils unXml] exception", e);
}
return null;
}

/**
* 根据类名称获取 JAXBContext
*/
private static JAXBContext getJAXBContext(Class clazz) {
JAXBContext jaxbContext = JAXB_CONTEXT_MAP.get(clazz.getName());
if (jaxbContext == null) {
try {
jaxbContext = JAXBContext.newInstance(clazz);
JAXB_CONTEXT_MAP.put(clazz.getName(), jaxbContext);
} catch (JAXBException e) {
log.error(e.getMessage(), e);
}
}
return jaxbContext;
}

}

生成 XML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void testToXml() {
LocalDateTime now = LocalDateTime.now();
Child child = Child.builder()
.key("key")
.value("value")
.build();

Parent parent = Parent.builder()
.child(Arrays.asList(child, child))
.build();

Head head = Head.builder()
.tradeCode("XT-001")
.tradeDate(now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")))
.tradeTime(now.format(DateTimeFormatter.ofPattern("HH:mm:ss")))
.serialNo(UUID.randomUUID().toString())
.parent(parent)
.build();
String xml = XmlUtils.toXml(head);
log.info(xml);
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<head>
<trade_code>XT-001</trade_code>
<trade_date>2020-03-19</trade_date>
<trade_time>18:39:28</trade_time>
<serial_no>114d2c0c-30a8-4275-982c-f8eba86cbadb</serial_no>
<parent>
<child key="key">value</child>
<child key="key">value</child>
</parent>
</head>

解析 XML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testUnXml() {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" +
"<head>\n" +
" <trade_code>XT-001</trade_code>\n" +
" <trade_date>2020-03-19</trade_date>\n" +
" <trade_time>18:39:28</trade_time>\n" +
" <serial_no>114d2c0c-30a8-4275-982c-f8eba86cbadb</serial_no>\n" +
" <parent>\n" +
" <child key=\"key\">value</child>\n" +
" <child key=\"key\">value</child>\n" +
" </parent>\n" +
"</head>";
Head head = XmlUtils.unXml(xml, Head.class);
log.info(head.toString());
}

输出结果

1
Head(tradeCode=XT-001, tradeDate=2020-03-19, tradeTime=18:39:28, serialNo=114d2c0c-30a8-4275-982c-f8eba86cbadb, parent=Parent(child=[Child(key=key, value=value), Child(key=key, value=value)]))

参考

https://jakarta.ee/specifications/xml-binding/

https://en.wikipedia.org/wiki/Jakarta_XML_Binding