JAXB:如何解组不同类型但具有共同父级的对象列表?

我们的应用程序中有一个相当常见的模式。 我们在Xml中配置一个对象的集合(或列表),它们都实现了一个公共接口。 在启动时,应用程序读取Xml并使用JAXB创建/配置对象列表。 我从未想过(经过多次阅读各种post后)只使用JAXB的“正确方法”。

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

        NYSE       ...   

在上面的Xml中,每个元素对应于Fee接口的具体子类。 type属性提供有关要实例化的type信息,然后一旦实例化,JAXB解组将应用剩余Xml中的属性。

我总是不得不采取这样的行动:

 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); } } 

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

 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自动执行此操作,而无需编写代码来迭代元素,如上所述?

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

如果您使用MOXy作为JAXB提供程序,则可以使用MOXy的@XmlPaths批注扩展标准JAXB @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 fees; } 

佣金

Fee接口的实现通常会被注释。

 import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) public class Commission implements Fee { @XmlAttribute private String name; @XmlAttribute private String rate; } 

了解更多信息

您可以将XmlAdapter用于此用例。 impl bleow只处理Commission类型,但可以轻松扩展以支持所有类型。 您需要确保AdaptedFee包含Fee接口的所有实现的组合属性。

 import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.adapters.XmlAdapter; public class FeeAdapter extends XmlAdapter{ 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; } } 

使用@XmlJavaTypeAdapter批注配置@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 fees; } 

了解更多信息

如果所有元素都被命名为我认为这是不可能的。 即使它(或者是)从维护的角度来看也会非常混乱。

您是否有能力根据类型重命名各种费用元素(例如而不是 )?

否则,您可以创建一个BaseFee类,其中包含每种可能类型的所有字段。 您可以将数据解组到BaseFee对象列表中,并在运行时将它们转换为更具体的类型,例如

 List fees = ...; for (BaseFee fee : fees) { if (isTradeFee(fee)) { TradeFee tradeFee = toTradeFee(fee); // do something with trade fee... } } 

有点破解,但考虑到要求它应该完成这项工作。