文章目录
前言
闲时记录下项目上XStream的使用,及特殊要求。
一、XStream简介
一个XML的序列化工具,可以实现 Java对象序列化成XML,或者将XML反序列为Java对象。
java
//简单例子
XStream xstream = new XStream();
String xml = xstream.toXML(myObject); // serialize to XML
Object myObject2 = xstream.fromXML(xml); // deserialize from XML
二、XStream使用
参考:企微依赖包的xml使用
直接上工具类
XStreamInitializer
java
public class XStreamInitializer {
public static ClassLoader classLoader;
public static void setClassLoader(ClassLoader classLoaderInfo) {
classLoader = classLoaderInfo;
}
private static final XppDriver XPP_DRIVER = new XppDriver(new NoNameCoder());
/**
* Gets instance.
*
* @return the instance
*/
public static XStream getInstance() {
XStream xstream = new XStream(new PureJavaReflectionProvider(), XPP_DRIVER) {
// only register the converters we need; other converters generate a private access warning in the console on Java9+...
@Override
protected void setupConverters() {
registerConverter(new NullConverter(), PRIORITY_VERY_HIGH);
registerConverter(new IntConverter(), PRIORITY_NORMAL);
registerConverter(new FloatConverter(), PRIORITY_NORMAL);
registerConverter(new DoubleConverter(), PRIORITY_NORMAL);
registerConverter(new LongConverter(), PRIORITY_NORMAL);
registerConverter(new ShortConverter(), PRIORITY_NORMAL);
registerConverter(new BooleanConverter(), PRIORITY_NORMAL);
registerConverter(new ByteConverter(), PRIORITY_NORMAL);
registerConverter(new StringConverter(), PRIORITY_NORMAL);
registerConverter(new BigDecimalConverter(), PRIORITY_NORMAL);
registerConverter(new DateConverter(), PRIORITY_NORMAL);
registerConverter(new CollectionConverter(getMapper()), PRIORITY_NORMAL);
registerConverter(new ReflectionConverter(getMapper(), getReflectionProvider()), PRIORITY_VERY_LOW);
}
};
xstream.ignoreUnknownElements();
xstream.setMode(XStream.NO_REFERENCES);
xstream.autodetectAnnotations(true);
// setup proper security by limiting which classes can be loaded by XStream
xstream.addPermission(NoTypePermission.NONE);
xstream.addPermission(new WildcardTypePermission(new String[]{
//允许序列化权限的包
"com.example.**"
}));
if (null == classLoader) {
classLoader = Thread.currentThread().getContextClassLoader();
}
xstream.setClassLoader(classLoader);
return xstream;
}
}
XStreamTransformer
java
/**
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public class XStreamTransformer {
private static final Map<Class<?>, XStream> CLASS_2_XSTREAM_INSTANCE = new HashMap<>();
static {
//需要转换的类
registerClass(TestXStream.class);
}
/**
* xml -> pojo.
*/
@SuppressWarnings("unchecked")
public static <T> T fromXml(Class<T> clazz, String xml) {
return (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(xml);
}
@SuppressWarnings("unchecked")
public static <T> T fromXml(Class<T> clazz, InputStream is) {
return (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(is);
}
/**
* pojo -> xml.
*/
public static <T> String toXml(Class<T> clazz, T object) {
return CLASS_2_XSTREAM_INSTANCE.get(clazz).toXML(object);
}
/**
* 注册扩展消息的解析器.
*
* @param clz 类型
* @param xStream xml解析器
*/
public static void register(Class<?> clz, XStream xStream) {
CLASS_2_XSTREAM_INSTANCE.put(clz, xStream);
}
/**
* 注册第三方的该类及其子类.
* 便利第三方类使用 XStreamTransformer进行序列化, 以及支持XStream 1.4.18 以上增加安全许可
* @param clz 要注册的类
*/
public static void registerExtendClass(Class<?> clz){
XStream xstream = XStreamInitializer.getInstance();
Class<?>[] innerClz = getInnerClasses(clz);
xstream.processAnnotations(clz);
xstream.processAnnotations(innerClz);
xstream.allowTypes(new Class[]{clz});
xstream.allowTypes(innerClz);
register(clz, xstream);
}
/**
* 会自动注册该类及其子类.
*
* @param clz 要注册的类
*/
private static void registerClass(Class<?> clz) {
XStream xstream = XStreamInitializer.getInstance();
xstream.processAnnotations(clz);
xstream.processAnnotations(getInnerClasses(clz));
register(clz, xstream);
}
private static Class<?>[] getInnerClasses(Class<?> clz) {
Class<?>[] innerClasses = clz.getClasses();
if (innerClasses == null) {
return null;
}
List<Class<?>> result = new ArrayList<>(Arrays.asList(innerClasses));
for (Class<?> inner : innerClasses) {
Class<?>[] innerClz = getInnerClasses(inner);
if (innerClz == null) {
continue;
}
result.addAll(Arrays.asList(innerClz));
}
return result.toArray(new Class<?>[0]);
}
}
使用:
java
@Data
@XStreamAlias("HEADER")
public class TestXStream {
@XStreamAlias("INVOICE_NUM")
private String invoiceNum;
@XStreamAlias("INVOICE_DESCRIPTION")
private String invoiceDescription;
@XStreamImplicit(itemFieldName = "LINE")
private List<TestXStreamLine> testXStreamLines;
/**
* pojo -> xml
* @return
*/
public String toXml(){
return XStreamTransformer.toXml((Class)this.getClass(), this);
}
}
@Data
@XStreamAlias("LINE")
public class TestXStreamLine {
@XStreamAlias("LINE_DESCRIPTION")
private String lineDescription;
}
测试
java
public static void main(String[] args) {
TestXStream testXStream = new TestXStream();
testXStream.setInvoiceDescription("invoiceDescription");
testXStream.setInvoiceNum("invoiceNum");
TestXStreamLine testXStreamLine1 = new TestXStreamLine();
testXStreamLine1.setLineDescription("lineDescription1");
TestXStreamLine testXStreamLine2 = new TestXStreamLine();
testXStreamLine2.setLineDescription("lineDescription2");
testXStream.setTestXStreamLines(List.of(testXStreamLine1,testXStreamLine2));
String xml = testXStream.toXml();
System.out.println(xml);
//记得更改XStreamInitializer中xstream.addPermission的路径
TestXStream fromXml = XStreamTransformer.fromXml(TestXStream.class, xml);
System.out.println(JSON.toJSONString(fromXml));
}
输出:
xml
<HEADER>
<INVOICE_NUM>invoiceNum</INVOICE_NUM>
<INVOICE_DESCRIPTION>invoiceDescription</INVOICE_DESCRIPTION>
<LINE>
<LINE_DESCRIPTION>lineDescription1</LINE_DESCRIPTION>
</LINE>
<LINE>
<LINE_DESCRIPTION>lineDescription2</LINE_DESCRIPTION>
</LINE>
</HEADER>
{"invoiceDescription":"invoiceDescription","invoiceNum":"invoiceNum","testXStreamLines":[{"lineDescription":"lineDescription1"},{"lineDescription":"lineDescription2"}]}
三.null值保留标签
- 新增转换器继承ReflectionConverter
- 重写AbstractReflectionConverter#doMarshal(Object, HierarchicalStreamWriter, MarshallingContext)
- 注意在使用的时候,记得将转换器注册到非常低的位置:xStream.registerConverter(nullConverter, XStream.PRIORITY_VERY_LOW)
代码如下:
java
/**
* 在xstream中注册这个converter,可以输出值为null的标签
* 注意在使用的时候,记得将转换器注册到非常低的位置:xStream.registerConverter(nullConverter, XStream.PRIORITY_VERY_LOW);
* 重写{@link AbstractReflectionConverter#doMarshal(Object, HierarchicalStreamWriter, MarshallingContext)}
* 使用的是xstream旧版本的重写方式
*/
public class EmptyConverter extends ReflectionConverter {
public EmptyConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
super(mapper, reflectionProvider);
}
public EmptyConverter(Mapper mapper, ReflectionProvider reflectionProvider, Class type) {
super(mapper, reflectionProvider, type);
}
@Override
protected void doMarshal(final Object source, final HierarchicalStreamWriter writer,
final MarshallingContext context) {
final List fields = new ArrayList();
final Map defaultFieldDefinition = new HashMap();
final Class sourceType = source.getClass();
// Attributes might be preferred to child elements ...
reflectionProvider.visitSerializableFields(source, new ReflectionProvider.Visitor() {
final Set writtenAttributes = new HashSet();
@Override
public void visit(String fieldName, Class type, Class definedIn, Object value) {
if (!mapper.shouldSerializeMember(definedIn, fieldName)) {
return;
}
if (!defaultFieldDefinition.containsKey(fieldName)) {
Class lookupType = source.getClass();
// See XSTR-457 and OmitFieldsTest
if (definedIn != sourceType
&& !mapper.shouldSerializeMember(lookupType, fieldName)) {
lookupType = definedIn;
}
defaultFieldDefinition.put(
fieldName, reflectionProvider.getField(lookupType, fieldName));
}
SingleValueConverter converter = mapper.getConverterFromItemType(
fieldName, type, definedIn);
if (converter != null) {
final String attribute = mapper.aliasForAttribute(mapper.serializedMember(
definedIn, fieldName));
if (value != null) {
if (writtenAttributes.contains(fieldName)) {
ConversionException exception =
new ConversionException("Cannot write field as attribute for object, attribute name already in use");
exception.add("field-name", fieldName);
exception.add("object-type", sourceType.getName());
throw exception;
}
final String str = converter.toString(value);
if (str != null) {
writer.addAttribute(attribute, str);
}
}
writtenAttributes.add(fieldName);
} else {
fields.add(new FieldInfo(fieldName, type, definedIn, value));
}
}
});
new Object() {
{
final Map hiddenMappers = new HashMap();
for (Iterator fieldIter = fields.iterator(); fieldIter.hasNext(); ) {
FieldInfo info = (FieldInfo) fieldIter.next();
if (info.value != null) {
final Field defaultField = (Field) defaultFieldDefinition.get(info.fieldName);
Mapper.ImplicitCollectionMapping mapping = mapper
.getImplicitCollectionDefForFieldName(
defaultField.getDeclaringClass() == info.definedIn ? sourceType : info.definedIn,
info.fieldName);
if (mapping != null) {
Set mappings = (Set) hiddenMappers.get(info.fieldName);
if (mappings == null) {
mappings = new HashSet();
mappings.add(mapping);
hiddenMappers.put(info.fieldName, mappings);
} else {
if (!mappings.add(mapping)) {
mapping = null;
}
}
}
if (mapping != null) {
if (context instanceof ReferencingMarshallingContext) {
if (info.value != Collections.EMPTY_LIST
&& info.value != Collections.EMPTY_SET
&& info.value != Collections.EMPTY_MAP) {
ReferencingMarshallingContext refContext = (ReferencingMarshallingContext) context;
refContext.registerImplicit(info.value);
}
}
final boolean isCollection = info.value instanceof Collection;
final boolean isMap = info.value instanceof Map;
final boolean isEntry = isMap && mapping.getKeyFieldName() == null;
final boolean isArray = info.value.getClass().isArray();
for (Iterator iter = isArray
? new ArrayIterator(info.value)
: isCollection ? ((Collection) info.value).iterator() : isEntry
? ((Map) info.value).entrySet().iterator()
: ((Map) info.value).values().iterator(); iter.hasNext(); ) {
Object obj = iter.next();
final String itemName;
final Class itemType;
if (obj == null) {
itemType = Object.class;
itemName = mapper.serializedClass(null);
} else if (isEntry) {
final String entryName = mapping.getItemFieldName() != null
? mapping.getItemFieldName()
: mapper.serializedClass(Map.Entry.class);
Map.Entry entry = (Map.Entry) obj;
ExtendedHierarchicalStreamWriterHelper.startNode(
writer, entryName, entry.getClass());
writeItem(entry.getKey(), context, writer);
writeItem(entry.getValue(), context, writer);
writer.endNode();
continue;
} else if (mapping.getItemFieldName() != null) {
itemType = mapping.getItemType();
itemName = mapping.getItemFieldName();
} else {
itemType = obj.getClass();
itemName = mapper.serializedClass(itemType);
}
writeField(
info.fieldName, itemName, itemType, info.definedIn, obj);
}
} else {
writeField(
info.fieldName, null, info.type, info.definedIn, info.value);
}
}else{
// 处理null值的标签也输出
writeField(info.fieldName,null,info.type,info.definedIn,"");
}
}
}
void writeField(String fieldName, String aliasName, Class fieldType,
Class definedIn, Object newObj) {
Class actualType = newObj != null ? newObj.getClass() : fieldType;
ExtendedHierarchicalStreamWriterHelper.startNode(writer, aliasName != null
? aliasName
: mapper.serializedMember(sourceType, fieldName), actualType);
if (newObj != null) {
Class defaultType = mapper.defaultImplementationOf(fieldType);
if (!actualType.equals(defaultType)) {
String serializedClassName = mapper.serializedClass(actualType);
if (!serializedClassName.equals(mapper.serializedClass(defaultType))) {
String attributeName = mapper.aliasForSystemAttribute("class");
if (attributeName != null) {
writer.addAttribute(attributeName, serializedClassName);
}
}
}
final Field defaultField = (Field) defaultFieldDefinition.get(fieldName);
if (defaultField.getDeclaringClass() != definedIn) {
String attributeName = mapper.aliasForSystemAttribute("defined-in");
if (attributeName != null) {
writer.addAttribute(
attributeName, mapper.serializedClass(definedIn));
}
}
Field field = reflectionProvider.getField(definedIn, fieldName);
marshallField(context, newObj, field);
}
writer.endNode();
}
void writeItem(Object item, MarshallingContext context,
HierarchicalStreamWriter writer) {
if (item == null) {
String name = mapper.serializedClass(null);
ExtendedHierarchicalStreamWriterHelper.startNode(
writer, name, Mapper.Null.class);
writer.endNode();
} else {
String name = mapper.serializedClass(item.getClass());
ExtendedHierarchicalStreamWriterHelper.startNode(
writer, name, item.getClass());
context.convertAnother(item);
writer.endNode();
}
}
};
}
private static class FieldInfo extends FieldLocation {
final Class type;
final Object value;
FieldInfo(final String fieldName, final Class type, final Class definedIn, final Object value) {
super(fieldName, definedIn);
this.type = type;
this.value = value;
}
}
private static class FieldLocation {
final String fieldName;
final Class definedIn;
FieldLocation(final String fieldName, final Class definedIn) {
this.fieldName = fieldName;
this.definedIn = definedIn;
}
@Override
public int hashCode() {
final int prime = 7;
int result = 1;
result = prime * result + (definedIn == null ? 0 : definedIn.getName().hashCode());
result = prime * result + (fieldName == null ? 0 : fieldName.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final EmptyConverter.FieldLocation other = (EmptyConverter.FieldLocation) obj;
if (definedIn != other.definedIn) {
return false;
}
if (fieldName == null) {
if (other.fieldName != null) {
return false;
}
} else if (!fieldName.equals(other.fieldName)) {
return false;
}
return true;
}
}
}
XStreamInitializer中将
registerConverter(new ReflectionConverter(getMapper(), getReflectionProvider()), PRIORITY_VERY_LOW);
替换为
registerConverter(new EmptyConverter(getMapper(), getReflectionProvider()), PRIORITY_VERY_LOW);
四.特殊字符不转义
- 源码参考com.thoughtworks.xstream.io.xml.PrettyPrintWriter
- 重写writeText(String, boolean)
java
public class NoneEscapePrettyPintWriter extends AbstractWriter {
public static int XML_QUIRKS = -1;
public static int XML_1_0 = 0;
public static int XML_1_1 = 1;
private final QuickWriter writer;
private final FastStack elementStack = new FastStack(16);
private final char[] lineIndenter;
private final int mode;
private boolean tagInProgress;
protected int depth;
private boolean readyForNewLine;
private boolean tagIsEmpty;
private String newLine;
private static final char[] NULL = "�".toCharArray();
private static final char[] AMP = "&".toCharArray();
private static final char[] LT = "<".toCharArray();
private static final char[] GT = ">".toCharArray();
private static final char[] CR = "
".toCharArray();
private static final char[] QUOT = """.toCharArray();
private static final char[] APOS = "'".toCharArray();
private static final char[] CLOSE = "</".toCharArray();
private NoneEscapePrettyPintWriter(
Writer writer, int mode, char[] lineIndenter, NameCoder nameCoder,
String newLine) {
super(nameCoder);
this.writer = new QuickWriter(writer);
this.lineIndenter = lineIndenter;
this.newLine = newLine;
this.mode = mode;
if (mode < XML_QUIRKS || mode > XML_1_1) {
throw new IllegalArgumentException("Not a valid XML mode");
}
}
/**
* @since 1.4
*/
public NoneEscapePrettyPintWriter(Writer writer, NameCoder nameCoder) {
this(writer, XML_QUIRKS, new char[]{' ', ' '}, nameCoder, "\n");
}
@Override
public void startNode(String name) {
String escapedName = encodeNode(name);
tagIsEmpty = false;
finishTag();
writer.write('<');
writer.write(escapedName);
elementStack.push(escapedName);
tagInProgress = true;
depth++;
readyForNewLine = true;
tagIsEmpty = true;
}
@Override
public void startNode(String name, Class clazz) {
startNode(name);
}
@Override
public void setValue(String text) {
readyForNewLine = false;
tagIsEmpty = false;
finishTag();
writeText(writer, text);
}
@Override
public void addAttribute(String key, String value) {
writer.write(' ');
writer.write(encodeAttribute(key));
writer.write('=');
writer.write('\"');
writeAttributeValue(writer, value);
writer.write('\"');
}
protected void writeAttributeValue(QuickWriter writer, String text) {
writeText(text, true);
}
protected void writeText(QuickWriter writer, String text) {
writeText(text, false);
}
private void writeText(String text, boolean isAttribute) {
int length = text.length();
for (int i = 0; i < length; i++) {
char c = text.charAt(i);
switch (c) {
case '\0':
if (mode == XML_QUIRKS) {
this.writer.write(NULL);
} else {
throw new StreamException("Invalid character 0x0 in XML stream");
}
break;
case '&':
if (isAttribute)
this.writer.write(AMP);
else
this.writer.write(c);
break;
case '<':
if (isAttribute)
this.writer.write(LT);
else
this.writer.write(c);
break;
case '>':
if (isAttribute)
this.writer.write(GT);
else
this.writer.write(c);
break;
case '"':
if (isAttribute)
this.writer.write(QUOT);
else
this.writer.write(c);
break;
case '\'':
if (isAttribute)
this.writer.write(APOS);
else
this.writer.write(c);
break;
case '\r':
if (isAttribute)
this.writer.write(CR);
else
this.writer.write(c);
break;
case '\t':
case '\n':
if (!isAttribute) {
this.writer.write(c);
break;
}
default:
if (Character.isDefined(c) && !Character.isISOControl(c)) {
if (mode != XML_QUIRKS) {
if (c > '\ud7ff' && c < '\ue000') {
throw new StreamException("Invalid character 0x"
+ Integer.toHexString(c)
+ " in XML stream");
}
}
this.writer.write(c);
} else {
if (mode == XML_1_0) {
if (c < 9
|| c == '\u000b'
|| c == '\u000c'
|| c == '\u000e'
|| (c >= '\u000f' && c <= '\u001f')) {
throw new StreamException("Invalid character 0x"
+ Integer.toHexString(c)
+ " in XML 1.0 stream");
}
}
if (mode != XML_QUIRKS) {
if (c == '\ufffe' || c == '\uffff') {
throw new StreamException("Invalid character 0x"
+ Integer.toHexString(c)
+ " in XML stream");
}
}
this.writer.write("&#x");
this.writer.write(Integer.toHexString(c));
this.writer.write(';');
}
}
}
}
@Override
public void endNode() {
depth--;
if (tagIsEmpty) {
writer.write('/');
readyForNewLine = false;
finishTag();
elementStack.popSilently();
} else {
finishTag();
writer.write(CLOSE);
writer.write((String) elementStack.pop());
writer.write('>');
}
readyForNewLine = true;
if (depth == 0) {
writer.flush();
}
}
private void finishTag() {
if (tagInProgress) {
writer.write('>');
}
tagInProgress = false;
if (readyForNewLine) {
endOfLine();
}
readyForNewLine = false;
tagIsEmpty = false;
}
protected void endOfLine() {
writer.write(getNewLine());
for (int i = 0; i < depth; i++) {
writer.write(lineIndenter);
}
}
@Override
public void flush() {
writer.flush();
}
@Override
public void close() {
writer.close();
}
/**
* Retrieve the line terminator.
* <p>
* This method returns always a line feed, since according the XML specification any parser
* must ignore a carriage return. Overload this method, if you need different behavior.
*
* @return the line terminator
* @since 1.3
*/
protected String getNewLine() {
return newLine;
}
}
XStreamInitializer类中将
private static final XppDriver XPP_DRIVER = new XppDriver(new NoNameCoder());
替换为
private static final XppDriver XPP_DRIVER = new XppDriver(new NoNameCoder()){
@Override
public HierarchicalStreamWriter createWriter(Writer out) {
//重写createWriter
return new NoneEscapePrettyPintWriter(out, getNameCoder());
}
};