基于 XML 配置实现 Java 控制反转(IOC)的底层原理
在传统的 JavaEE 开发中,业务逻辑层(Service/Biz)通常需要手动获取持久层(DAO)的实例。传统的做法是通过工厂模式直接调用获取方法,但这会导致代码耦合度较高。通过引入控制反转(IOC)的思想,我们可以让容器自动将 DAO 注入到 Service 中,从而实现解耦。本文将探讨如何通过解析 XML 配置文件,结合 Java 反射机制手动实现一个简易的 IOC 容器。
基础:解析 XML 配置文件
在实现对象注入之前,首先需要掌握如何解析 XML 数据。Java 提供了多种 XML 解析方式,这里我们使用标准的 DOM 接口进行处理。以下是一个简单的 XML 示例及其解析逻辑。
<?xml version="1.0" encoding="UTF-8"?>
<registry>
<entity id="101">
<name>Archer</name>
<level>A</level>
</entity>
<entity id="102">
<name>Lancer</name>
<level>B</level>
</entity>
</registry>
对应的解析代码实现如下,通过递归遍历节点并根据节点类型输出内容:
public class SimpleXmlReader {
public static void main(String[] args) throws Exception {
InputStream is = SimpleXmlReader.class.getResourceAsStream("/registry.xml");
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(is);
Element root = doc.getDocumentElement();
traverseNodes(root);
}
private static void traverseNodes(Node node) {
short type = node.getNodeType();
if (type == Node.ELEMENT_NODE) {
Element el = (Element) node;
System.out.print("<" + el.getTagName());
NamedNodeMap attrs = el.getAttributes();
for (int i = 0; i < attrs.getLength(); i++) {
Node attr = attrs.item(i);
System.out.print(" " + attr.getNodeName() + "=\"" + attr.getNodeValue() + "\"");
}
System.out.print(">");
NodeList children = el.getChildNodes();
for (int j = 0; j < children.getLength(); j++) {
traverseNodes(children.item(j));
}
System.out.print("</" + el.getTagName() + ">");
} else if (type == Node.TEXT_NODE) {
String val = node.getNodeValue().trim();
if (!val.isEmpty()) System.out.print(val);
}
}
}
进阶:对象实例化与 Setter 注入
实现 IOC 的核心是利用反射。我们通过读取配置文件中的全类名,使用 Class.forName().newInstance() 创建实例。接着,通过解析 property 标签获取属性名和属性值,拼接成 Setter 方法名并进行调用。
定义一个简单的 Bean 类:
public class Member {
private String uid;
private String nickname;
public void setUid(String uid) { this.uid = uid; }
public void setNickname(String nickname) { this.nickname = nickname; }
@Override
public String toString() {
return "Member{uid='" + uid + "', nickname='" + nickname + "'}";
}
}
对应的 beans-config.xml 如下:
<beans>
<bean class="com.example.Member">
<property name="uid" value="U001" />
<property name="nickname" value="Echo" />
</bean>
</beans>
通过反射实现自动赋值的逻辑:
public void injectProperties(Object target, String fieldName, String fieldValue) {
try {
String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
Method[] methods = target.getClass().getMethods();
for (Method m : methods) {
if (m.getName().equals(methodName)) {
m.invoke(target, fieldValue);
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
实战:实现 Service 与 DAO 的依赖注入
在生产环境下,我们需要一个容器来管理单例对象。首先在 XML 中定义 Bean 的 ID、类路径以及依赖关系(使用 ref 属性)。
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="userDao" class="com.example.dao.UserDaoImpl" />
<bean id="userService" class="com.example.service.UserServiceImpl">
<property name="userDao" ref="userDao" />
</bean>
</beans>
构建 BeanFactory 容器,其初始化过程分为两步:
1. 扫描与实例化:遍历所有的 bean 标签,根据 class 属性创建对象,并存入 Map 缓存。
2. 依赖装配:再次遍历节点,解析 property 中的 ref 属性,从 Map 中提取已实例化的对象并注入。
public class BeanContainer {
private static Map<String, Object> singletonPool = new HashMap<>();
static {
loadBeans("/application-context.xml");
}
public static void loadBeans(String path) {
try {
InputStream is = BeanContainer.class.getResourceAsStream(path);
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
NodeList beanNodes = doc.getElementsByTagName("bean");
// 阶段一:实例化
for (int i = 0; i < beanNodes.getLength(); i++) {
Element node = (Element) beanNodes.item(i);
String id = node.getAttribute("id");
String clazz = node.getAttribute("class");
singletonPool.put(id, Class.forName(clazz).newInstance());
}
// 阶段二:装配依赖
for (int i = 0; i < beanNodes.getLength(); i++) {
Element node = (Element) beanNodes.item(i);
String id = node.getAttribute("id");
Object currentBean = singletonPool.get(id);
NodeList props = node.getElementsByTagName("property");
for (int j = 0; j < props.getLength(); j++) {
Element p = (Element) props.item(j);
String pName = p.getAttribute("name");
String pRef = p.getAttribute("ref");
if (!pRef.isEmpty()) {
Object dependency = singletonPool.get(pRef);
performInjection(currentBean, pName, dependency);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void performInjection(Object bean, String name, Object value) throws Exception {
String setterName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
Method method = bean.getClass().getMethod(setterName, value.getClass().getInterfaces()[0]);
// 简化处理,假设参数类型匹配接口
method.invoke(bean, value);
}
}
这种模式模仿了 Spring 框架早期的实现方式。在 Web 应用中,可以在 Servlet 容器启动时(如通过 ServletContextListener)触发该加载流程。虽然手动实现效率与完善度不及成熟框架,但通过这个过程可以深入理解 Java 反射机制在解耦设计中的关键作用。