JAXB:如何取消将不同类型但具有共同父对象的对象列表取消元化?

2022-09-04 22:25:26

在我们的应用程序中有一个相当常见的模式。我们在Xml中配置一组(或列表)对象,这些对象都实现了一个公共接口。在启动时,应用程序读取 Xml 并使用 JAXB 创建/配置对象列表。我从来没有想出(在多次阅读各种帖子之后)仅使用JAXB来做到这一点的“正确方法”。

例如,我们有一个接口,以及多个具体的实现类,这些类具有一些共同的属性,以及一些发散的属性和非常不同的行为。我们用于配置应用程序使用的费用列表的 Xml 是:Fee

<fees>
   <fee type="Commission" name="commission" rate="0.000125" />
   <fee type="FINRAPerShare" name="FINRA" rate="0.000119" />
   <fee type="SEC" name="SEC" rate="0.0000224" />
   <fee type="Route" name="ROUTES">
       <routes>
        <route>
            <name>NYSE</name>
            <rates>
                <billing code="2" rate="-.0014" normalized="A" />
                <billing code="1" rate=".0029" normalized="R" />
            </rates>
        </route>        
        </routes>
          ...
    </fee>
  </fees>

在上面的 Xml 中,每个元素都对应于 Fee 接口的一个具体子类。该特性提供有关要实例化的类型的信息,然后在实例化后,JAXB 取消编组将应用其余 Xml 中的属性。<fee>type

我总是不得不采取这样的事情:

private void addFees(TradeFeeCalculator calculator) throws Exception {
    NodeList feeElements = configDocument.getElementsByTagName("fee");
    for (int i = 0; i < feeElements.getLength(); i++) {
        Element feeElement = (Element) feeElements.item(i);
        TradeFee fee = createFee(feeElement);
        calculator.add(fee);
    }
}

private TradeFee createFee(Element feeElement) {
    try {
        String type = feeElement.getAttribute("type");
        LOG.info("createFee(): creating TradeFee for type=" + type);
        Class<?> clazz = getClassFromType(type);
        TradeFee fee = (TradeFee) JAXBConfigurator.createAndConfigure(clazz, feeElement);
        return fee;
    } catch (Exception e) {
        throw new RuntimeException("Trade Fees are misconfigured, xml which caused this=" + XmlUtils.toString(feeElement), e);
    }
}

在上面的代码中,它只是一个简单的包装器,用于解组的 JAXB 对象:JAXBConfigurator

public static Object createAndConfigure(Class<?> clazz, Node startNode) {
    try {
        JAXBContext context = JAXBContext.newInstance(clazz);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        @SuppressWarnings("rawtypes")
        JAXBElement configElement = unmarshaller.unmarshal(startNode, clazz);
        return configElement.getValue();
    } catch (JAXBException e) {
        throw new RuntimeException(e);
    }
}

最后,在上面的代码中,我们得到一个List,其中包含在Xml中配置的任何类型。

有没有办法让JAXB自动执行此操作,而不必编写代码来迭代上述元素?


答案 1

注意:我是EclipseLink JAXB(MOXy)负责人,也是JAXB(JSR-222)专家组的成员。

如果使用 MOXy 作为 JAXB 提供程序,则可以使用 MOXy 的注释来扩展标准 JAXB 注释以执行以下操作:@XmlPaths@XmlElements

费用

import java.util.List;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;

@XmlRootElement
public class Fees {

    @XmlElements({
        @XmlElement(type=Commission.class),
        @XmlElement(type=FINRAPerShare.class),
        @XmlElement(type=SEC.class),
        @XmlElement(type=Route.class)
    })
    @XmlPaths({
        @XmlPath("fee[@type='Commission']"),
        @XmlPath("fee[@type='FINRAPerShare']"),
        @XmlPath("fee[@type='SEC']"),
        @XmlPath("fee[@type='Route']")
    })
    private List<Fee> fees;

}

委员会

接口的实现将正常注释。Fee

import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
public class Commission implements Fee {

    @XmlAttribute
    private String name;

    @XmlAttribute
    private String rate;

}

详细信息


答案 2

您可以对此用例使用 。impl bleow仅处理类型,但可以轻松扩展以支持所有类型。您需要确保 包含接口的所有实现中的组合属性。XmlAdapterCommissionAdaptedFeeFee

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class FeeAdapter extends XmlAdapter<FeeAdapter.AdaptedFee, Fee>{

    public static class AdaptedFee {

        @XmlAttribute
        public String type;

        @XmlAttribute
        public String name;

        @XmlAttribute
        public String rate;

    }

    @Override
    public AdaptedFee marshal(Fee fee) throws Exception {
        AdaptedFee adaptedFee = new AdaptedFee();
        if(fee instanceof Commission) {
            Commission commission = (Commission) fee;
            adaptedFee.type = "Commission";
            adaptedFee.name = commission.name;
            adaptedFee.rate = commission.rate;
        }
        return adaptedFee;
    }

    @Override
    public Fee unmarshal(AdaptedFee adaptedFee) throws Exception {
        if("Commission".equals(adaptedFee.type)) {
            Commission commission = new Commission();
            commission.name = adaptedFee.name;
            commission.rate = adaptedFee.rate;
            return commission;
        }
        return null;
    }

}

使用注释配置了 :XmlAdapter@XmlJavaTypeAdapter

import java.util.List;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Fees {

    @XmlElement(name="fee")
    @XmlJavaTypeAdapter(FeeAdapter.class)
    private List<Fee> fees;

}

详细信息


推荐