虽然现在开发中基本上都是使用JSON作为数据传输的格式,常用的JSON框架比如FastJson, FastJson2, Jackson, Gson等等,但是有时候我们也会用到xml格式用来传输数据,尤其做政府项目的时候很多数据格式都是xml, 所以今天就推荐一个xml的解析工具:XStream。它的功能也比较丰富,具体可以看下官网,它还可以用来解析JSON,只不过很少使用它。
1. XStream 的基本使用
com.thoughtworks.xstream
xstream
1.4.20
尽量使用高版本,因为低版本中有很多漏洞,目前最高版本是1.4.20。
1.1 Bean 转 XML
/**
* 默认使用全路径作为标签 ...
* @XStreamAlias("Person") 可以指定标签的别名
*/
@XStreamAlias("Person")
@Data
public class Person {
/**
* 默认使用属性名作为标签 ...
*/
private String idCard;
private String name;
/**
* 自定义类作为属性,包含了name和price属性
*/
@XStreamAlias("CAR_INFO")
private Car car;
/**
* 集合属性
*/
@XStreamAlias("foodList")
private List foodList;
}
---------------------------------------------
@AllArgsConstructor
@Data
public class Car {
//默认用属性作为标签名,使用 @XStreamAlias("PRICE") 注解可以指定别名
//@XStreamAlias("PRICE")
private String price;
private String name;
}
--------------------------------------------
@AllArgsConstructor
@Data
public class Food {
private String id;
private String name;
}
public class XmlTest {
public static void main(String[] args) {
Food f1 = new Food("F0001", "烤冷面");
Food f2 = new Food("F0002", "烧烤");
Car car = new Car("宝马", "50万");
Person person = new Person("1000001", "张三", car, Arrays.asList(f1, f2));
String xml = beanToXml(person);
System.out.println(xml);
}
public static String beanToXml(Object obj) {
XStream xstream = new XStream(new DomDriver("UTF-8"));
//不输出class信息,不然标签中会包含class属性
xstream.aliasSystemAttribute(null, "class");
//支持注解,不然使用的 @XStreamAlias() 注解不会生效,不生效并不会报错,可以测试看下
xstream.autodetectAnnotations(true);
return xstream.toXML(obj);
}
}
1000001
张三
宝马
50万
F0001
烤冷面
F0002
烧烤
用起来还是很简单的
1.2 XML 转 Bean
1000001
张三
宝马
50万
F0001
烤冷面
F0002
烧烤
23
public class XmlTest {
public static void main(String[] args) {
String xml=
"
1000001
张三
宝马
50万
F0001
烤冷面
F0002
烧烤
23
";
Person p = strToBean(xml, Person.class);
System.out.println(p);
}
@SuppressWarnings("unchecked")
public static T strToBean(String xmlStr, Class cls) {
T targetObject = null;
try {
XStream xStream = new XStream();
/**
* 高版本中为了解决安全漏洞,增加了白名单的机制,这里
* 需要设置权限,不然会报错
*/
xStream.addPermission(AnyTypePermission.ANY);
xStream.processAnnotations(cls);
xStream.autodetectAnnotations(true);
targetObject = (T) xStream.fromXML(xmlStr);
} catch (Exception e) {
e.printStackTrace();
}
return targetObject;
}
}
运行报错:
意思很明显,就是找不到
Person_Info
这个类,这是因为xml结构中的标签是, 但是javaBean中我通过@XStreamAlias("Person")
注解制定的是,所以他俩要保持一致,要将JavaBean中的相关标签与xml标签保持一致。所以将JavaBean调整为:
/**
* 默认使用全路径作为标签 ...
* @XStreamAlias("Person_Info") 可以指定标签的别名
*/
@XStreamAlias("Person_Info")
@Data
public class Person {
/**
* 默认使用属性名作为标签 ...
*/
private String idCard;
private String name;
/**
* 默认使用全路径作为标签
*/
@XStreamAlias("CAR_INFO")
private Car car;
/**
* 默认使用属性名作为标签,集合也一样
*/
private List foodList;
}
运行测试类,依然报错:
从报错信息上很明显的知道,因为xml中有一个
标签,但是
Person
类中没有这个属性,所以导致了报错,此时有两种办法可以解决。第一种就是给Person
类中加上age
属性,另外一种就是如下:
//忽略掉位置的属性
xStream.ignoreUnknownElements();
2. 存在的问题
2.1 序列化时,"_" 会变成 "__"
@AllArgsConstructor
@XStreamAlias("S_Student")
@Data
public class Student {
private String name;
//main
public static void main(String[] args) {
Student student = new Student("李四");
String xml = beanToXml(student);
System.out.println(xml);
}
public static String beanToXml(Object obj) {
String xmlString = "";
try {
XStream xstream = new XStream(new DomDriver("UTF-8"));
//支持注解,不然使用的 @XStreamAlias() 注解不会生效
xstream.autodetectAnnotations(true);
// 不输出class信息,不然标签中会包含class属性
xstream.aliasSystemAttribute(null, "class");
xmlString = xstream.toXML(obj);
} catch (Exception e) {
e.printStackTrace();
}
return xmlString;
}
}
解决办法:
在创建XStream对象时,指定NoNameCoder 对象
XStream xstream = new XStream(new DomDriver("UTF-8", new NoNameCoder()));
2.2 反序列化时XML的标签节点与Bean的属性不匹配时报错
前面也有提到过了,如果xml中存在的标签节点在JavaBean中不存在,在XML转Bean的时候将会报错,所以创建XStream对象时可以指定忽略未知属性
XStream xStream = new XStream(new DomDriver("UTF-8", new NoNameCoder()));
//忽略未知的属性
xStream.ignoreUnknownElements();
2.3 null值序列化时不会显示标签
参考文档
2.4 自定义转换器完成时间类型的转换
在创建XStream
对象时,其内部会注册大量的转换器,常见类型的转换器基本上都包含了,比如:
看个例子来验证下:
1001
34.06
23.67
3
2023-07-16 17:01:31.285 UTC
false
JavaBean:
@XStreamAlias("My_Order")
@Data
@AllArgsConstructor
public class Order {
//String类型
private String orderId;
//int/Integer 类型
private int count;
//Double 类型
private Double price;
//BigDecimal类型
private BigDecimal money;
//Byte 类型
private Byte flag;
//日期类型
private Date buyDate;
//Boolean 类型
private Boolean isSuccess;
}
测试类:
public class XmlTest {
public static void main(String[] args) {
String orderXml =
"n" +
" 1001n" +
" 590n" +
" 34.06n" +
" 23.67n" +
" 3n" +
" 2023-07-16 17:01:31.285 UTCn" +
" falsen" +
"";
Order o = strToBean(orderXml, Order.class);
System.out.println("order映射结果 = " + JSON.toJSONString(o, JSONWriter.Feature.PrettyFormat));
}
@SuppressWarnings("unchecked")
public static T strToBean(String xmlStr, Class cls) {
T targetObject = null;
try {
XStream xStream = new XStream(new DomDriver("UTF-8", new NoNameCoder()));
xStream.addPermission(AnyTypePermission.ANY);
xStream.processAnnotations(cls);
xStream.autodetectAnnotations(true);
//忽略掉未知的属性
xStream.ignoreUnknownElements();
targetObject = (T) xStream.fromXML(xmlStr);
} catch (Exception e) {
e.printStackTrace();
}
return targetObject;
}
}
验证结果:
不难发现,基本上所有的类型都可以匹配上。但是日期Date要稍微注意下,如果我换成常见的格式
2023-07-16 17:01:31
就会报错:
不难发现,他默认只能解析它列出的这9种格式的Date类型,并没有我xml中写的 yyyy-MM-dd HH:mm:ss
类型,如果就想支持解析这种格式的Date,可以继承它的com.thoughtworks.xstream.converters.basic.DateConverter
类 或者自定义类型转换器。我这里就不探究了,因为现在使用LocalDate
或者 LocalDateTime
格式的比较多,所以我通过LocalDateTime
来看下如何自定义转换器。
XStream 也提供了
com.thoughtworks.xstream.converters.time.LocalDateTimeConverter
转换器,但是默认也不会解析yyyy-MM-dd HH:mm:ss
, 所以这里就需要我们自定义转换器了。
1. JavaBean:
@XStreamAlias("My_Order")
@Data
@AllArgsConstructor
public class Order {
//String类型
private String orderId;
//int/Integer 类型
private int count;
//Double 类型
private Double price;
//BigDecimal类型
private BigDecimal money;
//Byte 类型
private Byte flag;
/**
* 修改为 LocalDateTime 类型
*/
private LocalDateTime buyDate;
//Boolean 类型
private Boolean isSuccess;
}
2. XML:
1001
34.06
23.67
3
2023-07-16 17:01:31.285 UTC
true
3. 自定义LocalDateTime类型转换器:
@Slf4j
public class LocalDateTimeConverter implements SingleValueConverter {
private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public String toString(Object obj) {
try {
if (!Objects.isNull(obj) && (obj instanceof LocalDateTime)) {
return dateTimeFormatter.format((TemporalAccessor) obj);
}
} catch (Exception e) {
log.error("LocalDateTime 格式转换错误,args: {}", obj);
}
return null;
}
@Override
public Object fromString(String str) {
try {
if (StringUtils.isNotBlank(str)) {
return LocalDateTime.parse(str, dateTimeFormatter);
}
} catch (Exception e) {
log.error("字符串格式的日期转换LocalDateTime发生错误, args: {}", str);
}
return null;
}
@Override
public boolean canConvert(Class type) {
return type == LocalDateTime.class;
}
}
4. 解析工具类:
public class XmlToStrTest {
public static void main(String[] args) {
String orderXml = "n" +
" 1001n" +
" 590n" +
" 34.06n" +
" 23.67n" +
" 3n" +
" 2023-07-16 17:01:31n" +
" truen" +
"";
Order o = strToBean(orderXml, Order.class);
System.out.println("order映射结果 = " + JSON.toJSONString(o, JSONWriter.Feature.PrettyFormat));
}
public static T strToBean(String xmlStr, Class cls) {
T targetObject = null;
try {
XStream xStream = new XStream(new DomDriver("UTF-8", new NoNameCoder()));
xStream.addPermission(AnyTypePermission.ANY);
xStream.processAnnotations(cls);
xStream.autodetectAnnotations(true);
/**
* 自定义类型转换器,优先级高一点,不然还是会使用框架的转换器
*
*/
xStream.registerConverter(new LocalDateTimeConverter(), XStream.PRIORITY_VERY_HIGH);
targetObject = (T) xStream.fromXML(xmlStr);
} catch (Exception e) {
e.printStackTrace();
}
return targetObject;
}
}
5. 解析结果:
自定义的LocalDateTime类型转换器已经生效了。
注意:上面这种注册转换器的方式是全局生效的,如果只是不想全局生效,只想作用在某一个属性上,可以使用 【@XStreamConverter】 注解:
@XStreamAlias("My_Order")
@Data
@AllArgsConstructor
public class Order {
//String类型
private String orderId;
//.....其他属性
/**
* @XStreamConverter(value = LocalDateTimeConverter.class)
* 只针对当前属性生效
*/
@XStreamConverter(value = LocalDateTimeConverter.class)
private LocalDateTime buyDate;
//.....其他属性
}
3. 常用注解
注解 | 说明 |
---|---|
@XStreamAlias |
这个是最常用的注解,用来指定匹配标签节点的名称,如果不指定默认为全类名或者属性名 |
@XStreamAsAttribute |
标注该注解的属性将成为标签节点的属性,不再是一个单独的节点 |
@XStreamConverter |
指定类型转换器,只对当前属性或当前类生效 |
@XStreamImplicit |
用于定义集合,数组,Map字段的序列化方式 |
@XStreamOmitField |
用于标记某个字段不参与序列化和反序列化 |
通过一个例子来演示看下:
@AllArgsConstructor
@Data
@XStreamAlias("A_TEACHER")
public class Teacher {
/**
* 将name作为根节点A_TEACHER的属性
*/
@XStreamAsAttribute
private String name;
/**
* 序列化和反序列时忽略
*/
@XStreamOmitField
private int age;
private Double salary;
private String color;
/**
* 指定元素的字段名
* 如果不指定的话,默认使用类型作为标签名 ...
*/
@XStreamImplicit(itemFieldName = "likes")
private List likes;
/**
* 注意看 xml
*/
@XStreamImplicit(itemFieldName = "FOOD_LIST")
private List foods;
}
序列化后的xml:
35000.0
黑色
读书
讲课
拍视频
F0001
炸酱面
F0002
卤煮
F0003
烧烤
F0004
烤饼
4.开箱即用的工具类
public class XmlParseUtils {
/**
* 反序列化
*/
@SuppressWarnings("unchecked")
public static T strToBean(String xmlStr, Class cls) {
T targetObject = null;
try {
XStream xstream = new XStream(new DomDriver("UTF-8", new NoNameCoder()));
xstream.ignoreUnknownElements();
xstream.addPermission(AnyTypePermission.ANY);
//和上面一样,也是用来解决高版本中安全问题的
//xstream.allowTypesByWildcard(new String[]{"com.qiuguan.**"});
xstream.processAnnotations(cls);
xstream.autodetectAnnotations(true);
xstream.registerConverter(new LocalDateTimeConverter(), XStream.PRIORITY_VERY_HIGH);
//框架自带的Boolean类型转换器,可以将xml中的 yes 转成true, 将 no 转成false, 默认是将 "true"字符串转成boolean类型的true
xstream.registerConverter(new BooleanConverter("yes", "no", false));
targetObject = (T) xstream.fromXML(xmlStr);
} catch (Exception e) {
e.printStackTrace();
}
return targetObject;
}
/**
* 序列化
*/
public static String beanToXml(Object obj) {
String xmlString = "";
try {
XStream xstream = new XStream(new DomDriver("UTF-8", new NoNameCoder()));
//支持注解,不然使用的 @XStreamAlias() 注解不会生效
xstream.autodetectAnnotations(true);
// 不输出class信息,不然标签中会包含class属性
xstream.aliasSystemAttribute(null, "class");
//自定义类型转换器,优先级要高,不然还是会使用框架的转换器
//这里如果不注册的话,序列化的时候自定义的转换器就不会生效了,只在反序列化的时候生效
xstream.registerConverter(new LocalDateTimeConverter(), XStream.PRIORITY_VERY_HIGH);
//框架自带的Boolean类型转换器,序列化时可以将 yes 转成true, 将 no 转成false, 默认是将 "true"字符串转成boolean类型的true
xstream.registerConverter(new BooleanConverter("yes", "no", false));
//注册null值序列化时也可以显示标签的转换器(参考文档)
//xstream.registerConverter(new NullConverter(xStream.getMapper(), new SunUnsafeReflectionProvider()));
xmlString = xstream.toXML(obj);
} catch (Exception e) {
e.printStackTrace();
}
return xmlString;
}
}
github传送门
gitee传送门
好了,关于Xstream就介绍到这里吧,欢迎大家批评指正。