Java/JAXB: Unmarshall Xml для определенного подкласса на основе атрибута
Можно ли использовать JAXB для демаршаллирования xml в определенный класс Java на основе атрибута xml?
<shapes>
<shape type="square" points="4" square-specific-attribute="foo" />
<shape type="triangle" points="3" triangle-specific-attribute="bar" />
</shapes>
Я хотел бы иметь объекты List of Shape, содержащие треугольник и квадрат, каждый из которых имеет свой собственный атрибут формы. IE:
abstract class Shape {
int points;
//...etc
}
class Square extends Shape {
String square-specific-attribute;
//...etc
}
class Triangle extends Shape {
String triangle-specific-attribute;
//...etc
}
В настоящее время я просто помещаю все атрибуты в один большой класс "Shape", и это далеко не идеально.
Я мог бы заставить это работать, если бы фигуры были правильно названы элементами xml, но, к сожалению, я не могу контролировать XML, который я получаю.
Спасибо!
5 ответов
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");
}
}
Вам необходимо убедиться, что у вас есть файл jaxb.properties с классами модели (Shape, Square и т. Д.) Со следующей записью, определяющей реализацию 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 см.:
РЕДАКТИРОВАТЬ
В EclipseLink 2.2 мы упростили настройку, ознакомьтесь со следующей статьей для получения дополнительной информации:
Аннотация @XmlElements позволяет указать, какой тег соответствует какому подклассу.
@XmlElements({
@XmlElement(name="square", type=Square.class),
@XmlElement(name="triangle", type=Triangle.class)
})
public List<Shape> getShape() {
return shape;
}
Также смотрите javadoc для @XmlElements
AFAIK, вам придется написать XmlAdapter, который знает, как обрабатывать маршал / демаршаллинг Shape.
Нет, я боюсь, что это не вариант, JAXB не настолько гибок.
Лучшее, что я могу предложить, это то, что вы положили метод на Shape
класс, который создает "правильный" тип на основе атрибута. Код клиента будет вызывать этот фабричный метод для его получения.
Лучше всего я могу придумать, извини.
Есть аннотация @XmlSeeAlso, чтобы сказать связывать подклассы.
Например, со следующими определениями классов:
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
Пользователь должен будет создать JAXBContext как JAXBContext.newInstance(Dog.class,Cat.class) (Animal будет автоматически выбран, поскольку Dog и Cat ссылаются на него.)
Аннотация XmlSeeAlso позволит вам написать:
@XmlSeeAlso({Dog.class,Cat.class})
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}