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



我一直在使用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的这个功能?

默认情况下,当Java Bean的属性仍然具有默认值时,XMLEncoder不序列化这些属性。我也觉得这很奇怪。特别是,我想知道为什么没有像

这样的方法
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工作得很好,并且可以作为自己实现的基础)。

我放弃了XMLEncoder,转而使用XStream(参见http://x-stream.github.io/)。

相关内容

  • 没有找到相关文章

最新更新