Java开发必会:dom4j读取XML与XPath定位实战解析
XML这个东西现在很多人一提就觉得是老古董觉得JSON已经统治世界了。但实际上真做Java Web开发XML依然是绕不开的一环框架的配置文件、系统间接口报文、某些银行的交易数据、SVG矢量图底层、Maven的pom.xml全是XML。更重要的是如果你要读别人系统暴露的XML数据接口或者维护老项目不懂XML解析真的寸步难行。这篇我就从手写XML讲起把dom4j读取和XPath定位的核心用法一次性说透最后配两个可以直接抄作业的完整案例。适合刚入行Java后端、或者写了一些CRUD但对XML解析不熟的开发者。1. 先搞清楚XML是什么值不值得花时间学1.1 XML和HTML的差异一句话讲透XML全称是Extensible Markup Language中文叫可扩展标记语言。它和HTML长的像但本质完全不同。HTML的标签是写死的浏览器认识h1、p、div这些它们的样式和含义是预定义的而XML的标签完全是你自己定义的比如你做一个图书系统可以定义book、author、price做订单系统可以定义order、amount你想叫什么就叫什么只要你把规则写清楚。所以XML的核心价值不是显示而是存储和传输结构化数据。它不关心数据长的好不好看只关心数据组织得清不清晰能否被程序稳定的读出来。我个人的理解是JSON适合前端和后端之间的轻量交互性能好、解析快XML适合跨系统、跨语言的数据交换它自带元数据和校验规则天生就带自描述性。举个例子booktitle深入理解Java虚拟机/title/book光看这一段你就知道它描述的是书和书名这就是自描述。1.2 现在还在用XML的场景有哪些很多人问我学了XML能做啥是不是过时了实际工作中XML不仅没过时还占据着不少关键位置框架配置文件Spring的applicationContext.xml、MyBatis的mapper.xml、Log4j的log4j2.xml这些都是XML。你说你不学XML结果连框架配置都读不懂这说不过去。系统间数据交换很多银行、支付、政务系统的接口报文还是XML格式历史原因是一方面另一方面是XML支持DTD和Schema校验数据格式的严谨度比JSON高。软件发布与依赖描述Maven的pom.xml、Android的项目配置核心就是XML。网页模板与文档格式SVG、RSS、还有Office的docx、xlsx文件内部结构全是XML。跨语言数据中转有些系统是Java写的有些是C#或Python写的语言之间传递复杂结构化数据XML的通用性比自定义序列化协议要稳。所以不要被XML已死的论调带偏对于Java Web开发者来说XML是必学的基础技能之一。2. 手写XML的硬核语法避开格式化深坑2.1 一个XML文件的基础骨架长什么样手写XML看起来简单但越是简单的东西越容易踩格式坑。我放一个标准示例你一眼就能看明白基本结构?xml version1.0 encodingUTF-8? !DOCTYPE 书籍列表 SYSTEM books.dtd 书籍列表 书 编号001 书名深入理解Java虚拟机/书名 作者虚构作者甲/作者 价格128.00/价格 简介JVM底层原理与性能调优/简介 /书 书 编号002 书名Effective Java/书名 作者虚构作者乙/作者 价格98.00/价格 简介Java编程最佳实践/简介 /书 /书籍列表这个文件包含了一个合法XML文件的全部要素声明头、文档类型定义这里只是声明可以没有、根元素、子元素、属性、文本内容。有一点必须强调XML文件必须有且只有一个根元素所有其他元素都得包在它里面。我见过不少新手手写XML一不小心写了两个根元素解析器直接报错Document root element is missing。2.2 中文标签和命名规范到底能否使用上面示例我故意用了中文标签这在语法上完全合法。XML的命名规则是可以包含字母、数字、下划线、连字符、点号但不能以数字开头不能包含空格也不能以xml不分大小写开头。实际上大家手写XML时建议用英文字母加下划线比如book_id而不是书的编号。原因倒不是语法问题而是后续配合XPath、dom4j、MyBatis等工具时英文标签更通用避免编码问题带来的误会。中文标签只是可以但不好这个是实操经验。属性命名规则也类似属性值必须用引号括起来——单引号双引号都行但同一个属性不能混用。属性值里如果要包含引号就需要用实体引用quot;双引号或apos;单引号。2.3 特殊字符与CDATA踩坑重灾区XML中有五个预定义的实体引用这是手写XML时必须记牢的字符实体引用说明lt;小于号左尖括号gt;大于号右尖括号amp;与号quot;双引号apos;单引号撇号比如你要在XML里存一段Java代码里面全是if (a b c d)这类内容如果直接写解析器会把当成标签开头直接报错。这时候有两种选择第一种把特殊字符换成实体引用缺点是代码读起来巨痛苦。第二种用CDATA节告诉解析器这部分内容原样输出别给我解析了这才是正确的姿势代码示例 ![CDATA[ if (a b c d) { System.out.println(a小于b且c大于d); } ]] /代码示例CDATA的范围是![CDATA[到]]之间这里面什么字符都能放唯一需要注意的是CDATA内部不能出现]]。我当年处理接口报文时对方返回的内容里恰好有SQL语句里面含了、第一次解析时直接炸了后来加了CDATA就安静了。2.4 合法性与良构性不只是格式对了就完事手写XML的时候要区分两个概念well-formed良构和valid有效。良构性指的是语法层面标签是否正确闭合、大小写是否一致、属性是否加了引号、根元素是否唯一。只要解析器能顺利读进去就是良构的。有效性说的是内容层面数据是否符合你的业务规则。比如价格abc/价格语法没问题但它不是一个合法的价格数字。要约束这种业务规则就需要用到DTDDocument Type Definition或XML Schema。DTD可以直接写在XML文件里也可以单独放一个.dtd文件格式大致是这样!DOCTYPE 书籍列表 [ !ELEMENT 书籍列表 (书) !ELEMENT 书 (书名, 作者, 价格, 简介) !ELEMENT 书名 (#PCDATA) !ELEMENT 作者 (#PCDATA) !ELEMENT 价格 (#PCDATA) !ELEMENT 简介 (#PCDATA) !ATTLIST 书 编号 CDATA #REQUIRED ]这段DTD的意思是根元素是书籍列表它必须包含一个或多个书每本书必须按顺序包含书名、作者、价格、简介书元素必须带一个编号属性。DTD的写法有些繁琐如果你对数据校验要求更高可以用XML Schema它本身就是XML所以不用额外学一套语法还能定义数据类型比如价格必须是decimal号码必须是integer。实际项目中用XML Schema做报文校验是比较正规的做法尤其是做支付接口这类对数据准确性要求极高的场景。3. XML的四种主流解析方式我为什么盯上dom4j3.1 DOM、SAX、JDOM、dom4j横评Java生态里解析XML的方式主要有四种先整体过一遍你才知道自己选的是什么、放弃了什么。DOM解析把整个XML文件读进内存构建成一棵Document对象树。优点是API简单直观可以随意增删改查、支持XPath缺点是文件一大了内存爆炸。比如一个100MB的XMLDOM直接加载内存占用轻松超过1GB。适合配置类、中小型文件。SAX解析基于事件驱动边读边触发回调方法比如读到元素开始就触发startElement读到文本就触发characters。优点是内存占用极低适合超大文件缺点是代码极其繁琐而且只能读不能改。JDOM解析比DOM更Java化API更清爽但功能相对简单高级特性薄弱生态也冷下来了。dom4j解析它既不是最简单也不是最快的那种但它是功能、性能、易用性平衡得最好的。底层基于SAX但对外暴露了类似DOM的面向对象API还内置了XPath支持。最要命的是Spring、Hibernate等一堆主流框架内部用的就是它来读XML配置文件你学会了dom4j相当于顺手解锁了框架内部的档案室。从学习投入回报率来看dom4j是你最值得花时间掌握的Java XML解析库。虽然现在很多项目用Spring Boot注解配置但面向遗留系统和接口报文dom4j依然是经典选择而且它不挑Java版本老项目里也能跑得很舒服。3.2 dom4j的核心对象模型dom4j把XML文档映射成一组Java接口最重要的有这么几个Document对应整个XML文档相当于那棵树的根入口。Element对应一个XML元素比如书名深入理解Java虚拟机/书名。Attribute对应元素的属性比如编号001。Text对应元素内的文本内容。Node是Element、Attribute的公共父接口很多方法统一返回Node。这几个对象的关系我习惯用俄罗斯套娃来类比Document是最外面那层套着根Element根Element里又有子Element、Attribute、Text一层套一层。dom4j的操作思路就是拿到Document - 拿到根元素 - 向下遍历/查找 - 按需读取或修改。3.3 Maven依赖和项目环境准备要用dom4j先把Maven依赖加上dependency groupIddom4j/groupId artifactIddom4j/artifactId version1.6.1/version /dependency老项目里也常见1.6.1这个版本兼容性好十分稳定。如果你的项目没有Maven也可以直接下载dom4j的jar包丢到classpath下但dom4j依赖一个叫jaxen的库用于XPath支持记得一并引入否则你调用selectNodes会抛ClassNotFoundException。我个人的建议是直接上Maven顺带把jaxen也显式声明一下避免某些环境下传递依赖没拉全dependency groupIdjaxen/groupId artifactIdjaxen/artifactId version1.1.6/version /dependency4. dom4j读取XML的完整实操4.1 从文件、字符串、网络流三种方式加载XMLdom4j最常用的入口是SAXReader类名字叫SAX但读进来的结果却是DOM风格的Document对象所以它兼具SAX的低内存和DOM的易用性。加载XML有三种最常见的方式// 方式一从文件加载 SAXReader reader new SAXReader(); Document document reader.read(new File(books.xml)); // 方式二从字符串加载接口返回或配置文件里抠出来的XML片段 String xmlStr ?xml version\1.0\ encoding\UTF-8\?书籍列表.../书籍列表; Document doc reader.read(new StringReader(xmlStr)); Document doc2 reader.read(new ByteArrayInputStream(xmlStr.getBytes(UTF-8))); // 方式三从网络流加载URL或远端接口 Document doc3 reader.read(new URL(http://example.com/data/books.xml));有一个非常容易踩的坑reader.read()方法接收的参数是多样化的但你如果传一个字符串进去它会把字符串当作文件路径去解析而不是当作XML内容。正确做法是用StringReader包一层或者转成ByteArrayInputStream。我第一次用的时候直接传了XML字符串报了一堆FileNotFoundException排查了半天才反应过来。4.2 遍历XML拿到所有节点数据加载完Document之后就是遍历和取值。这里的核心套路是获取根元素然后一级一级找下去。Element root document.getRootElement(); System.out.println(根元素: root.getName()); // 遍历所有书 ListElement bookList root.elements(书); for (Element book : bookList) { // 读取属性 String id book.attributeValue(编号); // 读取子元素文本 Element titleElem book.element(书名); String title titleElem.getText(); String author book.elementText(作者); String price book.elementText(价格); System.out.println(编号 id , 书名 title , 作者 author , 价格 price); }这里面有几个API你需要重点记root.elements(书)获取所有名为书的直接子元素。如果不传参数root.elements()返回所有直接子元素。book.attributeValue(编号)获取属性值属性不存在时返回null。book.element(书名)获取第一个名为书名的直接子元素。book.elementText(作者)一步到位直接取作者子元素的文本内容没有就返回null。注意elementText这种便捷方法的存在它能少写很多if判空。但要注意它和element(书名).getText()的区别前者内部帮你做了空元素和空文本的处理不存在的元素返回null而后者如果你不判空元素不存在就会抛NullPointerException。4.3 递归遍历不管XML嵌套多深都能读如果XML层级复杂比如商品分类套着子分类、评论套着回复那写死层级的遍历肯定不行。这时候可以用递归让代码自己往下钻。我封装过一个工具方法public void traverse(Element element, int depth) { // 打印当前节点名字和属性 StringBuilder prefix new StringBuilder(); for (int i 0; i depth; i) { prefix.append( ); } System.out.println(prefix 元素: element.getName() , 文本: element.getTextTrim()); // 遍历属性 ListAttribute attrs element.attributes(); for (Attribute attr : attrs) { System.out.println(prefix 属性: attr.getName() attr.getValue()); } // 递归遍历子元素 IteratorElement it element.elementIterator(); while (it.hasNext()) { Element child it.next(); traverse(child, depth 1); } }递归遍历的核心思想是每个元素都要做三件事——读自身、读属性、递归读子元素。只要把这三步做好不管XML嵌套多深都能完整解析出来。getTextTrim()比getText()更实用它会把文本两端的空白字符去掉因为很多XML文件为了可读性会有缩进和换行直接getText()会拿到一堆换行和空格文本值就脏了。4.4 修改XML增删改后写回文件解析XML不只是读很多时候需要改。我举个需求背景系统读取完books.xml后要把一本书的价格改掉再新增一本新书然后重新写回文件。dom4j实现起来非常直接// 找到第一本书修改价格 Element firstBook root.element(书); Element priceElem firstBook.element(价格); priceElem.setText(88.00); // 新建一本书 Element newBook root.addElement(书); newBook.addAttribute(编号, 003); newBook.addElement(书名).setText(Java并发编程实战); newBook.addElement(作者).setText(虚构作者丙); newBook.addElement(价格).setText(108.00); newBook.addElement(简介).setText(并发编程进阶指南); // 删除某本书比如删除编号为002的书 ListElement bookList root.elements(书); for (IteratorElement it bookList.iterator(); it.hasNext(); ) { Element book it.next(); if (002.equals(book.attributeValue(编号))) { it.remove(); // 注意用Iterator的remove不能边遍历边list.remove break; } } // 写回XML文件 OutputFormat format OutputFormat.createPrettyPrint(); format.setEncoding(UTF-8); XMLWriter writer new XMLWriter(new FileOutputStream(books_out.xml), format); writer.write(document); writer.close();写回文件时一定要指定编码格式不然会按系统默认编码输出中文很容易乱码。OutputFormat.createPrettyPrint()是格式化输出生成的XML会自动缩进换行给人看比较友好如果不需要格式、想要紧凑输出可以用OutputFormat.createCompactFormat()这个在要传输或存储的场景下能省不少空间。提示修改XML时如果原文件带DOCTYPE声明写回之后这个声明可能会丢失或被改变。如果接口报文对DTD有强校验要求写回时要注意保留或者改用XMLStreamWriter这类更底层的工具。5. XPath定位节点让查找效率起飞5.1 XPath语法速成像写路径一样查XMLXML解析最烦的往往是深层嵌套一个节点在根下的第三层、第四层你还得一层层写循环找。XPath就是解决这个痛点的它的核心思想是用路径表达式直接定位节点类似你在文件系统里写路径/usr/local/bin/java。我捡最常用的几类语法说/表示从根节点开始。比如/书籍列表/书表示根下所有书元素。//表示在整个文档中查找不管层级。比如//书名表示所有书名元素。.表示当前节点..表示父节点。[属性名]表示按属性筛选比如/书籍列表/书[编号001]。[N]表示取第几个比如/书籍列表/书[1]是第一个书元素注意这个下标从1开始不是0。[条件]还能用运算比如//书[价格100]选取价格大于100的书。text()用于取值比如//书名/text()直接返回书名的文本内容。配合dom4j的document.selectNodes()和document.selectSingleNode()一套组合拳下来代码能少写70%。5.2 dom4j XPath实战按条件精准定位还是用上面那份书籍XML我现在要从里面找出价格大于等于100且作者名字以虚构开头的所有书// 获取文档 SAXReader reader new SAXReader(); Document document reader.read(new File(books.xml)); // 查找所有价格大于等于100的书 ListElement expensiveBooks document.selectNodes(//书[价格 100]); for (Element book : expensiveBooks) { System.out.println(高价书: book.elementText(书名)); } // 查找作者以指定文字开头的书 ListElement authorBooks document.selectNodes(//书[作者[starts-with(text(),虚构)]]); for (Element book : authorBooks) { System.out.println(匹配作者的书: book.elementText(书名)); } // 按属性查找单本书 Element targetBook (Element) document.selectSingleNode(//书[编号002]); if (targetBook ! null) { System.out.println(找到编号002的书: targetBook.elementText(书名)); } // 直接取文本内容 ListNode titleNodes document.selectNodes(//书名/text()); for (Node node : titleNodes) { System.out.println(书名文本: node.getText()); }使用XPath时有一个高频坑路径写错或节点不存在时selectNodes不会抛异常只是返回空列表而selectSingleNode返回null。所以拿到selectSingleNode的结果后一定要判空别一上来就调用方法否则空指针教做人。另外一个坑是如果XML里用了命名空间xmlns属性那么你的XPath路径必须带上命名空间前缀否则怎么都匹配不到节点。比如ns:书 xmlns:nshttp://example.com直接//书是找不到东西的必须用//ns:书而且要注册NamespaceContext。这是很多新人在对接外部接口时一头雾水的地方排查思路要往命名空间上想。5.3 XPath的高级用法组合条件与函数XPath不止能取节点还能做不少逻辑操作。它内置了字符串函数、数值函数、集合操作等。比如//书[last()]取最后一个书元素。//书[position() 3]取前两个。//书[价格100 and 价格150]区间筛选。//书[not(编号)]找出没有编号属性的书。string(//书[编号001]/书名)把节点转成字符串直接拿文本值。dom4j对这些函数的支持依赖jaxen库所以前面我特意让你把jaxen加进依赖缺了它你在XPath里用函数或高级表达式会报错Can not find function。6. 一个完整的案例解析商城接口返回的XML前面讲了这么多碎片化的用法可能会觉得有点散。这个章节我直接给一个连贯的实战案例把你常见的需求串起来。6.1 场景与需求分析假设你在做一个电商系统上面有一个商品中心它对外暴露XML数据接口返回的是当日特价商品列表。你的任务有以下三条从接口返回的XML中解析出所有商品包含商品ID、名称、原价、特价、库存。筛选出特价比原价低20%以上、且库存大于100的商品。把这些商品按特价从低到高排序输出。接口返回的XML长这样?xml version1.0 encodingUTF-8? 商品中心 特价商品列表 商品 商品IDP1001 名称无线机械键盘/名称 原价399.00/原价 特价299.00/特价 库存580/库存 /商品 商品 商品IDP1002 名称显示器支架/名称 原价259.00/原价 特价189.00/特价 库存35/库存 /商品 商品 商品IDP1003 名称人体工学鼠标/名称 原价199.00/原价 特价149.00/特价 库存204/库存 /商品 /特价商品列表 /商品中心6.2 核心代码实现因为要读取的是接口数据所以用HTTP请求拉取后直接解析。整体分三步走拉取XML、用dom4j加载解析、用XPath筛选并处理。import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; import java.io.ByteArrayInputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.Comparator; import java.util.List; public class PromoProductParser { public static void main(String[] args) throws Exception { String xmlContent fetchXmlFromApi(http://example.com/api/promotions); ListProduct result parseAndFilterProducts(xmlContent); result.forEach(System.out::println); } /** 通过HTTP GET抓取接口返回XML字符串 */ private static String fetchXmlFromApi(String apiUrl) throws Exception { URL url new URL(apiUrl); HttpURLConnection conn (HttpURLConnection) url.openConnection(); conn.setRequestMethod(GET); conn.setConnectTimeout(3000); conn.setReadTimeout(3000); if (conn.getResponseCode() ! 200) { throw new RuntimeException(接口返回非200状态码: conn.getResponseCode()); } StringBuilder sb new StringBuilder(); try (var reader new java.io.BufferedReader( new java.io.InputStreamReader(conn.getInputStream(), UTF-8))) { String line; while ((line reader.readLine()) ! null) { sb.append(line); } } return sb.toString(); } /** 解析XML并根据业务条件筛选排序 */ private static ListProduct parseAndFilterProducts(String xmlContent) throws Exception { SAXReader saxReader new SAXReader(); Document document saxReader.read(new ByteArrayInputStream(xmlContent.getBytes(UTF-8))); // 使用XPath直接定位到所有商品元素 ListElement productElements document.selectNodes(//商品); ListProduct candidates new ArrayList(); for (Element elem : productElements) { Product product new Product(); product.id elem.attributeValue(商品ID); product.name elem.elementText(名称); product.originalPrice Double.parseDouble(elem.elementText(原价)); product.promoPrice Double.parseDouble(elem.elementText(特价)); product.stock Integer.parseInt(elem.elementText(库存)); // 条件特价比原价低20%以上 double discount (product.originalPrice - product.promoPrice) / product.originalPrice; if (discount 0.2 product.stock 100) { candidates.add(product); } } // 按特价从低到高排序 candidates.sort(Comparator.comparingDouble(p - p.promoPrice)); return candidates; } static class Product { String id; String name; double originalPrice; double promoPrice; int stock; Override public String toString() { return String.format(商品ID%s, 名称%s, 原价%.2f, 特价%.2f, 库存%d, id, name, originalPrice, promoPrice, stock); } } }输出结果商品IDP1003, 名称人体工学鼠标, 原价199.00, 特价149.00, 库存204 商品IDP1001, 名称无线机械键盘, 原价399.00, 特价299.00, 库存580这里有几个实操细节我想单独拎出来说fetchXmlFromApi里解析响应时指定了UTF-8字符集这是必须的。如果接口返回的是其他编码你硬按UTF-8去读中文直接变成乱码后面解析全废。XPath用了//商品不管商品节点嵌了多少层都能一把捞出来这就是XPath比手动遍历强的地方。金融类数据尽量不要用double这里仅为演示实际做电商价格计算建议用BigDecimal否则精度问题会咬人。HTTP请求别忘了设连接超时和读取超时不然接口挂了你整个系统会跟着卡死。6.3 案例扩展思路如果你想把它做扎实上面的代码能满足基本需求但如果你要在生产环境用我建议你继续往这几个方向完善增加XML Schema校验在解析前先校验报文格式防止上游数据格式错乱导致解析器受罪。把解析结果封装成Java对象列表配合MyBatis或JPA直接入库形成接口拉取 - 解析 - 持久化的完整链路。加日志埋点解析出错时把原始XML片段记录下来方便排查到底是上游数据问题还是解析代码问题。考虑缓存如果这个接口的数据不是实时变化的可以加本地缓存或Redis缓存减少重复请求带来的压力。7. 实战中避坑指南这几条都是血泪教训7.1 编码问题导致的乱码和解析失败XML解析失败十次有七次是因为编码不对。XML声明头写了encodingUTF-8但文件实际保存的是GBK编码解析器按UTF-8去读中文标签名就直接报错了。这种情况你排查半天代码都没用问题在文件本身的编码。正确做法保存XML文件时编辑器右下角明确选成UTF-8不要用系统默认编码。在Java里读取时能用InputStream就传InputStream并且显式指定字符集SAXReader reader new SAXReader(); Document doc reader.read(new InputStreamReader(new FileInputStream(books.xml), UTF-8));不过要注意如果XML头声明了编码InputStreamReader指定的编码会和声明不一致此时以声明为准。最稳妥的方案是自己保证文件编码、声明编码、读取编码三者统一。7.2 XML解析性能问题大文件别硬扛dom4j虽然底层是SAX但默认还是全量加载成Document树所以文件特别大的时候内存占用依然可观。如果你要处理的XML超过几十MB建议换用SAX事件流解析或者用dom4j的SAXReader配合过滤流只提取你关心的子文档片段不要一把梭全部加载。另外有个小技巧如果XML文件里含有大量的空白字符占位可以在加载后调用document.getRootElement().elements()时注意返回的是直接子元素不会包含纯空白文本节点这算是dom4j的友好处理避免了part方式解析里那堆isWhitespace的判断。7.3 XPath匹配不到时的排查套路XPath匹配不到大部分时候不是语法错了而是对不上。我总结一个排查顺序先确认命名空间XML有没有xmlns声明有的话路径里必须带前缀。这是新手头号杀手。确认节点层级用selectNodes(//*)看看整棵树的节点全名单确定你要的节点叫什么、在哪一层。确认标签大小写XML是大小写敏感的Book和book是两个不同节点。确认属性名准确性属性是编号还是ID多用//*[*]把属性一并捞出来看。确认jaxen依赖函数用不了时90%是jaxen缺了。7.4 dom4j的线程安全性问题dom4j的Document对象不是线程安全的同一个Document不能被多个线程同时修改。多个线程如果只是并发读问题不大但只要有写操作必须加锁或者每个线程用各自的Document实例。我见过一个定时任务里有个共享的Document对象被多个任务线程同时改结果出过几次莫名其妙的节点丢失问题最后排查就是并发写导致数据结构错乱。如果你要解析的XML是不变的静态配置我建议你启动时加载一次转成自己定义的Java对象放到内存里后续操作全部基于Java对象彻底绕开Document并发问题。8. 写在最后还有一个值得尝试的扩展这个专题从头到尾把XML手写、dom4j读取、XPath定位、案例应用都过了一遍。最后我想分享一个我自己的体会学习XML解析不要只学API一定结合一个真实场景去练手。比如把你手头某个项目的配置文件用dom4j去读取一遍或者模拟做一个第三方XML接口的对接这个过程中你会碰到的编码、命名空间、格式校验问题才是真正值钱的实战经验。另外一个可以继续折腾的方向是用dom4j结合反射写一个通用XML转Java对象的工具类——通过XPath定位到XML中每一个业务字段再通过反射映射到实体类的属性上这样一个工具类就能服务整个项目里所有XML接口的解析省去大量重复的getText调用。不过这个工具要写好没那么容易先把手写XML和dom4j基础打扎实再慢慢往里加功能就好。

相关新闻

C#入门实战:100例带你从语法到WinForms与数据库开发

C#入门实战:100例带你从语法到WinForms与数据库开发

简介:面向C#初学者的“C#100例”是一份以100个编程实例串联基础语法、面向对象、集合、异常处理、LINQ、异步编程、多线程、反射、Windows Forms/WPF及网络编程等核心模块的教程资源,旨在帮助零基础读者从基本语法逐步过渡到实际项目开发,建立…

2026/10/15 16:38:48 阅读更多 →
Shopee 店群怎么做?低成本开店实操路径

Shopee 店群怎么做?低成本开店实操路径

Shopee店群作为移动端占比高,铺货与店群模式常见,站斧在东南亚是强项区域,Shopee 七站点覆盖完整。Shopee店铺做多之后,同站点多店之间如果共用网络与设备,关联判定很快 这篇围绕Shopee店群展开,先把店群运…

2026/10/15 16:38:48 阅读更多 →
纯 CSS + SVG 实现鹈鹕骑自行车 2D 动画 —— 前端创意动效实战

纯 CSS + SVG 实现鹈鹕骑自行车 2D 动画 —— 前端创意动效实战

> 本文将手把手教你使用纯 SVG CSS 动画,绘制一只可爱的鹈鹕骑自行车的 2D 动画场景。无需 JavaScript,无需任何第三方库,一个 HTML 文件即可搞定!## 一、效果预览最终效果:一只白白胖胖的鹈鹕骑着红色自行车&…

2026/10/15 16:38:48 阅读更多 →

最新新闻

Seldon Core 2 实战:用 Pipeline 把 CIFAR10 图像分类器与异常检测、漂移监控打包为生产级推理服务

Seldon Core 2 实战:用 Pipeline 把 CIFAR10 图像分类器与异常检测、漂移监控打包为生产级推理服务

人工智能大模型MLOpsLLMOps模型推理服务云原生后端 【免费下载链接】seldon-core An MLOps framework to package, deploy, monitor and manage thousands of production machine learning models 项目地址: https://gitcode.com/gh_mirrors/se/seldon-core 点击查看…

2026/10/15 17:32:52 阅读更多 →
树形DP实战:将功补过类问题的状态设计与转移方程

树形DP实战:将功补过类问题的状态设计与转移方程

1. 从标题拆解这道题到底在考什么 “将功补过”配上“树形DP”,这个组合一出来,有经验的选手基本就能嗅到味道了。这不是一道单纯的树上背包,也不是简单的最大独立集,它把“功”和“过”这两个对立的概念同时挂在树结构上&#xf…

2026/10/15 17:32:52 阅读更多 →
海波龙(Oracle 产品)实施商怎么选?7个评估维度+服务商能力对比

海波龙(Oracle 产品)实施商怎么选?7个评估维度+服务商能力对比

海波龙(Oracle 产品)实施商怎么选?7个评估维度服务商能力对比海波龙(Oracle 产品)选型时,企业往往把注意力放在产品模块和功能演示上,等到项目进入第二年,真正决定体验的却是当初挑的…

2026/10/15 17:32:52 阅读更多 →
STM32F103C8T6驱动SG90舵机:PWM源码与调试避坑指南

STM32F103C8T6驱动SG90舵机:PWM源码与调试避坑指南

简介:这份源码面向嵌入式初学者与需要快速验证舵机控制的开发者,围绕STM32F103C8T6如何驱动SG90舵机展开,解决从PWM信号生成到角度精确控制的实际问题。资源包共4个文件,包含2个C源文件、1个头文件与1张舵机接口示意图&#xff0c…

2026/10/15 17:32:52 阅读更多 →
ZLG Python二次开发实战:CAN总线数据采集与避坑指南

ZLG Python二次开发实战:CAN总线数据采集与避坑指南

简介:这份资源是面向嵌入式与物联网开发者的ZLG DTU系列Python二次开发示例包,重点围绕DTU 200UWGR及CAN-DTU系列无线数据传输单元,帮助解决官方demo在特定环境下无法直接运行、功能不足等问题。包内共124个文件,以98个xml配置与接…

2026/10/15 17:32:52 阅读更多 →
herdr-gpui Teleport 深度解析:带着未提交改动、运行中进程和 Agent 会话跨主机迁移工作树

herdr-gpui Teleport 深度解析:带着未提交改动、运行中进程和 Agent 会话跨主机迁移工作树

【免费下载链接】herdr-gpui Native client for Herdr on macOS, Linux, and Windows, built with Rust and GPUI. View terminal sessions, workspaces, Git worktrees, and agent activity through your local Herdr daemon. 项目地址: https://gitcode.com/gh_mi…

2026/10/15 17:31:51 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →