Я предлагаю альтернативное решение. Предыдущее решение прекрасно, но вы заметите, что аннотация @XmlElements создает сильные зависимости между Owner.class и конкретными реализациями ваших животных (Dog.class, Cat.class, Lion.class). Это может быть источником разочарование - вы должны перекомпилировать свой класс Owner каждый раз, когда вы добавляете новую реализацию Animal. (У нас есть микросервисная архитектура и непрерывная доставка - и такие соединения не были идеальными для нашего процесса сборки ...)
Вместо этого рассмотрите это развязанное решение. Новые реализации животных могут быть созданы и использованы - без повторной компиляции класса Owner, удовлетворяющего принципу Open Closed.
Начать с класса Owner, который определяет элемент Abstract Animal.
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, который предоставляет функцию, аналогичную XmlAdapter для JAXB.
Конечный результата является то, что вы можете маршал и маршализация следующее - но без временной связи противных компиляций, что @XmlElements вводит:
<owner>
<animal type="dog">
<dog>
<name>FIDO</name>
</dog>
</animal>
</owner>