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

Java 具有多个名称和类型的JAXB-XmlElement,java,xml,jaxb,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 pu

我有以下类层次结构:

@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模式,如下所示,并将模式中的动物类型绑定到
所有者类中的
动物对象

<Owner>
 <Dog></Dog>
</Owner>

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

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

我发现的解决方案使用
xmlements
,它可以获取多个
xmlements
字段并创建一个集合。但是,在我的例子中,我不需要集合,只需要一个属性

JAXB是否允许任何XmlElement多重命名约定来解决此问题? 是否有其他注释可以解决此问题

注意:我在stackoverflow和其他文章中看到了类似问题的多个答案,但几乎所有答案都创建了一个集合,而不是单个对象。我找到的最接近的答案是:


编辑:我认为这个解决方案可能有效。必须对其进行测试

我使用注释使其正常工作,如下所示:

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;
    }
}

我想提供另一种解决方案。前面的解决方案很好,但您会注意到@XmlElements注释在Owner.class和动物的具体实现(Dog.class、Cat.class、Lion.class)之间创建了强大的依赖关系这可能是令人沮丧的原因——导致您在每次添加Animal的新实现时重新编译Owner类。(我们有一个微服务架构和连续交付——这种耦合对于我们的构建过程来说并不理想……)

——考虑这个解耦的解决方案。可以创建和使用新的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{

}
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;
    }

}
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.annotation.XmlTransient;

@XmlTransient
public abstract class Animal implements AnimalType{

}
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;
    }

}
package com.bjornloftis.domain;
导入javax.xml.bind.Binder;
导入javax.xml.bind.JAXBContext;
导入javax.xml.bind.JAXBElement;
导入javax.xml.bind.annotation.adapters.XmlAdapter;
导入org.w3c.dom.Node;
导入com.bjornloftis.registry.PropertyRegistryFactory;
公共类AnimalXmlAdapter扩展了XmlAdapter{
@凌驾
公共动物解组(Object ElementsImpl)引发异常{
Node Node=(Node)elementNSImpl;
字符串simplePayloadType=node.getAttributes().getNamedItem(“类型”).getNodeValue();
类clazz=PropertyRegistryFactory.getInstance().findClassByPropertyName(simplePayloadType);
JAXBContext jc=JAXBContext.newInstance(clazz);
Binder Binder=jc.createBinder();
JAXBElement=binder.unmarshal(节点,clazz);
return(Animal)jaxBElement.getValue();
}
@凌驾
公共动物执法官(动物)抛出异常{
返回动物;
}
}
最后,我们需要将类型“dog”与包装类DogWrapper.class相关联,这是通过我们在运行时在代码中初始化的注册表来完成的,该代码将对dogs进行封送或解封

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);
    }
}
package com.bjornloftis.registry;
导入com.bjornloftis.registry.PropertyRegistry;
导入java.util.Map;
导入java.util.concurrent.ConcurrentHashMap;
公共类PropertyRegistryFactory{
私有静态最终映射