Подтвердить что ты не робот

Java/JAXB: Unmarshall Xml для конкретного подкласса на основе атрибута

Можно ли использовать JAXB для unmarshall 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
}

В настоящее время я просто помещаю все атрибуты в один большой класс "Форма", и он меньше идеала.

Я мог бы заставить это работать, если формы были правильно названы элементами xml, но, к сожалению, у меня нет контроля над xml, который я извлекаю.

Спасибо!

4b9b3361

Ответ 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", чтобы просто "ввести":

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 мы упростили настройку, ознакомьтесь со следующей статьей для получения дополнительной информации:

Ответ 2

Аннотация @XmlElements позволяет указать, какой тег соответствует подклассу.

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

Также см. javadoc для @XmlElements

Ответ 3

AFAIK, вам нужно написать XmlAdapter, который знает, как обращаться с маршалом /unmarshalling формы.

Ответ 4

Нет, я боюсь, что не вариант, JAXB не настолько гибкий.

Самое лучшее, что я могу предложить, это то, что вы помещаете метод в класс Shape, который создает экземпляр "правильного" типа на основе атрибута. Клиентский код будет использовать этот метод factory для его получения.

Лучшее, что я могу придумать, извините.

Ответ 5

Существует аннотация @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 {}