Java/JAXB:根据属性将 Xml 解组为特定子类

Posted

技术标签:

【中文标题】Java/JAXB:根据属性将 Xml 解组为特定子类【英文标题】:Java/JAXB: Unmarshall Xml to specific subclass based on an attribute 【发布时间】:2011-02-28 20:26:41 【问题描述】:

是否可以使用 JAXB 根​​据 xml 的属性将 xml 解组为特定的 Java 类?

<shapes>
  <shape type="square" points="4" square-specific-attribute="foo" />
  <shape type="triangle" points="3" triangle-specific-attribute="bar" />
</shapes>

我想要一个包含三角形和正方形的 Shape 对象列表,每个对象都有自己的特定于形状的属性。即:

abstract class Shape 
    int points;
    //...etc


class Square extends Shape 
    String square-specific-attribute;
    //...etc


class Triangle extends Shape 
    String triangle-specific-attribute;
    //...etc

我目前只是将所有属性放在一个大的“形状”类中,这并不理想。

如果形状被正确命名为 xml 元素,我可以让它工作,但不幸的是我无法控制我正在检索的 xml。

谢谢!

【问题讨论】:

【参考方案1】:

JAXB 是一个规范,具体的实现会提供扩展点来做这样的事情。如果您使用的是EclipseLink JAXB (MOXy),您可以按如下方式修改 Shape 类:

import javax.xml.bind.annotation.XmlAttribute;
import org.eclipse.persistence.oxm.annotations.XmlCustomizer;

@XmlCustomizer(ShapeCustomizer.class)
public abstract class Shape 

    int points;

    @XmlAttribute
    public int getPoints() 
        return points;
    

    public void setPoints(int points) 
        this.points = points;
    


然后使用 MOXy @XMLCustomizer 您可以访问 InheritancePolicy 并将类指示符字段从“@xsi:type”更改为“type”:

import org.eclipse.persistence.config.DescriptorCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;

public class ShapeCustomizer implements DescriptorCustomizer 

    @Override
    public void customize(ClassDescriptor descriptor) throws Exception 
        descriptor.getInheritancePolicy().setClassIndicatorFieldName("@type");
    

您需要确保模型类(Shape、Square 等)中有一个 jaxb.properties 文件,其中包含指定 EclipseLink MOXy JAXB 实现的以下条目:

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

下面是其余的模型类:

形状

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Shapes 

    private List<Shape> shape = new ArrayList<Shape>();;

    public List<Shape> getShape() 
        return shape;
    

    public void setShape(List<Shape> shape) 
        this.shape = shape;
    


正方形

import javax.xml.bind.annotation.XmlAttribute;

public class Square extends Shape 
    private String squareSpecificAttribute;

    @XmlAttribute(name="square-specific-attribute")
    public String getSquareSpecificAttribute() 
        return squareSpecificAttribute;
    

    public void setSquareSpecificAttribute(String s) 
        this.squareSpecificAttribute = s;
    


三角形

import javax.xml.bind.annotation.XmlAttribute;

public class Triangle extends Shape 
    private String triangleSpecificAttribute;

    @XmlAttribute(name="triangle-specific-attribute")
    public String getTriangleSpecificAttribute() 
        return triangleSpecificAttribute;
    

    public void setTriangleSpecificAttribute(String t) 
        this.triangleSpecificAttribute = t;
    


下面是一个演示程序,用于检查一切是否正常:

import java.io.StringReader;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class Demo 

    public static void main(String[] args) throws Exception 
        JAXBContext jaxbContext = JAXBContext.newInstance(Shapes.class, Triangle.class, Square.class);

        StringReader xml = new StringReader("<shapes><shape square-specific-attribute='square stuff' type='square'><points>4</points></shape><shape triangle-specific-attribute='triangle stuff' type='triangle'><points>3</points></shape></shapes>");
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        Shapes root = (Shapes) unmarshaller.unmarshal(xml);

        Marshaller marshaller = jaxbContext.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(root, System.out);
    

我希望这会有所帮助。

有关 EclipseLink MOXy 的更多信息,请参阅:

http://www.eclipse.org/eclipselink/moxy.php

编辑

在 EclipseLink 2.2 中,我们使其更易于配置,请查看以下文章了解更多信息:

http://bdoughan.blogspot.com/2010/11/jaxb-and-inheritance-moxy-extension.html

【讨论】:

我绝对对这个作为解决方案感兴趣,但我似乎无法让它发挥作用。 Shape 实际上不是我的根元素是否重要?如果我尝试解组集合 JAXB/MOXy 似乎不会打扰使用定制器并抛出一堆关于尝试实例化抽象类的错误。我还缺少另一件作品吗? 我相信您缺少将 EclipseLink MOXy 指定为 JAXB 实现的 jaxb.properties 文件。我已经更新了上面的例子,使其更加完整。 你太棒了!这完美地工作。感谢您不遗余力地帮助我解决这个问题。 类名不等于类型怎么办?如何告诉 JAXB 哪个类型映射到哪个对象? 我发现***.com/a/15617571/5987669 描述了如何做同样的事情,但带有注释。我发现它更直观,更易于使用。【参考方案2】:

注解@XmlElements可以让你指定哪个标签对应哪个子类。

@XmlElements(
    @XmlElement(name="square", type=Square.class),
    @XmlElement(name="triangle", type=Triangle.class)
)
public List<Shape> getShape() 
    return shape;

另请参阅 @XmlElements 的 javadoc

【讨论】:

【参考方案3】:

AFAIK,你必须写一个XmlAdapter,它知道如何处理形状的编组/解组。

【讨论】:

XmlAdapter 用于将单个 String 值转换为特定类型,对于转换复杂类型没有用处。 @Skaffman:XmlAdapter 可以轻松用于复杂类型。最常见的用例之一是将 java.util.Map 映射到 XML。【参考方案4】:

不,恐怕这不是一个选项,JAXB 没有那么灵活。

我能建议的最好的方法是在Shape 类上放置一个方法,该方法根据属性实例化“正确”类型。客户端代码将调用该工厂方法来获取它。

我能想到的最好了,抱歉。

【讨论】:

我实际上没有想到这一点。当然不理想,但它可能会起作用。我真的希望有某种方式可以放入自定义适配器或其他东西。感谢您的快速回复! @Frothy:JAXB 确实非常有限。有一些替代方案可以提供更大的灵活性,例如 JiBX。 我从未听说过 JiBX,谢谢,今晚我会调查一下。 JAXB 不受限制,记住它是一个规范,EclipseLink MOXy 等实现提供扩展以轻松处理此类用例(请参阅我对这个问题的回答)。【参考方案5】:

有@XmlSeeAlso注解告诉绑定子类。

例如,使用以下类定义:

 class Animal 
 class Dog extends Animal 
 class Cat extends Animal 

用户需要将 JAXBContext 创建为 JAXBContext.newInstance(Dog.class,Cat.class) (由于 Dog 和 Cat 引用了动物,因此将自动拾取。)

XmlSeeAlso 注释将允许您编写:

 @XmlSeeAlso(Dog.class,Cat.class)
 class Animal 
 class Dog extends Animal 
 class Cat extends Animal 

【讨论】:

这是如何解决问题的? (根据元素属性映射不同的类) 我遇到了一个非常相似的问题,这就是我所缺少的,谢谢

以上是关于Java/JAXB:根据属性将 Xml 解组为特定子类的主要内容,如果未能解决你的问题,请参考以下文章

使用 Jaxb API 将 XML 解组为 Java 对象时获取 NullPointerException

如何使用 jaxb 将 xml 字符串解组为 java 对象

在将 XML 文件解组为对象后,如何让 JAXB 调用方法?

在 Android 中将 XML 文件解组为 Java 对象?

如何跳过字段并仅使用JAXB解组该字段的特定成员?

Java JAXB unmarshaller链接异常