本文示範如何從零構建一個輕量級依賴注入(DI)容器,支援三種注入類型:
核心技術棧:Dom4j 解析 XML(採用 ElementHandler 責任鏈機制)+ Java 反射(直接操作 Field 賦值,繞過 Setter 型別轉換難題)。
<?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>
// 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 */ }
private Map<String, Object> beanMap = new HashMap<>();
public Object getBean(String beanId) { return beanMap.get(beanId); }
public void init(String xmlUri) throws Exception {
SAXReader saxReader = new SAXReader();
saxReader.addHandler("/beans/bean", new BeanHandler());
saxReader.read(new File(xmlUri));
}
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");
}
}
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 處理流程圖
為何不呼叫 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(); }
}