JAXB 2映射:ELEMENT与多未包装ELEMENT组合的OR关系处理
SLIR元素的JAXB映射方案
DTD定义与约束说明
现有DTD对slir元素的定义如下:
<!ELEMENT slir ((msids | (msid, codeword?, gsm_net_param, trans_id?)+), eqop?, geo_info?, loc_type?, prio?, pushaddr?, service_coverage? %extension.param;)>
根据该定义,slir元素的内容必须满足二选一的互斥规则:
- 要么包含一个
msids元素 - 要么包含至少一组由
msid(必填)、codeword(可选)、gsm_net_param(必填)、trans_id(可选)组成的元素序列
二者绝对不能同时出现。
合法XML示例
示例1:使用msids元素
<slir> <msids> ... </msids> </slir>
示例2:使用多组msid序列
<slir> <!-- 第一组 --> <msid>12345</msid> <codeword>abc</codeword> <gsm_net_param>...</gsm_net_param> <trans_id>t001</trans_id> <!-- 第二组 --> <msid>67890</msid> <gsm_net_param>...</gsm_net_param> </slir>
需求与现有问题
需要将上述XML结构映射为Java实体类,要求把每组msid+可选codeword+gsm_net_param+可选trans_id封装为MsidGsmData类,但编组/解组XML时不能输出该包装类的元素标签,仅保留其内部子元素。
目前已有基础代码片段:
class Slir { @XmlElement private Msids msids; @XmlElement private Msid msid; ... }
但尚未处理msids与MsidGsmData序列的互斥约束,也未实现包装类无标签输出的需求。(注:个人认为这种未将序列组定义为独立元素、而是与msids同级的设计不够合理)
解决方案
方案1:标准JAXB实现(无第三方依赖)
通过@XmlAnyElement手动处理编组和解组,同时添加逻辑校验互斥关系。
1. 定义MsidGsmData包装类
@XmlAccessorType(XmlAccessType.FIELD) public class MsidGsmData { @XmlElement(required = true) private String msid; private String codeword; @XmlElement(name = "gsm_net_param", required = true) private GsmNetParam gsmNetParam; // 假设已实现该实体类 @XmlElement(name = "trans_id") private String transId; // getter、setter方法省略 }
2. 实现Slir类的映射与校验
@XmlAccessorType(XmlAccessType.FIELD) public class Slir { private Msids msids; // 用@XmlTransient隐藏包装类列表,避免生成多余标签 @XmlTransient private List<MsidGsmData> msidGroups; // 编组时:将msidGroups拆分为slir的子元素输出 @XmlAnyElement public List<Element> getMsidGroupElements() { if (msidGroups == null || msidGroups.isEmpty()) { return null; } List<Element> elements = new ArrayList<>(); try { DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document doc = db.newDocument(); for (MsidGsmData group : msidGroups) { // 添加msid元素 Element msidEl = doc.createElement("msid"); msidEl.setTextContent(group.getMsid()); elements.add(msidEl); // 添加可选的codeword元素 if (group.getCodeword() != null) { Element codewordEl = doc.createElement("codeword"); codewordEl.setTextContent(group.getCodeword()); elements.add(codewordEl); } // 添加gsm_net_param元素(此处简化处理,实际可通过JAXB将GsmNetParam转为Element) Element gsmEl = doc.createElement("gsm_net_param"); // 填充gsmEl内容,例如: // JAXBContext ctx = JAXBContext.newInstance(GsmNetParam.class); // ctx.createMarshaller().marshal(group.getGsmNetParam(), gsmEl); elements.add(gsmEl); // 添加可选的trans_id元素 if (group.getTransId() != null) { Element transIdEl = doc.createElement("trans_id"); transIdEl.setTextContent(group.getTransId()); elements.add(transIdEl); } } } catch (ParserConfigurationException e) { throw new RuntimeException("生成XML元素失败", e); } return elements; } // 解组时:将slir的子元素分组为MsidGsmData对象 public void setMsidGroupElements(List<Element> elements) { if (elements == null || elements.isEmpty()) { this.msidGroups = null; return; } List<MsidGsmData> groups = new ArrayList<>(); MsidGsmData currentGroup = null; for (Element el : elements) { String tag = el.getTagName(); switch (tag) { case "msid": // 新组开始,先保存上一组(如果存在) if (currentGroup != null) { groups.add(currentGroup); } currentGroup = new MsidGsmData(); currentGroup.setMsid(el.getTextContent()); break; case "codeword": if (currentGroup != null) { currentGroup.setCodeword(el.getTextContent()); } break; case "gsm_net_param": if (currentGroup != null) { // 解析GsmNetParam,例如: // JAXBContext ctx = JAXBContext.newInstance(GsmNetParam.class); // GsmNetParam param = (GsmNetParam) ctx.createUnmarshaller().unmarshal(el); // currentGroup.setGsmNetParam(param); currentGroup.setGsmNetParam(new GsmNetParam()); // 此处为示例占位 } break; case "trans_id": if (currentGroup != null) { currentGroup.setTransId(el.getTextContent()); } break; // 其他元素交给Slir类的其他字段处理,此处忽略 default: break; } } // 添加最后一组 if (currentGroup != null) { groups.add(currentGroup); } this.msidGroups = groups; } // 其他字段映射 private Eqop eqop; @XmlElement(name = "geo_info") private GeoInfo geoInfo; @XmlElement(name = "loc_type") private LocType locType; private Prio prio; private Pushaddr pushaddr; @XmlElement(name = "service_coverage") private ServiceCoverage serviceCoverage; // 互斥校验:设置msids时检查是否已有msidGroups public void setMsids(Msids msids) { if (this.msidGroups != null && !this.msidGroups.isEmpty()) { throw new IllegalArgumentException("msids与msid序列组不能同时存在"); } this.msids = msids; } // 互斥校验:设置msidGroups时检查是否已有msids public void setMsidGroups(List<MsidGsmData> msidGroups) { if (this.msids != null) { throw new IllegalArgumentException("msids与msid序列组不能同时存在"); } this.msidGroups = msidGroups; } // getter方法省略 }
方案2:使用EclipseLink MOXy(更简洁)
如果可以引入第三方JAXB实现EclipseLink MOXy,可通过@XmlChoice注解直接实现互斥约束,同时自动处理包装类的无标签输出。
1. 添加MOXy依赖(Maven)
<dependency> <groupId>org.eclipse.persistence</groupId> <artifactId>org.eclipse.persistence.moxy</artifactId> <version>3.0.2</version> </dependency>
2. 实现实体类
@XmlAccessorType(XmlAccessType.FIELD) public class Slir { // @XmlChoice标记互斥选项 @XmlChoice private Msids msids; // @XmlChoice标记互斥选项,@XmlElement指定序列的起始元素 @XmlChoice @XmlElement(name = "msid") private List<MsidGsmData> msidGroups; // 其他字段映射(同方案1) private Eqop eqop; @XmlElement(name = "geo_info") private GeoInfo geoInfo; ... // 内部定义MsidGsmData,用@XmlType指定元素顺序 @XmlType(propOrder = {"msid", "codeword", "gsmNetParam", "transId"}) @XmlAccessorType(XmlAccessType.FIELD) public static class MsidGsmData { @XmlElement(required = true) private String msid; private String codeword; @XmlElement(name = "gsm_net_param", required = true) private GsmNetParam gsmNetParam; @XmlElement(name = "trans_id") private String transId; // getter、setter省略 } // 可选:添加互斥校验逻辑(同方案1) }
MOXy会自动识别@XmlChoice标记的字段为互斥选项,编组时直接输出MsidGsmData的子元素,无需包装类标签。
内容的提问来源于stack exchange,提问作者WesternGun
相关产品推荐
相关产品推荐

