Недавно я столкнулся с вопросом о переполнении стека, спрашивающем, может ли 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