XML解析工具:XStream

2023年 8月 13日 73.2k 0

michael-kroul-bTZU6EwVEQs-unsplash.jpg

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

  • 先定义一个实体Bean
  • /**  
     * 默认使用全路径作为标签 ...    
     * @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;
    }
    
  • 测试解析成XML
  • 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

  • 先提供一个xml结构
  • 
        1000001
        张三
        
            宝马
            50万
        
        
            
                F0001
                烤冷面
            
            
                F0002
                烧烤
            
        
        23
    
    
  • Bean结构还是和上面一样。
  • 我们写一个解析工具类看下
  • 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;  
        }
    }
    

    运行报错:
    image.png

    意思很明显,就是找不到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;  
    }
    

    运行测试类,依然报错:
    image.png

    从报错信息上很明显的知道,因为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;  
        }  
    }
    

    image.png

    解决办法:

    在创建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对象时,其内部会注册大量的转换器,常见类型的转换器基本上都包含了,比如:

    image.png

    看个例子来验证下:

       
          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;  
        }  
    }
    

    验证结果:
    image.png

    不难发现,基本上所有的类型都可以匹配上。但是日期Date要稍微注意下,如果我换成常见的格式 2023-07-16 17:01:31 就会报错:

    image.png

    不难发现,他默认只能解析它列出的这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. 解析结果:
    image.png

    自定义的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就介绍到这里吧,欢迎大家批评指正。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论