JAXB - 具有多个名称和类型的XmlElement

时间:2017-02-17 04:58:07

标签: java xml jaxb

我有以下类层次结构:

@XmlRootElement
public abstract class Animal{}

@XmlRootElement
public class Dog extends Animal{}

@XmlRootElement
public class Cat extends Animal{}

@XmlRootElement
public class Lion extends Animal{}

和一个具有名为animal的属性的类:

@XmlRootElement
public class Owner{
  private Animal animal;
}

我想允许不同的XML Schema如下,并将架构中的Animal Type绑定到animal object

中的Owner class
<Owner>
 <Dog></Dog>
</Owner>

<Owner>
 <Cat></Cat>
</Owner>

<Owner>
 <Lion></Lion>
</Owner>

我找到的解决方案使用XmlElements,可以使用多个XmlElement字段并创建一个集合。但是,就我而言,我不需要一个集合,只需要一个属性。

JAXB是否允许此问题的任何XmlElement多个命名约定? 还有其他注释可以解决这个问题吗?

注意:我在stackoverflow及其周围查看了类似问题的多个答案,但几乎所有问题都创建了一个集合,而不是单个对象。我找到的最接近的答案是:@XmlElement with multiple names

编辑:我认为this解决方案可能会有效。必须测试出来

2 个答案:

答案 0 :(得分:3)

我使用@XmlElements注释使其工作,如下所示:

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;

public class Main {
    public static void main(String[] args) throws JAXBException {
        String xml = "<owner><dog></dog></owner>";
        JAXBContext jaxbContext = JAXBContext.newInstance(Owner.class);
        Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
        Owner owner = (Owner) jaxbUnmarshaller.unmarshal(
                new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));

        System.out.println(owner.getAnimal().getClass());
    }
}

abstract class Animal {}

class Dog extends Animal {}

class Cat extends Animal {}

class Lion extends Animal {}

@XmlRootElement
class Owner {
    @XmlElements({
            @XmlElement(name = "dog", type = Dog.class),
            @XmlElement(name = "cat", type = Cat.class),
            @XmlElement(name = "lion", type = Lion.class)
    })
    private Animal animal;

    public Animal getAnimal() {
        return animal;
    }
}

使用Oracle Java 8 SDK附带的默认JAXB实现,打印出来:

class Dog

答案 1 :(得分:2)

我想提供另一种解决方案。之前的解决方案很好 - 但是你会注意到@XmlElements注释在Owner.class和动物的具体实现之间创建了强大的依赖关系(Dog.class,Cat.class,Lion.class)这可以是沮丧的根源 - 每次添加Animal的新实现时都会重新编译所有者类。 (我们有一个微服务架构和持续交付 - 这种耦合对我们的构建过程并不理想......)

相反 - 考虑这种解耦的解决方案。可以创建和使用新的动物实现 - 无需重新编译Owner类 - 满足Open Closed原则。

从定义Abstract Animal元素的Owner类开始。

package com.bjornloftis.domain;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement(name = "owner")
public class Owner {


    @XmlElement(name = "animal")
    @XmlJavaTypeAdapter(AnimalXmlAdapter.class)
    private Animal animal;

    public Owner() {
    }

    public Owner(Animal animal) {
        this.animal = animal;
    }

    public Animal getAnimal() {
        return animal;
    }

}

现在你需要一个抽象类和一个接口。这对于编组和解组非常重要。

package com.bjornloftis.domain;


import javax.xml.bind.annotation.XmlTransient;

@XmlTransient
public abstract class Animal implements AnimalType{

}

AnimalType接口定义了一种方法,该方法在运行时确保JaxB可以确定应该使用哪个实现来解组XML文档。它由我们的XmlAdapter使用 - 您很快就会看到它。否则 - JAXB将无法在运行时派生实现类。

package com.bjornloftis.domain;


import javax.xml.bind.annotation.XmlAttribute;

public interface AnimalType {

    @XmlAttribute(name = "type")
    String getAnimalType();

}

现在 - 您将拥有动物的包装物 - 以及动物实施本身。这可以与所有者分开编译。在编译时没有耦合。

package com.bjornloftis.domain;

import javax.xml.bind.annotation.*;

@XmlRootElement(name = "animal")
@XmlAccessorType(XmlAccessType.FIELD)
public class DogWrapper extends Animal {

    private Dog dog;

    public DogWrapper(){

    }

    public DogWrapper(Dog dog) {
       dog = dog;
    }

    public Dog getDog() {
        return dog;
    }

    public void setError(Dog dog) {
        this.dog = dog;
    }

    @Override
    @XmlAttribute(name = "type")
    public String getAnimalType(){
        return "dog";
    }

}

动物本身:

package com.bjornloftis.domain;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "dog")
public class Dog {


    @XmlElement(name = "name")
    private String name;


    public Dog() {

    }

}

最后 - 将它们组合在一起 - 您需要实现XmlAdapter - 这将有助于编组和解组。

package com.bjornloftis.domain;


import javax.xml.bind.Binder;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;

import org.w3c.dom.Node;

import com.bjornloftis.registry.PropertyRegistryFactory;

public class AnimalXmlAdapter extends XmlAdapter<Object, Animal> {

    @Override
    public Animal unmarshal(Object elementNSImpl) throws Exception {
        Node node = (Node)elementNSImpl;
        String simplePayloadType =     node.getAttributes().getNamedItem("type").getNodeValue();
        Class<?> clazz =     PropertyRegistryFactory.getInstance().findClassByPropertyName(simplePayloadType);
        JAXBContext jc = JAXBContext.newInstance(clazz);
        Binder<Node> binder = jc.createBinder();
        JAXBElement<?> jaxBElement = binder.unmarshal(node, clazz);
        return (Animal)jaxBElement.getValue();
    }

    @Override
    public Animal marshal(Animal animal) throws Exception {
        return animal;
    }

}

最后 - 我们需要关联类型&#34; dog&#34;使用包装器类DogWrapper.class这是通过我们在运行时在代码中初始化的注册表来完成的,这些代码将对狗进行编组或解组。

package com.bjornloftis.registry;

import com.bjornloftis.registry.PropertyRegistry;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class PropertyRegistryFactory {
    private static final Map<String, Class<?>> DEFAULT_REGISTRY = new ConcurrentHashMap();

    public PropertyRegistryFactory() {
    }

    public static final PropertyRegistry getInstance() {
        return new PropertyRegistry(DEFAULT_REGISTRY);
    }

    public static final void setDefaultRegistry(Map<String, Class<?>> defaultRegistry) {
        DEFAULT_REGISTRY.putAll(defaultRegistry);
    }
}

这些都是从我们的生产代码中提取的 - 并且在某种程度上需要进行消毒以删除专有IP。

如果难以理解 - 请在评论中告诉我 - 我会将其全部捆绑到github上的工作项目中。

同样,理解为一个更复杂的解决方案 - 但必须避免耦合我们的代码。另一个好处是,这也可以与杰克逊的库非常无缝地用于JSON。对于JSON编组和解组 - 我们使用TypeIdResolver有一组类似的注释 - 它提供了类似于JAXB的XmlAdapter的函数。

最终结果是您可以编组和解组以下内容 - 但没有@XmlElements引入的讨厌的编译时间耦合:

<owner>
    <animal type="dog">
        <dog>
            <name>FIDO</name>
        </dog>
    </animal>
</owner>