Skip to content

Java 从 XML 到设计模式:零基础到精通指南(附代码详解)

XML 作为数据交换和配置的经典格式,与设计模式(代码设计的最佳实践)共同构成了 Java 开发的重要基础。本文从 XML 基础、解析技术到设计模式,通过实例代码逐步讲解,帮助零基础读者实现从入门到精通的跨越。

一、XML 基础:数据描述的通用语言

1. 什么是 XML?

XML(Extensible Markup Language,可扩展标记语言)是一种用于存储和传输数据的标记语言,其核心是自定义标签,允许开发者根据需求定义标签名,实现数据的结构化描述。

与 HTML 的区别

  • HTML 是预定义标签(如 <p><div>),用于展示数据;
  • XML 无预定义标签,专注于描述数据的结构和含义(如 <user><order>)。

2. XML 基本语法与结构

(1)基本规则

  • 标签成对出现:<tag>内容</tag>(空标签可简写为 <tag/>);
  • 嵌套有序:子标签必须完全包含在父标签内,不能交叉;
  • 大小写敏感:<User><user> 是不同标签;
  • 必须有根标签:整个 XML 有且仅有一个根标签。

(2)示例:用户信息 XML

xml
<?xml version="1.0" encoding="UTF-8"?> <!-- 声明:版本和编码 -->
<users> <!-- 根标签 -->
    <user id="1001"> <!-- 属性:id 描述用户唯一标识 -->
        <name>张三</name> <!-- 子标签:姓名 -->
        <age>25</age> <!-- 子标签:年龄 -->
        <address>
            <city>北京</city>
            <street>中关村</street>
        </address>
    </user>
    <user id="1002">
        <name>李四</name>
        <age>30</age>
        <address>
            <city>上海</city>
            <street>陆家嘴</street>
        </address>
    </user>
</users>

说明

  • <?xml ...?> 是 XML 声明,定义版本和编码;
  • <user id="1001">id 是属性,用于补充标签信息;
  • 标签嵌套形成层次结构,清晰描述数据间的关系。

3. XML 的应用场景

  • 配置文件:Java 框架(如 Spring、MyBatis)用 XML 配置Bean、映射关系等;
  • 数据交换:不同系统间通过 XML 传输数据(如接口调用返回 XML 格式结果);
  • 存储数据:小型应用用 XML 替代数据库存储简单数据。

二、XML 解析:Java 读取 XML 数据的四种方式

Java 中解析 XML 的核心是将 XML 文档转换为程序可操作的对象。常用解析方式有 DOM、SAX、JDOM、DOM4J,各有适用场景。

1. DOM 解析:基于文档对象模型

DOM(Document Object Model)将整个 XML 文档加载到内存,生成一个树形结构(文档对象模型),允许对节点进行增删改查。

特点

  • 优点:支持随机访问(可任意操作任意节点)、支持修改 XML;
  • 缺点:加载整个文档到内存,适合小型 XML(大型 XML 可能导致内存溢出)。

代码示例:DOM 解析用户 XML

java
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;

public class DomParserDemo {
    public static void main(String[] args) throws Exception {
        // 1. 创建 DOM 解析器工厂
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        // 2. 获取解析器
        DocumentBuilder builder = factory.newDocumentBuilder();
        // 3. 解析 XML 文件,生成文档对象
        Document document = builder.parse(new File("src/users.xml"));

        // 4. 获取根标签 <users>
        Element root = document.getDocumentElement();
        System.out.println("根标签:" + root.getNodeName());

        // 5. 获取所有 <user> 标签
        NodeList userNodes = root.getElementsByTagName("user");
        System.out.println("用户数量:" + userNodes.getLength());

        // 6. 遍历用户节点
        for (int i = 0; i < userNodes.getLength(); i++) {
            Element userElement = (Element) userNodes.item(i);
            // 获取属性 id
            String id = userElement.getAttribute("id");
            // 获取子标签内容
            String name = userElement.getElementsByTagName("name").item(0).getTextContent();
            String age = userElement.getElementsByTagName("age").item(0).getTextContent();
            // 获取嵌套子标签 <city>
            String city = userElement.getElementsByTagName("city").item(0).getTextContent();

            System.out.printf("用户ID:%s,姓名:%s,年龄:%s,城市:%s%n", id, name, age, city);
        }
    }
}

输出结果

根标签:users
用户数量:2
用户ID:1001,姓名:张三,年龄:25,城市:北京
用户ID:1002,姓名:李四,年龄:30,城市:上海

2. SAX 解析:基于事件驱动

SAX(Simple API for XML)是一种事件驱动的解析方式,逐行扫描 XML 文档,触发startElement、endElement等事件,无需加载整个文档到内存。

特点

  • 优点:内存占用小,适合大型 XML(如几GB的日志文件);
  • 缺点:仅支持读取(无法修改),不支持随机访问(只能顺序解析)。

代码示例:SAX 解析用户 XML

java
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.File;

// 自定义处理器:重写事件方法
class UserHandler extends DefaultHandler {
    private boolean isName = false;
    private boolean isAge = false;
    private boolean isCity = false;
    private String currentId;

    // 开始解析标签时触发
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        if (qName.equals("user")) {
            // 获取 user 标签的 id 属性
            currentId = attributes.getValue("id");
            System.out.println("用户ID:" + currentId);
        } else if (qName.equals("name")) {
            isName = true; // 标记即将读取 name 内容
        } else if (qName.equals("age")) {
            isAge = true;
        } else if (qName.equals("city")) {
            isCity = true;
        }
    }

    // 读取标签内容时触发
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        if (isName) {
            System.out.println("姓名:" + new String(ch, start, length));
            isName = false; // 重置标记
        } else if (isAge) {
            System.out.println("年龄:" + new String(ch, start, length));
            isAge = false;
        } else if (isCity) {
            System.out.println("城市:" + new String(ch, start, length));
            isCity = false;
        }
    }
}

public class SaxParserDemo {
    public static void main(String[] args) throws Exception {
        // 1. 创建 SAX 解析器工厂
        SAXParserFactory factory = SAXParserFactory.newInstance();
        // 2. 获取解析器
        SAXParser saxParser = factory.newSAXParser();
        // 3. 解析 XML,使用自定义处理器
        saxParser.parse(new File("src/users.xml"), new UserHandler());
    }
}

输出结果

用户ID:1001
姓名:张三
年龄:25
城市:北京
用户ID:1002
姓名:李四
年龄:30
城市:上海

3. DOM4J 解析:高效易用的主流方案

DOM4J 是一个开源解析框架(需导入依赖),结合了 DOM 和 SAX 的优点,支持 XPath 快速查询,是实际开发中最常用的解析方式。

(1)导入 DOM4J 依赖(Maven)

xml
<dependency>
    <groupId>org.dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>2.1.4</version>
</dependency>
<!-- 支持 XPath -->
<dependency>
    <groupId>jaxen</groupId>
    <artifactId>jaxen</artifactId>
    <version>1.2.0</version>
</dependency>

(2)代码示例:DOM4J 解析 + XPath 查询

java
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.util.List;

public class Dom4jParserDemo {
    public static void main(String[] args) throws DocumentException {
        // 1. 创建 SAXReader(DOM4J 的解析器)
        SAXReader reader = new SAXReader();
        // 2. 读取 XML 文件
        Document document = reader.read(new File("src/users.xml"));

        // 3. 获取根标签
        Element root = document.getRootElement();
        System.out.println("根标签:" + root.getName());

        // 4. 遍历所有 user 标签(传统方式)
        List<Element> userElements = root.elements("user");
        for (Element user : userElements) {
            String id = user.attributeValue("id");
            String name = user.elementText("name"); // 直接获取子标签文本
            System.out.printf("传统遍历:ID=%s,姓名=%s%n", id, name);
        }

        // 5. 使用 XPath 快速查询(推荐)
        // 查询所有 user 标签中 city 为 "北京" 的 name
        List<Element> beijingUsers = root.selectNodes("//user[address/city='北京']/name");
        for (Element name : beijingUsers) {
            System.out.println("北京用户:" + name.getText()); // 输出:张三
        }

        // 查询 id=1002 的 user 的 age
        Element ageElement = (Element) root.selectSingleNode("//user[@id='1002']/age");
        System.out.println("用户1002的年龄:" + ageElement.getText()); // 输出:30
    }
}

XPath 语法说明

  • //user:匹配所有 user 标签(无论层级);
  • user[@id='1002']:匹配 id 属性为 1002 的 user 标签;
  • address/city='北京':筛选 address 子标签中 city 为北京的节点。

4. 四种解析方式对比与选择

解析方式优点缺点适用场景
DOM支持增删改查、随机访问内存占用大小型 XML、需修改数据
SAX内存占用小、速度快仅支持读取、顺序访问大型 XML、仅需读取数据
JDOM简化 DOM API、纯 Java 实现功能较少中等规模 XML
DOM4J支持 XPath、功能全面、易用需导入第三方依赖大部分场景(推荐)

三、设计模式:Java 代码设计的最佳实践

设计模式是对反复出现的问题的标准化解决方案,目的是提高代码的可复用性、可维护性和扩展性。Java 中常用的设计模式有 23 种,这里重点讲解 5 种基础且高频使用的模式。

1. 单例模式(Singleton Pattern)

意图:确保一个类仅有一个实例,并提供全局访问点(如工具类、配置管理器)。

(1)饿汉式(线程安全,简单)

java
public class SingletonHungry {
    // 1. 私有构造器:防止外部创建实例
    private SingletonHungry() {}

    // 2. 私有静态实例(类加载时初始化)
    private static final SingletonHungry INSTANCE = new SingletonHungry();

    // 3. 公开静态方法:返回唯一实例
    public static SingletonHungry getInstance() {
        return INSTANCE;
    }
}

(2)懒汉式(延迟初始化,线程安全版)

java
public class SingletonLazy {
    private SingletonLazy() {}

    // volatile 防止指令重排序
    private static volatile SingletonLazy instance;

    // 双重检查锁(DCL)保证线程安全
    public static SingletonLazy getInstance() {
        if (instance == null) { // 第一次检查:减少锁竞争
            synchronized (SingletonLazy.class) { // 加锁
                if (instance == null) { // 第二次检查:防止多线程同时创建
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
}

应用场景:日志工具类(Logger)、数据库连接池(DataSource)。

2. 简单工厂模式(Simple Factory)

意图:通过一个工厂类统一创建不同类型的对象,隐藏对象创建的细节。

代码示例:计算器工厂

java
// 1. 抽象产品:运算
interface Operation {
    double calculate(double a, double b);
}

// 2. 具体产品:加法
class AddOperation implements Operation {
    @Override
    public double calculate(double a, double b) {
        return a + b;
    }
}

// 具体产品:减法
class SubtractOperation implements Operation {
    @Override
    public double calculate(double a, double b) {
        return a - b;
    }
}

// 3. 工厂类:创建运算对象
class OperationFactory {
    // 根据类型创建对应运算实例
    public static Operation createOperation(String type) {
        switch (type) {
            case "+": return new AddOperation();
            case "-": return new SubtractOperation();
            default: throw new IllegalArgumentException("无效运算类型");
        }
    }
}

// 测试
public class SimpleFactoryDemo {
    public static void main(String[] args) {
        // 无需直接 new 对象,通过工厂创建
        Operation add = OperationFactory.createOperation("+");
        System.out.println("3+5=" + add.calculate(3, 5)); // 8

        Operation subtract = OperationFactory.createOperation("-");
        System.out.println("10-4=" + subtract.calculate(10, 4)); // 6
    }
}

优点:客户端无需知道对象创建细节,只需调用工厂方法,符合“单一职责原则”。

3. 观察者模式(Observer Pattern)

意图:定义对象间的一对多依赖关系,当一个对象状态变化时,所有依赖它的对象会自动收到通知(如事件监听、消息订阅)。

代码示例:天气预报系统

java
import java.util.ArrayList;
import java.util.List;

// 1. 被观察者接口(主题)
interface Subject {
    void registerObserver(Observer observer); // 注册观察者
    void removeObserver(Observer observer);  // 移除观察者
    void notifyObservers();                 // 通知所有观察者
}

// 2. 观察者接口
interface Observer {
    void update(float temperature); // 接收更新
}

// 3. 具体被观察者:天气预报站
class WeatherStation implements Subject {
    private float temperature; // 温度(被观察的状态)
    private List<Observer> observers = new ArrayList<>();

    public void setTemperature(float temperature) {
        this.temperature = temperature;
        notifyObservers(); // 温度变化时通知观察者
    }

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        // 通知所有观察者
        for (Observer observer : observers) {
            observer.update(temperature);
        }
    }
}

// 4. 具体观察者:手机客户端
class PhoneDisplay implements Observer {
    @Override
    public void update(float temperature) {
        System.out.println("手机显示:当前温度 " + temperature + "℃");
    }
}

// 具体观察者:电脑客户端
class ComputerDisplay implements Observer {
    @Override
    public void update(float temperature) {
        System.out.println("电脑显示:当前温度 " + temperature + "℃");
    }
}

// 测试
public class ObserverDemo {
    public static void main(String[] args) {
        // 创建被观察者
        WeatherStation station = new WeatherStation();
        // 创建观察者
        Observer phone = new PhoneDisplay();
        Observer computer = new ComputerDisplay();

        // 注册观察者
        station.registerObserver(phone);
        station.registerObserver(computer);

        // 温度变化(触发通知)
        station.setTemperature(25.5f);
        // 输出:
        // 手机显示:当前温度 25.5℃
        // 电脑显示:当前温度 25.5℃
    }
}

应用场景:GUI 事件监听(如按钮点击)、消息队列订阅。

4. 策略模式(Strategy Pattern)

意图:定义一系列算法,将每个算法封装起来,使它们可互相替换(如不同的排序算法、支付方式)。

代码示例:支付系统

java
// 1. 策略接口:支付方式
interface PaymentStrategy {
    void pay(double amount);
}

// 2. 具体策略:支付宝支付
class AlipayStrategy implements PaymentStrategy {
    private String username;

    public AlipayStrategy(String username) {
        this.username = username;
    }

    @Override
    public void pay(double amount) {
        System.out.println(username + "使用支付宝支付 " + amount + "元");
    }
}

// 具体策略:微信支付
class WechatPayStrategy implements PaymentStrategy {
    private String openId;

    public WechatPayStrategy(String openId) {
        this.openId = openId;
    }

    @Override
    public void pay(double amount) {
        System.out.println(openId + "使用微信支付 " + amount + "元");
    }
}

// 3. 上下文:订单(使用策略)
class Order {
    private PaymentStrategy paymentStrategy;

    // 设置支付策略(灵活切换)
    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }

    // 支付
    public void checkout(double amount) {
        paymentStrategy.pay(amount);
    }
}

// 测试
public class StrategyDemo {
    public static void main(String[] args) {
        Order order = new Order();

        // 选择支付宝支付
        order.setPaymentStrategy(new AlipayStrategy("zhangsan@alipay.com"));
        order.checkout(199.9); // 输出:zhangsan@alipay.com使用支付宝支付 199.9元

        // 切换为微信支付
        order.setPaymentStrategy(new WechatPayStrategy("wx123456"));
        order.checkout(99.5); // 输出:wx123456使用微信支付 99.5元
    }
}

优点:算法可独立于客户端变化,新增支付方式只需实现 PaymentStrategy,无需修改订单类(符合“开闭原则”)。

5. 工厂方法模式(Factory Method)

意图:定义一个创建对象的接口,但让子类决定实例化哪个类(比简单工厂更灵活,支持扩展)。

代码示例:文档编辑器

java
// 1. 产品接口:文档
interface Document {
    void open();
}

// 2. 具体产品:文本文档
class TextDocument implements Document {
    @Override
    public void open() {
        System.out.println("打开文本文档");
    }
}

// 具体产品:表格文档
class SpreadsheetDocument implements Document {
    @Override
    public void open() {
        System.out.println("打开表格文档");
    }
}

// 3. 工厂接口:文档工厂
interface DocumentFactory {
    Document createDocument(); // 工厂方法:创建文档
}

// 4. 具体工厂:文本工厂
class TextDocumentFactory implements DocumentFactory {
    @Override
    public Document createDocument() {
        return new TextDocument();
    }
}

// 具体工厂:表格工厂
class SpreadsheetDocumentFactory implements DocumentFactory {
    @Override
    public Document createDocument() {
        return new SpreadsheetDocument();
    }
}

// 测试
public class FactoryMethodDemo {
    public static void main(String[] args) {
        // 创建文本文档(通过文本工厂)
        DocumentFactory textFactory = new TextDocumentFactory();
        Document textDoc = textFactory.createDocument();
        textDoc.open(); // 打开文本文档

        // 创建表格文档(通过表格工厂)
        DocumentFactory sheetFactory = new SpreadsheetDocumentFactory();
        Document sheetDoc = sheetFactory.createDocument();
        sheetDoc.open(); // 打开表格文档
    }
}

与简单工厂的区别

  • 简单工厂由一个工厂类创建所有产品,新增产品需修改工厂类;
  • 工厂方法为每个产品对应一个工厂类,新增产品只需新增工厂类(符合“开闭原则”)。

四、综合实战:XML 配置 + 工厂模式实现灵活扩展

结合 XML 和设计模式,实现一个可通过 XML 配置动态切换日志输出方式的系统。

1. 定义日志接口与实现类

java
// 日志接口
interface Logger {
    void log(String message);
}

// 控制台日志
class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("控制台日志:" + message);
    }
}

// 文件日志
class FileLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("文件日志:" + message); // 实际会写入文件
    }
}

2. 编写 XML 配置文件(指定日志类型)

xml
<?xml version="1.0" encoding="UTF-8"?>
<config>
    <loggerType>console</loggerType> <!-- 可改为 file 切换日志方式 -->
</config>

3. 工厂类:通过 XML 配置创建日志对象

java
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

public class LoggerFactory {
    public static Logger createLogger() throws DocumentException {
        // 1. 解析 XML 配置
        SAXReader reader = new SAXReader();
        Document document = reader.read("src/config.xml");
        Element root = document.getRootElement();
        String loggerType = root.elementText("loggerType");

        // 2. 根据配置创建对应日志对象
        if ("console".equals(loggerType)) {
            return new ConsoleLogger();
        } else if ("file".equals(loggerType)) {
            return new FileLogger();
        } else {
            throw new IllegalArgumentException("无效的日志类型");
        }
    }
}

4. 测试:动态切换日志方式

java
import org.dom4j.DocumentException;

public class LoggerDemo {
    public static void main(String[] args) throws DocumentException {
        // 从工厂获取日志对象(由 XML 配置决定类型)
        Logger logger = LoggerFactory.createLogger();
        logger.log("系统启动成功");

        // 若修改 config.xml 中 loggerType 为 file,输出变为:
        // 文件日志:系统启动成功
    }
}

优势:无需修改代码,仅通过修改 XML 配置即可切换日志输出方式,体现了设计模式的“开闭原则”(对扩展开放,对修改关闭)。

五、总结与进阶路线

1. 核心收获

  • XML:掌握基本语法和四种解析方式,重点理解 DOM4J + XPath 的高效查询;
  • 设计模式:理解单例、工厂、观察者等模式的意图和应用场景,能通过模式解决实际问题。

2. 进阶建议

  • XML:学习 XML Schema(XSD)验证 XML 格式,掌握 JAXB 实现 XML 与 Java 对象的自动转换;
  • 设计模式:深入学习装饰器模式、代理模式(与 Spring AOP 相关)、迭代器模式(集合框架底层),阅读《设计模式:可复用面向对象软件的基础》;
  • 实战:分析 Spring 框架源码中的设计模式(如 BeanFactory 是工厂模式,事件机制是观察者模式),加深理解。

通过 XML 解析获取配置信息,结合设计模式实现灵活扩展,是 Java 框架(如 Spring、MyBatis)的核心思想。掌握这些知识,能帮助你从“会写代码”提升到“写出高质量代码”。

Released under the MIT License.