You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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方法省略
}

如果可以引入第三方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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 01:37:31