使用java.beans的XMLEncoder序列化XML时出现无限循环问题
解决XMLEncoder序列化双向引用的无限循环问题
在使用java.beans.XMLEncoder序列化Category、Contact和Event类时,由于Contact与Event存在双向引用(Contact持有List<Event>,Event持有List<Contact>),序列化过程会陷入无限循环,无法生成XML文件。
原序列化器代码
public class XMLSerializer { public void encode(List<Category> categories, List<Event> events, List<Contact> contacts) { try (XMLEncoder xmlEncoder = new XMLEncoder(new BufferedOutputStream(new FileOutputStream("data/xml_files/data.xml")))) { xmlEncoder.setPersistenceDelegate(LocalDateTime.class, new LocalDateTimePersistenceDelegate()); xmlEncoder.setPersistenceDelegate(LocalTime.class, new LocalTimePersistenceDelegate()); List<Object> mergedList = new ArrayList<>(); mergedList.addAll(categories); mergedList.addAll(events); mergedList.addAll(contacts); xmlEncoder.writeObject(mergedList); } catch (IOException e) { e.printStackTrace(); } } public class LocalDateTimePersistenceDelegate extends DefaultPersistenceDelegate { @Override protected Expression instantiate(Object oldInstance, Encoder out) { LocalDateTime ldt = (LocalDateTime) oldInstance; return new Expression(ldt, oldInstance.getClass(), "parse", new Object[] { ldt.toString() }); } } public class LocalTimePersistenceDelegate extends DefaultPersistenceDelegate { @Override protected Expression instantiate(Object oldInstance, Encoder out) { LocalTime lt = (LocalTime) oldInstance; return new Expression(lt, oldInstance.getClass(), "parse", new Object[] { lt.toString() }); } } }
原模型类代码
Category类
public class Category implements Comparable<Category> { private int id; private String name; private String colorHex; public Category() {} @Override public String toString() { return name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getColorHex() { return colorHex; } public void setColorHex(String colorHex) { this.colorHex = colorHex; } @Override public int compareTo(Category o) { return this.name.compareTo(o.getName()); } }
Event类
public class Event implements Comparable<Event> { private int id; private String name; private LocalDateTime date; private LocalTime notifyOffset; private String location; private String description; private Category category; private List<Contact> contacts = new ArrayList<Contact>(); public Event() {} @Override public String toString() { String locationStr = location.isEmpty() ? "No location" : location; String descriptionStr = description.isEmpty() ? "No description" : description; String categoryName = category != null ? category.getName() : "No category"; return String.format("%s | %s | %s%n%s%n%s", name, getFormattedDate(), locationStr, descriptionStr, categoryName); } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public LocalDateTime getDate() { return date; } public void setDate(LocalDateTime date) { this.date = date; } public LocalTime getNotifyOffset() { return notifyOffset; } public void setNotifyOffset(LocalTime notifyOffset) { this.notifyOffset = notifyOffset; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Category getCategory() { return category; } public void setCategory(Category category) { this.category = category; } public List<Contact> getContacts() { return Collections.unmodifiableList(contacts); } public void setContacts(List<Contact> contacts) { this.contacts = contacts; for (Contact contact : contacts) { contact.addEvent(this); } } public void addContact(Contact contact) { if (!this.contacts.contains(contact)) { this.contacts.add(contact); contact.addEvent(this); } } public void removeContact(Contact contact) { if (this.contacts.contains(contact)) { this.contacts.remove(contact); contact.removeEvent(this); } } public String getFormattedDate() { return this.date.format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")); } public String getFormattedDateWithOffset() { LocalDateTime offsetDateTime = this.date.minusHours(notifyOffset.getHour()).minusMinutes(notifyOffset.getMinute()); return offsetDateTime.format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")); } public LocalDateTime getDateWithOffset() { LocalDateTime offsetDateTime = this.date.minusHours(notifyOffset.getHour()).minusMinutes(notifyOffset.getMinute()); return offsetDateTime; } @Override public int compareTo(Event o) { return this.date.compareTo(o.getDate()); } }
Contact类
public class Contact implements Comparable<Contact> { private int id; private String firstName; private String lastName; private String phoneNumber; private List<Event> events = new ArrayList<Event>(); public Contact() {} @Override public String toString() { return String.format("%s %s | %s", firstName, lastName, phoneNumber); } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getPhoneNumber() { return phoneNumber; } public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; } public List<Event> getEvents() { return Collections.unmodifiableList(events); } public void setEvents(List<Event> events) { this.events = events; for (Event event : events) { event.addContact(this); } } public void addEvent(Event event) { if (!this.events.contains(event)) { this.events.add(event); event.addContact(this); } } public void removeEvent(Event event) { if (this.events.contains(event)) { this.events.remove(event); event.removeContact(this); } } @Override public int compareTo(Contact o) { return this.firstName.compareTo(o.getFirstName()); } }
解决方案:自定义PersistenceDelegate忽略双向引用字段
XMLEncoder通过PersistenceDelegate控制对象的序列化逻辑,我们可以为Event和Contact指定只序列化必要字段,忽略双向引用的列表,从而避免循环。
修改后的XMLSerializer代码:
public class XMLSerializer { public void encode(List<Category> categories, List<Event> events, List<Contact> contacts) { try (XMLEncoder xmlEncoder = new XMLEncoder(new BufferedOutputStream(new FileOutputStream("data/xml_files/data.xml")))) { xmlEncoder.setPersistenceDelegate(LocalDateTime.class, new LocalDateTimePersistenceDelegate()); xmlEncoder.setPersistenceDelegate(LocalTime.class, new LocalTimePersistenceDelegate()); // 为Event指定序列化字段,忽略contacts列表 xmlEncoder.setPersistenceDelegate(Event.class, new DefaultPersistenceDelegate( new String[]{"id", "name", "date", "notifyOffset", "location", "description", "category"} )); // 为Contact指定序列化字段,忽略events列表 xmlEncoder.setPersistenceDelegate(Contact.class, new DefaultPersistenceDelegate( new String[]{"id", "firstName", "lastName", "phoneNumber"} )); List<Object> mergedList = new ArrayList<>(); mergedList.addAll(categories); mergedList.addAll(events); mergedList.addAll(contacts); xmlEncoder.writeObject(mergedList); } catch (IOException e) { e.printStackTrace(); } } // 原有的LocalDateTime和LocalTime持久化代理保持不变 public class LocalDateTimePersistenceDelegate extends DefaultPersistenceDelegate { @Override protected Expression instantiate(Object oldInstance, Encoder out) { LocalDateTime ldt = (LocalDateTime) oldInstance; return new Expression(ldt, oldInstance.getClass(), "parse", new Object[] { ldt.toString() }); } } public class LocalTimePersistenceDelegate extends DefaultPersistenceDelegate { @Override protected Expression instantiate(Object oldInstance, Encoder out) { LocalTime lt = (LocalTime) oldInstance; return new Expression(lt, oldInstance.getClass(), "parse", new Object[] { lt.toString() }); } } // 反序列化时重建双向引用(示例) public List<Object> decode() { try (XMLDecoder xmlDecoder = new XMLDecoder(new BufferedInputStream(new FileInputStream("data/xml_files/data.xml")))) { List<Object> mergedList = (List<Object>) xmlDecoder.readObject(); List<Event> events = mergedList.stream().filter(obj -> obj instanceof Event).map(Event.class::cast).toList(); List<Contact> contacts = mergedList.stream().filter(obj -> obj instanceof Contact).map(Contact.class::cast).toList(); // 根据业务逻辑重建双向关联,例如从数据库或其他存储中获取关联关系 // 示例:假设你在序列化前已记录Event与Contact的关联,这里重新添加 // for (Event event : events) { // List<Contact> relatedContacts = ...; // 获取该事件关联的联系人 // relatedContacts.forEach(event::addContact); // } return mergedList; } catch (IOException e) { e.printStackTrace(); return null; } } }
原理说明
- 通过
DefaultPersistenceDelegate的构造参数指定序列化时需要包含的字段,未指定的字段(如Event的contacts、Contact的events)会被跳过,避免了循环引用的递归序列化。 - 反序列化时需要手动重建双向关联,因为序列化时没有保存这部分引用,需根据业务逻辑从其他地方恢复(比如预先记录的关联关系)。
内容的提问来源于stack exchange,提问作者KopyKay
相关产品推荐
相关产品推荐

