我有以下类层次结构:
@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解决方案可能会有效。必须测试出来
答案 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>