我一直在使用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/)。