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