Статьи

JAXB и Наследование — Использование XmlAdapter

В предыдущих постах я рассмотрел, как отобразить отношения наследования в JAXB. Это можно сделать по имени элемента (через @XmlElementRef), по атрибуту xsi: type или в EclipseLink MOXy, используя другой атрибут XML (через @ XmlDescriminatorNode / @ XmlDescriminatorValue). В этом посте индикатор типа будет атрибутом / элементом XML, уникальным для этого типа, и мы будем использовать XmlAdapter для реализации этого поведения.

Ввод (input.xml)


В этом примере возможными методами контакта являются
Address и
PhoneNumber . Если
атрибут
street присутствует в
 элементе
contact-method, мы будем создавать экземпляр
объекта
Address , а если
указан атрибут
number, мы
создаем объект PhoneNumber .

<?xml version="1.0" encoding="UTF-8"?>
<customer>
    <contact-method
        number="555-1111"/>
    <contact-method
        street="1 A St"
        city = "Any Town"/>
    <contact-method
        number="555-2222"/>
</customer>
Модель Java

Ниже приведена модель домена, которая будет использоваться в этом примере.

Клиент

package blog.inheritance.xmladapter;
 
import java.util.List;
import javax.xml.bind.annotation.*;
 
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
 
    @XmlElement(name="contact-method")
    private List<ContactMethod> contactMethods;
 
}
ContactMethod

ContactMethod и его подклассы (
Address &
PhoneNumber) будут обрабатываться
XmlAdapter , поэтому единственное требуемое сопоставление — это
@XmlJavaTypeAdapter, чтобы указать реализацию
XmlAdapter .

package blog.inheritance.xmladapter;
 
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 
@XmlJavaTypeAdapter(ContactMethodAdapter.class)
public abstract class ContactMethod {
 
}
Адрес

package blog.inheritance.xmladapter;
 
public class Address extends ContactMethod {
 
    protected String street;
    protected String city;
 
}

Номер телефона

package blog.inheritance.xmladapter;
 
public class PhoneNumber extends ContactMethod {
 
    protected String number;
 
}

XmlAdapter (ContactMethodAdapter)

Класс AdaptedContactMethod создан и представляет объединенные свойства ContactMethod , Address и PhoneNumber . В маршальной операции заполняются только свойства, соответствующие маршалируемому типу. Во время операции unmarshal после того, как AdaptedContactMethod был построен, мы можем посмотреть, какие свойства были заполнены, чтобы определить соответствующий подтип, который должен быть возвращен.

package blog.inheritance.xmladapter;
 
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.adapters.XmlAdapter;
 
public class ContactMethodAdapter extends
    XmlAdapter<ContactMethodAdapter.AdaptedContactMethod, ContactMethod> {
 
    @Override
    public AdaptedContactMethod marshal(ContactMethod contactMethod)
        throws Exception {
        if (null == contactMethod) {
            return null;
        }
        AdaptedContactMethod adaptedContactMethod = new AdaptedContactMethod();
        if (contactMethod instanceof Address) {
            Address address = (Address) contactMethod;
            adaptedContactMethod.street = address.street;
            adaptedContactMethod.city = address.city;
        } else {
            PhoneNumber phoneNumber = (PhoneNumber) contactMethod;
            adaptedContactMethod.number = phoneNumber.number;
        }
        return adaptedContactMethod;
    }
 
    @Override
    public ContactMethod unmarshal(AdaptedContactMethod adaptedContactMethod)
        throws Exception {
        if (null == adaptedContactMethod) {
            return null;
        }
        if (null != adaptedContactMethod.number) {
            PhoneNumber phoneNumber = new PhoneNumber();
            phoneNumber.number = adaptedContactMethod.number;
            return phoneNumber;
        } else {
            Address address = new Address();
            address.street = adaptedContactMethod.street;
            address.city = adaptedContactMethod.city;
            return address;
        }
    }
 
    public static class AdaptedContactMethod {
 
        @XmlAttribute
        public String number;
 
        @XmlAttribute
        public String street;
 
        @XmlAttribute
        public String city;
 
    }
 
}
Демонстрационный код

Следующий демонстрационный код будет использоваться для этого примера. Мы демонтируем входной документ, выводим тип каждого объекта в коллекции, а затем перенаправляем объекты обратно в XML.

package blog.inheritance.xmladapter;
 
import java.io.File;
import javax.xml.bind.*;
 
public class Demo {
 
    public static void main(String[] args) throws Exception  {
        JAXBContext jc = JAXBContext.newInstance(Customer.class);
 
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/blog/inheritance/xmladapter/input.xml");
        Customer customer = (Customer) unmarshaller.unmarshal(xml);
 
        for(ContactMethod contactMethod : customer.getContactMethods()) {
            System.out.println(contactMethod.getClass());
        }
 
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(customer, System.out);
    }
 
}

Выход

Ниже приведен результат запуска демонстрационного кода. Обратите внимание, что каждый из экземпляров ContactMethod в коллекции имеет соответствующий подтип.

class blog.inheritance.xmladapter.PhoneNumber
class blog.inheritance.xmladapter.Address
class blog.inheritance.xmladapter.PhoneNumber
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
    <contact-method number="555-1111"/>
    <contact-method city="Any Town" street="1 A St"/>
    <contact-method number="555-2222"/>
</customer>
Вопросы по форуму

Ниже приведено несколько вариантов использования  стека переполнения,  которые можно реализовать с помощью этого подхода:

 

От http://blog.bdoughan.com/2012/01/jaxb-and-inhertiance-using-xmladapter.html