You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 04:52:33