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

從零手寫依賴注入容器:基於 XML 配置與反射機制的實現

     編輯:Java 相關知識

前言:模仿 Spring 核心 IoC 容器

本文示範如何從零構建一個輕量級依賴注入(DI)容器,支援三種注入類型:

  1. 單值注入: 基本型別(int、float、double、char 等)與 String
  2. 集合注入: List、Set、Map 三大 Java 容器
  3. Bean 物件注入: 任意 JavaBean 實例之間的引用關係

核心技術棧:Dom4j 解析 XML(採用 ElementHandler 責任鏈機制)+ Java 反射(直接操作 Field 賦值,繞過 Setter 型別轉換難題)。

XML 配置檔範例(setting.xml)

<?xml version="1.0" encoding="UTF-8"?>
<beans>
  <!-- 單值注入 -->
  <bean id="me" class="com.zj.ioc.di.imp.Person">
    <property name="name"><value>ZJ</value></property>
    <property name="age"><value>26</value></property>
    <property name="height"><value>1.78</value></property>
  </bean>

  <!-- 集合注入 -->
  <bean id="myList" class="com.zj.ioc.di.imp.ListOne">
    <property name="msg">
      <list><value>java</value><value>c</value><value>windows</value></list>
    </property>
  </bean>
  <bean id="mySet" class="com.zj.ioc.di.imp.SetOne">
    <property name="msg">
      <set><value>tom</value><value>cat</value><value>dog</value></set>
    </property>
  </bean>
  <bean id="myMap" class="com.zj.ioc.di.imp.MapOne">
    <property name="msg">
      <map>
        <entry key="c"><value>CHINA</value></entry>
        <entry key="j"><value>JAPAN</value></entry>
        <entry key="k"><value>KOREA</value></entry>
      </map>
    </property>
  </bean>

  <!-- Bean 物件引用注入 -->
  <bean id="us" class="com.zj.ioc.di.imp.Persons">
    <property name="i"><ref bean="me"/></property>
    <property name="u"><ref bean="you"/></property>
  </bean>
</beans>

領域模型類別(POJO)

// Person.java
public class Person {
    private String name; private int age; private float height;
    // getter/setter 省略
}

// ListOne.java
public class ListOne { private List<String> msg; /* getter/setter */ }

// SetOne.java
public class SetOne { private Set<String> msg; /* getter/setter */ }

// MapOne.java
public class MapOne { private Map<String,String> msg; /* getter/setter */ }

// Persons.java
public class Persons { private Person i; private Person u; /* getter/setter */ }

核心容器實現:BeanFactory

1. Bean 快取 Map

private Map<String, Object> beanMap = new HashMap<>();

public Object getBean(String beanId) { return beanMap.get(beanId); }

2. 初始化:啟動 Dom4j SAX 解析並註冊 Handler

public void init(String xmlUri) throws Exception {
    SAXReader saxReader = new SAXReader();
    saxReader.addHandler("/beans/bean", new BeanHandler());
    saxReader.read(new File(xmlUri));
}

3. BeanHandler:處理 <bean> 元素,實例化物件並註冊屬性 Handler

private class BeanHandler implements ElementHandler {
    private Object obj;

    public void onStart(ElementPath path) {
        Element beanEle = path.getCurrent();
        Class<?> clazz = Class.forName(beanEle.attributeValue("class"));
        obj = clazz.newInstance();

        // 收集所有 Field 供後續屬性注入使用
        Map<String, Field> fieldMap = new HashMap<>();
        for (Field f : clazz.getDeclaredFields()) fieldMap.put(f.getName(), f);

        // 註冊巢狀 property Handler
        path.addHandler("property", new PropertyHandler(fieldMap, obj));
    }

    public void onEnd(ElementPath path) {
        String id = path.getCurrent().attributeValue("id");
        beanMap.put(id, obj); // 存入容器
        path.removeHandler("property");
    }
}

4. PropertyHandler:分派 <value>、<list>、<set>、<map>、<ref> 到對應 Handler

private class PropertyHandler implements ElementHandler {
    private Map<String, Field> fieldMap;
    private Object targetObj;

    public PropertyHandler(Map<String, Field> fm, Object obj) {
        this.fieldMap = fm; this.targetObj = obj;
    }

    public void onStart(ElementPath path) {
        String propName = path.getCurrent().attributeValue("name");
        // 註冊五種子元素 Handler
        path.addHandler("value", new ValueHandler(fieldMap, targetObj, propName));
        path.addHandler("list", new ListHandler(fieldMap, targetObj, propName));
        path.addHandler("set", new SetHandler(fieldMap, targetObj, propName));
        path.addHandler("map", new MapHandler(fieldMap, targetObj, propName));
        path.addHandler("ref", new RefHandler(fieldMap, targetObj, propName));
    }

    public void onEnd(ElementPath path) {
        path.removeHandler("value");
        path.removeHandler("list");
        path.removeHandler("set");
        path.removeHandler("map");
        path.removeHandler("ref");
    }
}

Handler 處理流程圖

各注入元素對應的 Handler 處理流程圖

5. 核心賦值邏輯:setFieldValue 利用反射直接操作 Field

為何不呼叫 Setter?因為 XML 解析出的皆為 String,動態轉換為目標型別(int、float 等)並透過 Method.invoke 傳遞極易失敗(Integer 不能傳給 int 參數)。直接用 Field.setXxx 繞過此問題。

private void setFieldValue(Object obj, Field field, String value) {
    String type = field.getType().getSimpleName();
    try {
        if ("int".equals(type)) field.setInt(obj, Integer.parseInt(value));
        else if ("float".equals(type)) field.setFloat(obj, Float.parseFloat(value));
        else if ("boolean".equals(type)) field.setBoolean(obj, Boolean.parseBoolean(value));
        else if ("char".equals(type)) field.setChar(obj, value.charAt(0));
        else if ("double".equals(type)) field.setDouble(obj, Double.parseDouble(value));
        else if ("long".equals(type)) field.setLong(obj, Long.parseLong(value));
        else field.set(obj, value); // String 及其他
    } catch (Exception e) { e.printStackTrace(); }
}

private void setFieldValue(Object obj, Field field, List<String> list) {
    try { field.set(obj, list); } catch (Exception e) { e.printStackTrace(); }
}
// Set/Map/Ref 類似,RefHandler 直接從 beanMap 獲取已建立的 Bean 實例注入

測試驗證

public static void main(String[] args) {
    try {
        BeanFactory factory = new BeanFactory();
        factory.init("setting.xml");

        Person p1 = (Person) factory.getBean("me");
        System.out.println(p1.getName() + " " + p1.getAge() + " " + p1.getHeight());
        // ZJ 26 1.78

        ListOne list = (ListOne) factory.getBean("myList");
        System.out.println(list.getMsg()); // [java, c, windows]

        SetOne set = (SetOne) factory.getBean("mySet");
        System.out.println(set.getMsg()); // [cat, tom, dog]

        MapOne map = (MapOne) factory.getBean("myMap");
        System.out.println(map.getMsg()); // {c=CHINA, j=JAPAN, k=KOREA}

        Persons us = (Persons) factory.getBean("us");
        System.out.println(us.getI()); // Person@hash1
        System.out.println(us.getU()); // Person@hash2
    } catch (Exception e) { e.printStackTrace(); }
}

總結與擴展方向

  • 採用 Dom4j ElementHandler(責任鏈) 實現流式解析,記憶體佔用低,效能佳。
  • Field 直接注入 解決了基本型別轉換與 Setter 簽名不匹配的難題。
  • 可擴展支援:構造器注入、注解驅動、循環依賴檢測、Bean 後處理器、AOP 整合等。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved