如何禁用XMLEncoder的冗余消除功能以确保POJO完全序列化?

时间:2014-05-27 13:30:18

标签: java xml-serialization xmlencoder

我一直在使用XMLEncoder将POJO转换为XML并再次转换回来。除了存储POJO之外,其他应用程序还将使用某些XML输出来生成其他形式的数据,例如报告。

到目前为止,这一点运作良好。即使POJO发展,也没有问题。

最近我意识到并非所有的值都是实际输出的。未写入默认值未被更改的属性。这对我来说是一个问题。

来自Javadoc:

"The XMLEncoder class uses a redundancy elimination algorithm internally so that the default values of a Bean's properties are not written to the stream"

对我来说,重要的是bean以其整体输出 - 包括所有默认值。

有没有办法禁用XMLEncoder的此功能?

2 个答案:

答案 0 :(得分:1)

默认情况下,XMLEncoder在Java Bean仍具有默认值时不会序列化它们的属性。我也发现这很奇怪。特别是,我想知道为什么没有像

这样的方法
xmlEncoder.setRedundancyEliminationEnabled(false);

但是,有一个选项可以在没有第三方库的情况下解决这个问题:为了完全序列化Java Bean,即使其属性仍然具有默认值,也可以使用自己的PersistenceDelegate

以下是此类PersistenceDelegate的示例实现:

import java.beans.BeanInfo;
import java.beans.Encoder;
import java.beans.Expression;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PersistenceDelegate;
import java.beans.PropertyDescriptor;
import java.beans.Statement;
import java.lang.reflect.Method;

/**
 * Implementation of a PersistenceDelegate that serializes all properties
 * of a Java Bean, even if they still have their default values.
 */
class FullPersistenceDelegate extends PersistenceDelegate
{
    @Override
    protected Expression instantiate(Object oldInstance, Encoder out)
    {
        return new Expression(
            oldInstance, oldInstance.getClass(), "new", null);
    }

    @Override
    protected void initialize(Class<?> type, Object oldInstance,
        Object newInstance, Encoder out)
    {
        super.initialize(type, oldInstance, newInstance, out);
        if (oldInstance.getClass() == type)
        {
            initializeProperties(type, oldInstance, out);
        }
    }

    /**
     * Write all statements to initialize the properties of the given 
     * Java Bean Type, based on the given instance, using the given
     * encoder
     *   
     * @param type The Java Bean Type
     * @param oldInstance The base instance
     * @param out The encoder
     */
    private void initializeProperties(
        Class<?> type, Object oldInstance, Encoder out)
    {
        BeanInfo info = null;
        try
        {
            info = Introspector.getBeanInfo(type);
        }
        catch (IntrospectionException ie)
        {
            out.getExceptionListener().exceptionThrown(ie);
            return;
        }
        PropertyDescriptor[] pds = info.getPropertyDescriptors();
        for (int i = 0; i < pds.length; ++i)
        {
            try
            {
                initializeProperty(type, pds[i], oldInstance, out);
            }
            catch (Exception e)
            {
                out.getExceptionListener().exceptionThrown(e);
            }
        }
    }

    /**
     * Write the statement to initialize the specified property of the given 
     * Java Bean Type, based on the given instance, using the given
     * encoder
     *   
     * @param type The Java Bean Type
     * @param pd The property descriptor
     * @param oldInstance The base instance
     * @param out The encoder
     * @throws Exception If the value can not be obtained
     */
    private void initializeProperty(
        Class<?> type, PropertyDescriptor pd, Object oldInstance, Encoder out) 
        throws Exception
    {
        Method getter = pd.getReadMethod();
        Method setter = pd.getWriteMethod();
        if (getter != null && setter != null)
        {
            Expression oldGetExpression =
                new Expression(oldInstance, getter.getName(), new Object[] {});
            Object oldValue = oldGetExpression.getValue();
            Statement setStatement =
                new Statement(oldInstance, setter.getName(), 
                    new Object[] { oldValue });
            out.writeStatement(setStatement);
        }
    }
}

使用它是直截了当的:只需要为编码器注册:

encoder.setPersistenceDelegate(ExampleBean.class,
    new FullPersistenceDelegate());

就是这样。

这是一个用法示例。给出一个简单的示例bean类......

public class ExampleBean
{
    enum ExampleBeanEnum
    {
        DEFAULT_ENUM_VALUE,
        MODIFIED_ENUM_VALUE,
    }
    private String stringValue;
    private int intValue;
    private ExampleBeanEnum enumValue;

    public ExampleBean()
    {
        stringValue = "Default String Value";
        intValue = 123;
        enumValue = ExampleBeanEnum.DEFAULT_ENUM_VALUE;
    }

    public String getStringValue()
    {
        return stringValue;
    }
    public void setStringValue(String stringValue)
    {
        this.stringValue = stringValue;
    }
    public int getIntValue()
    {
        return intValue;
    }
    public void setIntValue(int intValue)
    {
        this.intValue = intValue;
    }
    public ExampleBeanEnum getEnumValue()
    {
        return enumValue;
    }
    public void setEnumValue(ExampleBeanEnum enumValue)
    {
        this.enumValue = enumValue;
    }
}

和测试类......

import java.beans.ExceptionListener;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

public class FullPersistenceDelegateExample
{
    public static void main(String[] args)
    {
        // Create an XMLEncoder that writes to a byte array
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        XMLEncoder encoder = new XMLEncoder(stream);
        encoder.setExceptionListener(new ExceptionListener()
        {
            @Override
            public void exceptionThrown(Exception e)
            {
                e.printStackTrace();
            }
        });

        // Set the FullPersistenceDelegate for the ExampleBean class
        encoder.setPersistenceDelegate(ExampleBean.class,
            new FullPersistenceDelegate());

        // Write an instance of the ExampleBean, where only one property
        // was modified, as compared to the default value.
        // The default persistence delegate would cause only the modified
        // property to be written. However, the FullPersistenceDelegate
        // will cause ALL properties to be written
        ExampleBean oldExampleBean = new ExampleBean();
        oldExampleBean.setIntValue(234);

        encoder.writeObject(oldExampleBean);
        encoder.flush();
        encoder.close();

        // Print the encoding result
        System.out.println(stream);

        // Read the instance back and print its properties
        XMLDecoder d =
            new XMLDecoder(new ByteArrayInputStream(stream.toByteArray()));
        ExampleBean newExampleBean = (ExampleBean) d.readObject();
        System.out.println("stringValue: " + newExampleBean.getStringValue());
        System.out.println("intValue   : " + newExampleBean.getIntValue());
        System.out.println("enumValue  : " + newExampleBean.getEnumValue());
    }
}

可以看到输出包含所有字段的属性值,即使它们仍然具有默认值:

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.7.0_65" class="java.beans.XMLDecoder">
 <object class="ExampleBean">
  <void property="enumValue">
   <object class="java.lang.Enum" method="valueOf">
    <class>ExampleBean$ExampleBeanEnum</class>
    <string>DEFAULT_ENUM_VALUE</string>
   </object>
  </void>
  <void property="intValue">
   <int>234</int>
  </void>
  <void property="stringValue">
   <string>Default String Value</string>
  </void>
 </object>
</java>

(请注意,这尚未针对更复杂的场景进行测试,但它适用于简单的bean,可以作为自己实现的基础)。

答案 1 :(得分:0)

我放弃了XMLEncoder并切换到XStream(参见http://x-stream.github.io/)。

到目前为止效果很好......