我想提供一个替代解决方案。以前的解决方案很好 - 但是你会注意到@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>