编程技术是改变世界的力量。
本站
当前位置:网站首页 > 后端语言 > 正文

详解Java中Xml报文四种解析方式 xml报文解析空格问题

gowuye 2024-04-04 11:54 11 浏览 0 评论

为什么要了解xml报文的解析方式呢?我觉得有两种情况:

  1. 项目需求,比如接口调用时,接收到的数据是xml报文,这时候你需要解析xml报文,然后将其封装成对应的实体类,最后将报文中的数据写入库中。
  2. 有利于阅读框架源码,在Java众多优秀的开源框架中,有许多框架用到xml报文解析,比如,强大的Spring(虽然后来官方推荐采用注解的方式创建对象),Mybatis框架(解析Mybatis-config.xml和对应的XxxMapper.xml文件)等。

那么xml报文有哪些解析方式呢?答案有4种,前两种与平台无关,后两种是在Java环境中封装前两种方式,使调用者更加方便地使用。

  • DOM解析
  • SAX解析
  • JDOM解析
  • DOM4J解析

下面,分别介绍这4种解析方式的特点。

一、DOM解析

DOM(Document Object Model)文档对象模型,它将xml报文解析成一棵节点树结构。这种解析方式比较占用内存,但是它能够随机访问xml报文的节点,比较灵活。

优点:

  • 将xml文档转换为树形结构,易于理解
  • 解析过程中,熟悉结构在内存中保存,易于修改

缺点:

  • 一次性读取整个xml报文,占用内存,对机器性能要求较高

>代码逻辑实现:采用工厂模式创建实例对象

基本流程:

  1. 实例化DocumentBuilderFactory对象
  2. 调用newDocumentBuilder()方法生成DocumentBuilder对象
  3. 调用parse()方法,解析xml,生成Document对象
  4. 用Document对象处理节点数据

1. xml报文

<?xml version="1.0" encoding="UTF-8" ?>
<father id="张大">
    <child id="张三">
        <age value = "18"/>
        <height>172</height>
    </child>
    <child id="张四">
        <age value="19"/>
        <height>173</height>
    </child>
</father>

2. 代码举例:

public class DOMResolve {
    private static final InputStream in;

    static {
        in = DOMResolve.class.getClassLoader().getResourceAsStream("test.xml");
        if (in != null) {
            System.out.println("加载成功...");
        }
    }

    public static void main(String[] args) {
        // 定义一个工厂API,该API使应用程序能够获取从XML文档生成DOM对象树的解析器
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

        try {
            // 通过DocumentBuilderFactory生成DocumentBuilder对象,该对象通过加载xml文件生成Document对象
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();

            // 调用parse()方法,解析xml,生成Document对象
            Document document = documentBuilder.parse(in);
            // 获取父节点father
            NodeList father = document.getElementsByTagName("father");

            if (father.getLength() != 1) {
                throw new RuntimeException("xml报文父节点为空或多于1个");
            }
            Node item = father.item(0);
            System.out.println("父节点:" + item.getNodeName());

            NamedNodeMap attributes1 = item.getAttributes();
            for (int i = 0; i < attributes1.getLength(); i++) {
                Node attr = attributes1.item(i);
                // 属性名、 属性值
                System.out.println(attr.getNodeName() + " = " + attr.getNodeValue());
            }

            // 获取指定的子节点child
            NodeList childNodes = document.getElementsByTagName("child");
            for (int i = 0; i < childNodes.getLength(); i++) {
                Node node = childNodes.item(i);
                System.out.println("\t二级节点:" + node.getNodeName());
                // 获取子节点的属性
                NamedNodeMap attributes = node.getAttributes();
                for (int j = 0; j < attributes.getLength(); j++) {
                    Node node1 = attributes.item(j);
                    System.out.print("\t\t" + node1.getNodeName() + "=" + node1.getNodeValue());
                }
                // 如果有子标签
                if (node.hasChildNodes()) {
                    // 获取子节点下面的所有子标签
                    NodeList nodeChildNodes = node.getChildNodes();
                    for (int j = 0; j < nodeChildNodes.getLength(); j++) {
                        Node item2 = nodeChildNodes.item(j);
                        // 如果标签类型为Element
                        if (item2.getNodeType() == Node.ELEMENT_NODE) {
                            // 如果有属性标签,则打印出来
                            if (item2.hasAttributes()) {
                                NamedNodeMap attributes2 = item2.getAttributes();
                                for (int k = 0; k < attributes2.getLength(); k++) {
                                    Node item1 = attributes2.item(0);
                                    System.out.print("\t" + item1.getNodeName() + "=" + item1.getNodeValue());
                                }
                                System.out.println();
                            }
                            System.out.println("\t\t\t子节点=" + item2.getNodeName() + ", content = " + item2.getTextContent().trim());
                        }
                    }
                }
            }
        } catch (ParserConfigurationException | SAXException | IOException e) {
            e.printStackTrace();
        }
    }

}

二、SAX解析

SAX(Simple APIs for Xml) 简单的XML操作API,它是基于事件驱动的顺序访问模式,从头到尾逐个元素读取内容。

优点:

  • 基于事件驱动,消耗内存小

缺点:

  • 编码麻烦,需要重写DefaultHandler类的方法
  1. xml报文
<?xml version="1.0" encoding="UTF-8"?>
<father id="张大">
    <child id="1">
        <age>18</age>
        <name>jack</name>
    </child>
    <child id="2">
        <age>20</age>
        <name>michael</name>
    </child>
</father>
  1. 自定义Handler,该Handler继承DefaultHandler
public class ChildHandler extends DefaultHandler {

    /**
     * startDocument开始索引
     */
    private static int startIndex = 0;

    /**
     * endDocument结束所有
     */
    private static int endIndex = 0;

    /**
     * 当前解析到的节点名称
     */
    private String currentTag;

    /**
     * 当前的节点对象
     */
    private Child child;
    /**
     * 文档集合
     */
    private List<Child> list;

    public List<Child> getList() {
        return list;
    }

    /**
     * 文档解析时开始调用,该方法只会被调用一次
     *
     * @throws SAXException
     */
    @Override
    public void startDocument() throws SAXException {
        super.startDocument();
        list = new ArrayList<>();
        startIndex = startIndex + 1;
        System.out.println("xml文档解析开始,调用次数:" + startIndex);
    }

    /**
     * 标签开始解析时调用
     *
     * @param uri        xml文档的命名空间
     * @param localName  包含名称空间的标签,如果没有名称空间,则为空
     * @param qName      不包含名称空间的标签
     * @param attributes 标签的属性集
     * @throws SAXException
     */
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        super.startElement(uri, localName, qName, attributes);
        if ("child".equals(qName)){
            // 遍历属性
            for (int i = 0; i < attributes.getLength(); i++) {
                child = new Child();
                if ("id".equals(attributes.getLocalName(i))){
                    child.setId(attributes.getValue(i));
                }
            }
        }
        currentTag = qName;
    }

    /**
     * 解析标签的内容的时候调用
     *
     * @param ch     当前读取到的TextNode(文本节点)的字节数组
     * @param start  字节开始的位置,为0表示读取全部
     * @param length 当前TextNode的长度
     * @throws SAXException
     */
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        super.characters(ch, start, length);
        // 将当前读取TextNode转换为String
        String value = new String(ch, start, length);
        if ("age".equals(currentTag)){
            child.setAge(Integer.parseInt(value));
        }else if ("name".equals(currentTag)){
            child.setName(value);
        }
    }

    /**
     * 标签结束时调用
     *
     * @param uri       xml文档的命名空间
     * @param localName 包含名称空间的标签,如果没有名称空间,则为空
     * @param qName     不包含名称空间的标签
     * @throws SAXException
     */
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        super.endElement(uri, localName, qName);
        if ("child".equals(qName)){
            list.add(child);
            child = null;
        }
        // 当碰到结束标签时,将当前标签置为null
        currentTag = null;
    }

    /**
     * 文档解析结束时调用,该方法只会被调用一次
     *
     * @throws SAXException
     */
    @Override
    public void endDocument() throws SAXException {
        super.endDocument();
        endIndex = endIndex + 1;
        System.out.println("xml文档解析结束,调用次数:" + endIndex);
    }
}
  1. 加载配置文件,测试
public class SaxBuilderTest {
    private static final InputStream in;
    static {
        in = SaxBuilderTest.class.getResourceAsStream("users.xml");
    }
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        // 实例化SAXParserFactory,由于SAXParserFactory的构造方法受保护的,因此无法new,必须的调用其提供的实例化方法newInstance
        SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
        // 实例化SAXParser解析器
        SAXParser parser = saxParserFactory.newSAXParser();
        // 创建自定义Handler,该Handler继承DefaultHandler
        ChildHandler handler = new ChildHandler();
        // Handler顺序解析配置文件
        parser.parse(in, handler);
        List<Child> list = handler.getList();
        for (Child child : list) {
            System.out.println(child);
        }
    }
}
输出:
Child{id='1', age=18, name='jack'}
Child{id='2', age=20, name='michael'}

三、JDOM解析

JDOM是一个开源项目,它基于树形结构,利用纯Java的技术对XML文档实现解析、生成、序列化及多种操作。

JDOM与DOM非常类似,它是处理XML的纯JAVA API,API大量使用了Collections类,且JDOM仅使用具体类而不使用接口。JDOM 它自身不包含解析器。它通常使用 SAX2 解析器来解析和验证输入 XML 文档(尽管它还可以将以前构造的 DOM 表示作为输入)。

导入依赖:

<dependency>
    <groupId>org.jdom</groupId>
    <artifactId>jdom2</artifactId>
</dependency>
@SpringBootTest
public class JDOMTest {

    @Test
    public void jdomTest(){
        try{
            InputStream in = new FileInputStream(new File("src/test/resources/test.xml"));
            List<Child> list = new ArrayList<>();
            SAXBuilder saxBuilder = new SAXBuilder();
            Document document = saxBuilder.build(in);
            //获取xml文档的根节点
            Element root = document.getRootElement();

            // 获取根节点下的所有子节点
            List<Element> childrens = root.getChildren();
            // 遍历子节点
            for (Element children : childrens) {
                Child child = new Child();
                child.setId(children.getAttributeValue("id"));
                Element element = children.getChild("age");
                Element element1 = children.getChild("height");
                child.setAge(Integer.parseInt(element.getText()));
                child.setHeight(Integer.parseInt(element1.getText()));
                list.add(child);
            }
            System.out.println(list);
        } catch (IOException | JDOMException e) {
            e.printStackTrace();
        }
    }
}

四、DOM4j解析

dom4j是一款基于Java性能最好的xml解析工具,它使用接口和抽象类等方法来完成xml文件解析,因此,这个工具大家需要掌握。

操作步骤:

1. 创建Document对象

// 1. 读取XML文件,获得document对象
SAXReader reader = new SAXReader();
Document document = reader.read(in);
// 2. 解析xml报文
Document document = DocumentHelper.parseText("");
// 3. 主动创建document,主要用来生成XML文件的
Document document = DocumentHelper.createDocument();

2. 获取元素节点Element

1. 获取文档的根节点.
    Element root = document.getRootElement();
2. 取得某个节点的子节点.
    Element element=node.element("age");
3. 取得节点的内容
    String text=node.getText();
4. 取得某节点下所有名为"age"的子节点,并进行遍历.
	List nodes = rootElm.elements("age"); 
	for (Iterator it = nodes.iterator(); it.hasNext();) {
		Element elm = (Element) it.next();
	}
5. 对某节点下的所有子节点进行遍历.    
	for(Iterator it=root.elementIterator();it.hasNext();){      
		Element element = (Element) it.next();
	}
6. 在某节点下添加子节点
		Element elm = newElm.addElement("朝代");
7. 设置节点文字.  elm.setText("明朝");
8. 删除某节点.//childElement是待删除的节点,parentElement是其父节点  parentElement.remove(childElment);
9. 添加一个CDATA节点.Element contentElm = infoElm.addElement("content");contentElm.addCDATA(“cdata区域”);

3. 获取节点中的属性Attributes

1.取得某节点下的某属性
	Element root=document.getRootElement();//属性名name
    Attribute attribute=root.attribute("id");
2.取得属性的文字
	String text=attribute.getText();
3.删除某属性 
	Attribute attribute=root.attribute("size"); root.remove(attribute);
4.遍历某节点的所有属性   
    Element root=document.getRootElement();      
    for(Iterator it=root.attributeIterator();it.hasNext();){        
        Attribute attribute = (Attribute) it.next();         
        String text=attribute.getText();        
        System.out.println(text);
	}
5.设置某节点的属性和文字.   newMemberElm.addAttribute("name", "sitinspring");
6.设置属性的文字   Attribute attribute=root.attribute("name");   attribute.setText("csdn");

测试:

XML报文:

<?xml version="1.0" encoding="UTF-8" ?>
<father id="张大">
    <child id="张三">
        <age>18</age>
        <height>172</height>
    </child>
    <child id="张四">
        <age>19</age>
        <height>173</height>
    </child>
</father>
public class Dom4jTest {

    private static InputStream in;

    static {
        in = Dom4jTest.class.getResourceAsStream("/test.xml");
    }

    @Test
    public void test() {
        SAXReader reader = new SAXReader();
        //解析xml报文
        try {
            Document document = reader.read(in);
            Element rootElement = document.getRootElement();
            Iterator it = rootElement.elementIterator();
            while (it.hasNext()) {
                Element element = (Element) it.next();
                List<Attribute> attributes = element.attributes();
                for (Attribute attr : attributes) {
                    System.out.println("节点名:" + attr.getName() + ",节点值:" + attr.getValue());
                }

                for(Iterator iterator = element.elementIterator(); iterator.hasNext();){
                    Element childElement = (Element)iterator.next();

                    if ("age".equals(childElement.getName())){
                        System.out.println("age = " + childElement.getText());
                    }
                    if ("height".equals(childElement.getName())){
                        System.out.println("height = " + childElement.getText());
                    }
                }
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } finally {
            if (in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
输出:
节点名:id,节点值:张三
age = 18
height = 172
节点名:id,节点值:张四
age = 19
height = 173

相关推荐

爱上开源之golang入门至实战第四章-切片(Slice)

前言Go数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可...

Go语言入门必知教程-切片

切片是一种灵活的和可扩展的数据结构,用于实现和管理数据集。切片由多个元素组成,所有元素都是相同类型的。切片是动态数组的一部分,可以根据需要进行增长和收缩。与数组一样,切片也可以索引。切片具有容量和长度...

Go语言基础-切片

切片是什么?切片是Go语言的一种数据结构。和数组相似,不过切片可以在它的结尾增加更多的元素。这样可变长度在实际编程中更为有用。声明切片切片的声明和数组也很相似,只是声明切片时不需要指定大小。例:va...

5分钟掌握GO中切片的基本使用方法

最近Golang越来越火,不少小伙伴都纷纷开始学习Golang,但对于原先为C++或者JAVA的同学,用习惯了数据、list、vector等,会对Go的切片slice不习惯,下面整理出go中slice...

揭秘 Go 切片(Slice)的秘密

当向切片添加新参数时,底层数组会发生什么变化?它会扩展以容纳更多元素吗?在这篇文章中,我们将深入探讨切片的内部工作原理,以及如何利用这些知识来进行更好的内存管理和性能优化。具体而言,我们将探索Go...

【Go语言slice详解】深入掌握Go语言中的slice类型及常用操作!

Go语言中的slice(切片)是一种非常方便的数据结构,可以动态地增加或减少其元素数量,且可以访问底层数组的任意一个子序列。本文将对Go语言中的slice进行详细的讲解。Slice的定义在Go语言中,...

掌握GO中的Slice,这就够了

最近Golang越来越火,不少小伙伴都纷纷开始学习Golang,但对于原先为C++或者JAVA的同学,用习惯了数据、list、vector等,会对Go的切片slice不习惯,下面整理出go中slice...

golang2021面向对象(26)Go语言类型内嵌和结构体内嵌

结构体可以包含一个或多个匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型也就是字段的名字。匿名字段本身可以是一个结构体类型,即结构体可以包含内嵌结构体。?可以粗略地将这个...

2022-11-13:以下go语言代码中,如何获取结构体列表以及结构体内

2022-11-13:以下go语言代码中,如何获取结构体列表以及结构体内的指针方法列表?以下代码应该返回{"S1":["M1","M2"],"S...

Go语言文件和目录操作

文件和目录操作概述一、文件和目录操作概述在计算机中,文件和目录是存储数据的重要方式。在Go语言中,我们可以使用os和io/ioutil包提供的函数和结构体来进行文件和目录操作。本文将详细介绍Go语言中...

跟我一起学习go语言(五)golang中结构体的初始化方法

1、自定义一个结构体typeVertexstruct{X,Yfloat64}2、初始化方法-指针:rect1:=new(Vertex)rect2:=&Vertex...

Go复合数据类型:结构体

一种通用的、对实体对象进行聚合抽象的能力,在Go中,提供这种聚合抽象能力的类型是结构体类型,也就是struct。自定义一个新类型在Go中,我们自定义一个新类型一般有两种方法。第一种是类型定义...

Go语言基础:方法

导读在阅读本文章前,假定你具备如下能力:?已掌握结构体1.方法1.1方法的概念在理解程序中方法的概念时,我们先看看现实中的一些情况,这样相对比较好理解一些。在农村的朋友可能会知道,在医疗落后的情况...

为什么 Go 语言 struct 要使用 tags

在Go语言中,struct是一种常见的数据类型,它可以用来表示复杂的数据结构。在struct中,我们可以定义多个字段,每个字段可以有不同的类型和名称。除了这些基本信息之外,Go还提供了s...

一文带你掌握掌握 Golang结构体与方法

1.Golang结构体的概念及定义结构体是Golang中一种复合类型,它是由一组具有相同或不同类型的数据字段组成的数据结构。结构体是一种用户自定义类型,它可以被用来封装多个字段,从而实现数据的...

取消回复欢迎 发表评论: