
虽然现在开发中基本上都是使用JSON作为数据传输的格式,常用的JSON框架比如FastJson, FastJson2, Jackson, Gson等等,但是有时候我们也会用到xml格式用来传输数据,尤其做政府项目的时候很多数据格式都是xml, 所以今天就推荐一个xml的解析工具:XStream。它的功能也比较丰富,具体可以看下官网,它还可以用来解析JSON,只不过很少使用它。
1. XStream 的基本使用
xml
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.20</version>
</dependency>
尽量使用高版本,因为低版本中有很多漏洞,目前最高版本是1.4.20。
1.1 Bean 转 XML
- 先定义一个实体Bean
java
/**
* 默认使用全路径作为标签 <com.qiuguan.xml.test.Perosn>... </com.qiuguan.xml.test.Perosn>
* @XStreamAlias("Person") 可以指定标签的别名
*/
@XStreamAlias("Person")
@Data
public class Person {
/**
* 默认使用属性名作为标签 <idCard>...</idCard>
*/
private String idCard;
private String name;
/**
* 自定义类作为属性,包含了name和price属性
*/
@XStreamAlias("CAR_INFO")
private Car car;
/**
* 集合属性
*/
@XStreamAlias("foodList")
private List<Food> 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;
}
- 测试解析成XML
java
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);
}
}
- 输出结果:
xml
<Person>
<!-- 默认使用属性名作为标签 -->
<idCard>1000001</idCard>
<name>张三</name>
<!-- @XStreamAlias("CAR_INFO") -->
<CAR_INFO>
<name>宝马</name>
<price>50万</price>
</CAR_INFO>
<!-- 默认使用属性名作为标签 -->
<foodList>
<food>
<id>F0001</id>
<name>烤冷面</name>
</food>
<food>
<id>F0002</id>
<name>烧烤</name>
</food>
</foodList>
</Person>
用起来还是很简单的
1.2 XML 转 Bean
- 先提供一个xml结构
xml
<Person_Info>
<idCard>1000001</idCard>
<name>张三</name>
<CAR>
<name>宝马</name>
<price>50万</price>
</CAR>
<foodList>
<food>
<id>F0001</id>
<name>烤冷面</name>
</food>
<food>
<id>F0002</id>
<name>烧烤</name>
</food>
</foodList>
<age>23</age>
</Person_Info>
- Bean结构还是和上面一样。
- 我们写一个解析工具类看下
java
public class XmlTest {
public static void main(String[] args) {
String xml=
"<Person_Info>
<idCard>1000001</idCard>
<NAME>张三</NAME>
<CAR>
<name>宝马</name>
<price>50万</price>
</CAR>
<FoodList_Infos>
<food>
<id>F0001</id>
<name>烤冷面</name>
</food>
<food>
<id>F0002</id>
<name>烧烤</name>
</food>
</FoodList_Infos>
<age>23</age>
</Person_Info>";
Person p = strToBean(xml, Person.class);
System.out.println(p);
}
@SuppressWarnings("unchecked")
public static <T> T strToBean(String xmlStr, Class<T> 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结构中的标签是<Person_Info>, 但是javaBean中我通过@XStreamAlias("Person")
注解制定的是<Person>
,所以他俩要保持一致,要将JavaBean中的相关标签与xml标签保持一致。所以将JavaBean调整为:
java
/**
* 默认使用全路径作为标签 <com.qiuguan.xml.test.Perosn>...</com.qiuguan.xml.test.Perosn>
* @XStreamAlias("Person_Info") 可以指定标签的别名
*/
@XStreamAlias("Person_Info")
@Data
public class Person {
/**
* 默认使用属性名作为标签 <idCard>...</idCard>
*/
private String idCard;
private String name;
/**
* 默认使用全路径作为标签
*/
@XStreamAlias("CAR_INFO")
private Car car;
/**
* 默认使用属性名作为标签,集合也一样
*/
private List<Food> foodList;
}
运行测试类,依然报错:
从报错信息上很明显的知道,因为xml中有一个
<age></age>
标签,但是Person
类中没有这个属性,所以导致了报错,此时有两种办法可以解决。第一种就是给Person
类中加上age
属性,另外一种就是如下:
java
//忽略掉位置的属性
xStream.ignoreUnknownElements();
2. 存在的问题
2.1 序列化时,"_" 会变成 "__"
java
@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 对象
java
XStream xstream = new XStream(new DomDriver("UTF-8", new NoNameCoder()));
2.2 反序列化时XML的标签节点与Bean的属性不匹配时报错
前面也有提到过了,如果xml中存在的标签节点在JavaBean中不存在,在XML转Bean的时候将会报错,所以创建XStream对象时可以指定忽略未知属性
java
XStream xStream = new XStream(new DomDriver("UTF-8", new NoNameCoder()));
//忽略未知的属性
xStream.ignoreUnknownElements();
2.3 null值序列化时不会显示标签
2.4 自定义转换器完成时间类型的转换
在创建XStream
对象时,其内部会注册大量的转换器,常见类型的转换器基本上都包含了,比如:

看个例子来验证下:
xml
<My_Order>
<orderId>1001</orderId>
<price>34.06</price>
<money>23.67</money>
<flag>3</flag>
<buyDate>2023-07-16 17:01:31.285 UTC</buyDate>
<isSuccess>false</isSuccess>
</My_Order>
JavaBean:
java
@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;
}
测试类:
java
public class XmlTest {
public static void main(String[] args) {
String orderXml =
"<My_Order>\n" +
" <orderId>1001</orderId>\n" +
" <count>590</count>\n" +
" <price>34.06</price>\n" +
" <money>23.67</money>\n" +
" <flag>3</flag>\n" +
" <buyDate>2023-07-16 17:01:31.285 UTC</buyDate>\n" +
" <isSuccess>false</isSuccess>\n" +
"</My_Order>";
Order o = strToBean(orderXml, Order.class);
System.out.println("order映射结果 = " + JSON.toJSONString(o, JSONWriter.Feature.PrettyFormat));
}
@SuppressWarnings("unchecked")
public static <T> T strToBean(String xmlStr, Class<T> 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:
java
@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:
xml
<My_Order>
<orderId>1001</orderId>
<price>34.06</price>
<money>23.67</money>
<flag>3</flag>
<buyDate>2023-07-16 17:01:31.285 UTC</buyDate>
<isSuccess>true</isSuccess>
</My_Order>
3. 自定义LocalDateTime类型转换器:
java
@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. 解析工具类:
java
public class XmlToStrTest {
public static void main(String[] args) {
String orderXml = "<My_Order>\n" +
" <orderId>1001</orderId>\n" +
" <count>590</count>\n" +
" <price>34.06</price>\n" +
" <money>23.67</money>\n" +
" <flag>3</flag>\n" +
" <buyDate>2023-07-16 17:01:31</buyDate>\n" +
" <isSuccess>true</isSuccess>\n" +
"</My_Order>";
Order o = strToBean(orderXml, Order.class);
System.out.println("order映射结果 = " + JSON.toJSONString(o, JSONWriter.Feature.PrettyFormat));
}
public static <T> T strToBean(String xmlStr, Class<T> 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】 注解:
java
@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 |
用于标记某个字段不参与序列化和反序列化 |
通过一个例子来演示看下:
java
@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;
/**
* 指定元素的字段名
* 如果不指定的话,默认使用类型作为标签名 <String>...</String>
*/
@XStreamImplicit(itemFieldName = "likes")
private List<String> likes;
/**
* 注意看 xml
*/
@XStreamImplicit(itemFieldName = "FOOD_LIST")
private List<Food> foods;
}
序列化后的xml:
xml
<!--name作为节点的属性,不再单独作为一个节点-->
<A_TEACHER name="马云">
<!--age属性不参与序列化和反序列化-->
<salary>35000.0</salary>
<color>黑色</color>
<!--指定集合元素的名称为 likeItem -->
<likeItem>读书</likeItem>
<likeItem>讲课</likeItem>
<likeItem>拍视频</likeItem>
<!-- 通过 @XStreamImplicit注解标注后,集合中每一个对象都是被 <FOOD_LIST> 标签包裹 -->
<FOOD_LIST>
<id>F0001</id>
<name>炸酱面</name>
</FOOD_LIST>
<FOOD_LIST>
<id>F0002</id>
<name>卤煮</name>
</FOOD_LIST>
<!-- 通过 @XStreamAlias注解标注后,所有对象属于同一个集合,每个对象的标签名也可以自定义 -->
<FOOD_LIST2>
<FOOD>
<id>F0003</id>
<name>烧烤</name>
</FOOD>
<FOOD>
<id>F0004</id>
<name>烤饼</name>
</FOOD>
</FOOD_LIST2>
</A_TEACHER>
4.开箱即用的工具类
java
public class XmlParseUtils {
/**
* 反序列化
*/
@SuppressWarnings("unchecked")
public static <T> T strToBean(String xmlStr, Class<T> 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;
}
}
好了,关于Xstream就介绍到这里吧,欢迎大家批评指正。