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 调用方法?