首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java JDOM 解析 XML 實戰:從配置文件到 Bean 工廠的完整實現

     編輯:Java 程式設計入門

JDOM 簡介

  • 純 Java XML 解析庫,樹模型、API 直觀、支援 XPath、命名空間
  • 較 DOM 更簡潔,較 SAX 更易用,適合中小型 XML 文檔
  • Maven 依賴:org.jdom:jdom2:2.0.6

場景:XML 配置驅動的 Bean 工廠

模擬 Spring 核心機制:解析 XML → 反射創建 Bean → 註冊容器 → 獲取使用

領域模型

// 統一行為介面
public interface Moveable {
    void run();
}

// 實現類
public class Plane implements Moveable {
    @Override public void run() { System.out.println("飛機在翱翔....."); }
}

public class Train implements Moveable {
    @Override public void run() { System.out.println("火車在飛奔...."); }
}

XML 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="train" class="com.example.xml.Train"/>
    <bean id="plane" class="com.example.xml.Plane"/>
</beans>

Bean 工廠介面

public interface BeanFactory {
    Object getBean(String id);
}

JDOM 解析實現

public class ClassPathXmlApplicationContext implements BeanFactory {
    private final Map beanContainer = new HashMap<>();
    
    public ClassPathXmlApplicationContext(String xmlPath) throws Exception {
        // 1. 創建 SAXBuilder(JDOM 解析器)
        SAXBuilder builder = new SAXBuilder();
        
        // 2. 從 classpath 讀取輸入流
        InputStream is = getClass().getClassLoader().getResourceAsStream(xmlPath);
        if (is == null) throw new IllegalArgumentException("XML not found: " + xmlPath);
        
        // 3. 構建 Document 物件樹
        Document document = builder.build(is);
        
        // 4. 獲取根元素
        Element root = document.getRootElement();  // 
        
        // 5. XPath 選擇所有 bean 元素
        List beanElements = XPathFactory.instance()
            .compile("/beans/bean", Filters.element())
            .evaluate(root);
        
        // 6. 遍歷解析並實例化
        for (Element beanElem : beanElements) {
            String id = beanElem.getAttributeValue("id");
            String className = beanElem.getAttributeValue("class");
            
            if (id == null || className == null) {
                throw new IllegalArgumentException("bean 缺少 id 或 class: " + beanElem);
            }
            
            // 反射創建實例
            Class clazz = Class.forName(className);
            Object bean = clazz.getDeclaredConstructor().newInstance();
            
            // 註冊到容器
            beanContainer.put(id, bean);
            System.out.println("註冊 Bean: " + id + " -> " + className);
        }
    }
    
    @Override
    public Object getBean(String id) {
        return beanContainer.get(id);
    }
}

主程序測試

public class Main {
    public static void main(String[] args) throws Exception {
        // 初始化容器(解析 XML、創建 Bean)
        BeanFactory factory = new ClassPathXmlApplicationContext("beans.xml");
        
        // 獲取 Bean 並使用
        Moveable train = (Moveable) factory.getBean("train");
        train.run();  // 輸出: 火車在飛奔....
        
        Moveable plane = (Moveable) factory.getBean("plane");
        plane.run();  // 輸出: 飛機在翱翔.....
    }
}

進階功能擴展

1. 屬性注入(Setter/構造器)

<bean id="car" class="com.example.Car">
    <property name="brand" value="Toyota"/>
    <property name="engine" ref="engine"/>
</bean>
<bean id="engine" class="com.example.Engine"/>

// 解析邏輯擴展
Element beanElem = ...;
Object bean = clazz.getDeclaredConstructor().newInstance();

// 處理 
List props = beanElem.getChildren("property");
for (Element prop : props) {
    String name = prop.getAttributeValue("name");
    String value = prop.getAttributeValue("value");
    String ref = prop.getAttributeValue("ref");
    
    Object propValue = (ref != null) ? beanContainer.get(ref) : convertValue(value, targetType);
    // 反射調用 setter
    String setter = "set" + Character.toUpperCase(name.charAt(0)) + name.substring(1);
    clazz.getMethod(setter, propValue.getClass()).invoke(bean, propValue);
}

2. 單例/原型作用域

<bean id="singletonBean" class="..." scope="singleton"/>
<bean id="prototypeBean" class="..." scope="prototype"/>

// 容器邏輯
String scope = beanElem.getAttributeValue("scope", "singleton");
if ("prototype".equals(scope)) {
    // 每次 getBean 重新創建
    return clazz.getDeclaredConstructor().newInstance();
} else {
    // 單例:容器持有
    return beanContainer.computeIfAbsent(id, k -> createBean(beanElem));
}

3. 生命週期回調

<bean id="service" class="..." init-method="init" destroy-method="close"/>

// 初始化
if (initMethod != null) clazz.getMethod(initMethod).invoke(bean);

// 銷毀(容器關閉時)
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    beanContainer.values().forEach(b -> {
        try { destroyMethod.invoke(b); } catch (Exception ignored) {}
    });
}));

JDOM vs 其他解析器選型

最佳實踐總結

  1. 輸入流來源抽象化:classpath、文件系統、網絡、字串統一用 InputStream
  2. XPath 簡化導航:避免手寫遞歸遍歷 Element
  3. 異常處理分層:解析異常、反射異常、類型轉換異常分別捕獲、包裝業務異常
  4. 命名空間處理:生產級 XML 帶命名空間,XPath 需註冊 NamespaceContext
  5. 考慮遷移:新專案優先考慮 JAXB 或 Jackson XML,JDOM 適合輕量級、無 Schema 場景

總結

JDOM 以直觀的樹模型 API、強大的 XPath 支援、純 Java 實現,成為中小型 XML 解析的優秀選擇。本文通過「XML 配置 → 反射實例化 → 容器管理」的完整鏈路,展示了框架核心機制的縮影。理解這一過程,有助於深入掌握 Spring、MyBatis 等主流框架的初始化原理。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
解析器模型適用場景記憶體
DOM / JDOM樹中小文檔、需隨機存取、修改寫回整個文檔載入記憶體
SAX事件流大文檔、單次順序讀取、低記憶體極低
StAX拉取流大文檔、需控制解析流程低
JAXB綁定XML⇄物件映射、Schema 驅動中等
dom4j樹類 JDOM、XPath 支援更強、Hibernate 早期使用整個文檔