XML解析工具:XStream

虽然现在开发中基本上都是使用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

  1. 先定义一个实体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;
}
  1. 测试解析成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);
   }
}
  1. 输出结果
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

  1. 先提供一个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>
  1. Bean结构还是和上面一样。
  2. 我们写一个解析工具类看下
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;
    }
}

github传送门
gitee传送门

好了,关于Xstream就介绍到这里吧,欢迎大家批评指正。

相关推荐
东阳马生架构1 分钟前
订单初版—2.生单链路中的技术问题说明文档
java
凌览2 分钟前
有了 25k Star 的MediaCrawler爬虫库加持,三分钟搞定某红书、某音等平台爬取!
前端·后端·python
这里有鱼汤14 分钟前
给你的DeepSeek装上实时行情,让他帮你炒股
后端·python·mcp
咖啡啡不加糖16 分钟前
暴力破解漏洞与命令执行漏洞
java·后端·web安全
风象南19 分钟前
SpringBoot敏感配置项加密与解密实战
java·spring boot·后端
DKPT29 分钟前
Java享元模式实现方式与应用场景分析
java·笔记·学习·设计模式·享元模式
Percep_gan37 分钟前
idea的使用小技巧,个人向
java·ide·intellij-idea
ん贤37 分钟前
RESTful风格
后端·go·restful
缘来是庄38 分钟前
设计模式之迭代器模式
java·设计模式·迭代器模式
Humbunklung39 分钟前
Rust方法语法:赋予结构体行为的力量
开发语言·后端·rust