本文详解如何在JAXB中同时解析XML元素的文本值(如220 中的220)及其属性、子元素,核心在于启用混合内容(mixed content)支持,并合理使用@XmlMixed与@XmlElementRef。
本文详解如何在jaxb中同时解析xml元素的文本值(如`
220
`中的`220`)及其属性、子元素,核心在于启用混合内容(mixed content)支持,并合理使用`@xmlmixed`与`@xmlelementref`。
在使用JAXB进行XML到Java对象的反序列化时,若XML元素既包含文本内容(PCDATA),又含有子元素和属性(例如 220 ),默认的JAXB绑定将
忽略文本内容
——这正是你遇到的问题:Id属性能正确映射,但220始终丢失。
根本原因在于:JAXB默认将元素建模为“纯元素内容”(element-only)或“纯文本内容”(simpleType),而
混合内容(mixed content)需显式声明
。当XML Schema中对应元素定义为 mixed="true",或在Java类中通过注解主动启用混合模式时,JAXB才能将文本节点与子元素一同捕获并有序组织。
✅ 正确做法:使用 @XmlMixed + @XmlElementRef 组合
对于Java-First开发(无XSD场景),关键是在承载混合内容的字段上添加 @XmlMixed,并配合 @XmlElementRef 声明可能的子元素类型。文本值将作为 String 或 Serializable 实例,与子元素对象一同存入 List 中,
严格保持XML中出现的顺序
。
以下为推荐的Element类实现(已适配Jakarta EE 9+,使用jakarta.xml.bind.*):
对应的子元素类 Subelement 示例:
? 解析结果示例
对如下XML:
反序列化后,element.getContent() 将返回长度为5的列表:
⚠️
重要注意事项
不要用@XmlValue
:@XmlValue仅适用于“无子元素”的简单类型元素(如 John ),一旦存在子元素,JAXB会抛出 IllegalAnnotationException。
慎用空白字符
:XML中用于格式化的换行/缩进会被视为文本节点并加入content列表。如需过滤,应在业务逻辑中处理(例如 content.stream().filter(o -> o instanceof String).map(Object::toString).filter(s -> !s.trim().isEmpty()))。
避免Lombok @Data
:@XmlMixed字段需手动实现getter/setter以确保初始化(如上例中的getContent()),Lombok自动生成可能引发NullPointerException。
Schema优先更健壮
:若项目允许,建议采用XSD驱动方式,定义 mixed="true" 的complexType,再通过hisrc-higherjaxb-maven-plugin等插件生成带content字段的代码,语义更清晰、类型更安全。
✅ 验证与测试建议
编写JUnit测试验证反序列化行为:
通过以上配置,JAXB即可完整、准确地还原XML中“属性 + 文本 + 子元素”的混合结构,彻底解决文本值丢失问题。
package org.example.element;
import java.io.Serializable;
import java.util.*;
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.annotation.*;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = { "content" })
@XmlRootElement(name = "element")
public class Element implements Serializable {
private static final long serialVersionUID = 20230301L;
@XmlElementRef(name = "subelement", type = JAXBElement.class, required = false)
@XmlMixed
protected List content;
public List getContent() {
if (content == null) {
content = new ArrayList<>();
}
return this.content;
}
@XmlAttribute(name = "Id")
protected String id;
@XmlAttribute(name = "Composite")
protected String composite;
// Getters & Setters (omitted for brevity; Lombok @Data is NOT recommended here due to mixed content complexity)
public String getId() { return id; }
public void setId(String value) { this.id = value; }
public String getComposite() { return composite; }
public void setComposite(String value) { this.composite = value; }
} package org.example.element;
import java.io.Serializable;
import jakarta.xml.bind.annotation.*;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "subelement")
public class Subelement implements Serializable {
private static final long serialVersionUID = 20230301L;
@XmlAttribute(name = "Id")
protected String id;
@XmlAttribute(name = "Composite")
protected String composite;
public String getId() { return id; }
public void setId(String value) { this.id = value; }
public String getComposite() { return composite; }
public void setComposite(String value) { this.composite = value; }
}220 456 [
"220", // String — 文本节点
JAXBElement, // subelement with Id="S1"
JAXBElement, // subelement with Id="S2"
"456" // String — 后续文本节点
] @Test
public void testMixedContentUnmarshal() throws Exception {
JAXBContext ctx = JAXBContext.newInstance(Element.class);
Unmarshaller unmarshaller = ctx.createUnmarshaller();
String xml = "220 ";
Element element = (Element) unmarshaller.unmarshal(new StringReader(xml));
assertEquals("BGM01", element.getId());
assertEquals(1, element.getContent().size());
assertTrue(element.getContent().get(0) instanceof String);
assertEquals("220", element.getContent().get(0));
}