JAXB - XmlElement的多个名称和类型
我有以下的类层次结构:JAXB - XmlElement的多个名称和类型
@XmlRootElement
public abstract class Animal{}
@XmlRootElement
public class Dog extends Animal{}
@XmlRootElement
public class Cat extends Animal{}
@XmlRootElement
public class Lion extends Animal{}
,并具有一个属性命名为动物类:
@XmlRootElement
public class Owner{
private Animal animal;
}
我想允许不同的XML模式如下所示,并将模式中的动物类型绑定到animal object
中Owner class
<Owner>
<Dog></Dog>
</Owner>
<Owner>
<Cat></Cat>
</Owner>
<Owner>
<Lion></Lion>
</Owner>
我找到的解决方案使用XmlElements
,可以使用多个XmlElement
字段并创建一个集合。但是,就我而言,我不需要集合,而只需要一个属性。
JAXB是否允许针对此问题的任何XmlElement多重命名约定? 有没有其他注解可以解决这个问题?
注意:我已经看到了对*和类似问题的多个答案,但几乎所有人都创建了一个集合而不是单个对象。我发现的最接近的答案是这样的:@XmlElement with multiple names
编辑:我认为this解决方案可能工作。要测试它
我得到了它使用@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;
}
}
使用默认JAXB实现附带了Oracle的Java 8 SDK,这样打印出:
class Dog
我想提供一个替代解决方案。以前的解决方案很好 - 但是你会注意到@XmlElements注解在Owner.class和动物的具体实现(Dog.class,Cat.class,Lion.class)之间创建了很强的依赖关系。沮丧 - 每次添加Animal的新实现时,都会导致您重新编译您的Owner类。 (我们有一个微服务架构和连续交付 - 而这种联结对我们的构建过程来说并不理想......)
相反 - 考虑这个解耦解决方案。可以创建和使用新的动物实现 - 无需重新编译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;
}
}
最后 - 我们需要的类型“狗”与包装类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>