Статьи

Смешивание вложенности и ссылок с помощью JAXB XmlAdapter

Недавно я столкнулся с вопросом о переполнении стека, спрашивающем, может ли JAXB маршалировать первое вхождение объекта в качестве содержимого и все последующие вхождения в качестве ссылки. Ниже приведена расширенная версия моего ответа (высоко оценили голоса), демонстрирующая, как этого можно добиться, используя XmlAdapter JAXB.

Input.xml

Ниже приведен документ XML, который я буду использовать для этого примера. Второй элемент «Другой номер телефона» относится к тем же данным, что и элемент «основной номер телефона» , а третий элемент «Другой номер телефона» относится к тем же данным, что и первый «Другой номер телефона». номер « элемент.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
    <primary-phone-number id="B">555-BBBB</primary-phone-number>
    <other-phone-number id="A">555-AAAA</other-phone-number>
    <other-phone-number reference="B"/>
    <other-phone-number reference="A"/>
</customer>

Клиент

Класс Customer поддерживает ссылки на объекты PhoneNumber . На один и тот же экземпляр PhoneNumber можно ссылаться несколько раз.

package package blog.xmladapter.stateful;
 
import java.util.List;
import javax.xml.bind.annotation.*;
 
@XmlRootElement
@XmlType(propOrder={"primaryPhoneNumber", "otherPhoneNumbers"})
public class Customer {
 
    private PhoneNumber primaryPhoneNumber;
    private List<PhoneNumber> otherPhoneNumbers;
 
    @XmlElement(name="primary-phone-number")
    public PhoneNumber getPrimaryPhoneNumber() {
        return primaryPhoneNumber;
    }
 
    public void setPrimaryPhoneNumber(PhoneNumber primaryPhoneNumber) {
        this.primaryPhoneNumber = primaryPhoneNumber;
    }
 
    @XmlElement(name="other-phone-number")
    public List<PhoneNumber> getOtherPhoneNumbers() {
        return otherPhoneNumbers;
    }
 
    public void setOtherPhoneNumbers(List<PhoneNumber> phoneNumbers) {
        this.otherPhoneNumbers = phoneNumbers;
    }
 
}

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

Это класс, который может отображаться либо в самом документе, либо в качестве ссылки. Это будет обработано с использованием XmlAdapter . XMLAdapter конфигурируется с использованием @XmlJavaTypeAdapter аннотации. Поскольку мы указали этот адаптер на уровне типа / класса, он будет применяться ко всем свойствам, ссылающимся на класс PhoneNumber :

package blog.xmladapter.stateful;
 
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlValue;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 
@XmlJavaTypeAdapter(PhoneNumberAdapter.class)
@XmlTransient
public class PhoneNumber {
 
    private String id;
    private String number;
 
    @XmlAttribute
    public String getId() {
        return id;
    }
 
    public void setId(String id) {
        this.id = id;
    }
 
    @XmlValue
    public String getNumber() {
        return number;
    }
 
    public void setNumber(String number) {
        this.number = number;
    }
 
    @Override
    public boolean equals(Object object) {
        if(null == object || object.getClass() != this.getClass()) {
            return false;
        }
        PhoneNumber test = (PhoneNumber) object;
        if(!equals(id, test.getId())) {
            return false;
        }
        return equals(number, test.getNumber());
    }
 
    private boolean equals(String control, String test) {
        if(null == control) {
            return null == test;
        } else {
            return control.equals(test);
        }
    }
 
    @Override
    public int hashCode() {
        return id.hashCode();
    }
 
}

PhoneNumberAdapter

Ниже приведена реализация XmlAdapter . Обратите внимание, что мы должны поддерживать, если объект PhoneNumber был замечен ранее. Если это так, мы заполняем только часть id объекта AdaptedPhoneNumber .

package blog.xmladapter.stateful;
 
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.adapters.XmlAdapter;
 
public class PhoneNumberAdapter extends XmlAdapter<PhoneNumberAdapter.AdaptedPhoneNumber, PhoneNumber>{
 
    private List<PhoneNumber> phoneNumberList = new ArrayList<PhoneNumber>();
    private Map<String, PhoneNumber> phoneNumberMap = new HashMap<String, PhoneNumber>();
 
    public static class AdaptedPhoneNumber extends PhoneNumber {
        @XmlAttribute
        public String reference;
    }
 
    @Override
    public AdaptedPhoneNumber marshal(PhoneNumber phoneNumber) throws Exception {
        AdaptedPhoneNumber adaptedPhoneNumber = new AdaptedPhoneNumber();
        if(phoneNumberList.contains(phoneNumber)) {
            adaptedPhoneNumber.reference = phoneNumber.getId();
        } else {
            adaptedPhoneNumber.setId(phoneNumber.getId());
            adaptedPhoneNumber.setNumber(phoneNumber.getNumber());
            phoneNumberList.add(phoneNumber);
        }
        return adaptedPhoneNumber;
    }
 
    @Override
    public PhoneNumber unmarshal(AdaptedPhoneNumber adaptedPhoneNumber) throws Exception {
        PhoneNumber phoneNumber = phoneNumberMap.get(adaptedPhoneNumber.reference);
        if(null == phoneNumber) {
            phoneNumber = new PhoneNumber();
            phoneNumber.setId(adaptedPhoneNumber.getId());
            phoneNumber.setNumber(adaptedPhoneNumber.getNumber());
            phoneNumberMap.put(phoneNumber.getId(), phoneNumber);
        }
        return phoneNumber;
    }
 
}

демонстрация

Чтобы гарантировать, что один и тот же экземпляр XmlAdapter используется для всех операций маршала и демаршала, мы должны специально установить экземпляр XmlAdapter как для Marshaller, так и для Unmarshaller :

package blog.xmladapter.stateful;
 
import java.io.File;
 
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
 
public class Demo {
 
    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Customer.class);
 
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        unmarshaller.setAdapter(new PhoneNumberAdapter());
        File xml = new File("src/blog/xmladapter/stateful/input.xml");
        Customer customer = (Customer) unmarshaller.unmarshal(xml);
 
        PhoneNumber primaryPN = customer.getPrimaryPhoneNumber();
        PhoneNumber otherPN1 = customer.getOtherPhoneNumbers().get(0);
        PhoneNumber otherPN2 = customer.getOtherPhoneNumbers().get(1);
        PhoneNumber otherPN3 = customer.getOtherPhoneNumbers().get(2);
        System.out.println(primaryPN == otherPN2);
        System.out.println(otherPN1 == otherPN3);
 
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setAdapter(new PhoneNumberAdapter());
        marshaller.marshal(customer, System.out);
    }
 
}

Выход

Ниже приведен результат запуска демонстрационного кода:

true
true
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
    <primary-phone-number id="B">555-BBBB</primary-phone-number>
    <other-phone-number id="A">555-AAAA</other-phone-number>
    <other-phone-number reference="B"/>
    <other-phone-number reference="A"/>
</customer>

 

С http://blog.bdoughan.com/2011/09/mixing-nesting-and-references-with.html