2017-02-17 11 views
0

У меня есть следующие иерархии классов: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 для этой проблемы? Есть ли какая-нибудь другая аннотация, которая могла бы решить эту проблему?

Примечание. Я рассмотрел несколько ответов на похожие вопросы в stackoverflow и вокруг, но почти все они создают коллекцию вместо одного объекта. Самый близкий ответ, который я нашел, следующий: @XmlElement with multiple names

Редактировать: Я думаю, this Решение может работать. Придется проверить его

ответ

1

Я получил его на работу, используя @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 
1

Я предлагаю альтернативное решение. Предыдущее решение прекрасно, но вы заметите, что аннотация @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>