简单模拟spring装载注入bean原理
前言:自己工作时间也将近9个月了,但还没系统学习过spring的知识,近来跟着马士兵老师的spring2.5的视频学,一步一个脚印的向前走。
主要步骤:
一 将需要被加载的类放入beans.xml中
二 通过jdom读取xml配置
三 通过反射机制,将beans.xml中对应类加载进来
beans.xml文件如下:
<beans>
<bean id="userDAO" class="org.zeng.dao.impl.UserDAOImpl" />
<bean id="userService" class="org.zeng.service.UserService">
<property name="userDAO" bean="userDAO"/>
</bean>
</beans>
UserService类代码如下:
package org.zeng.service;
import org.zeng.dao.UserDAO;
import org.zeng.dao.impl.UserDAOImpl;
import org.zeng.model.User;
public class UserService {
private UserDAO userDAO;
public UserDAO getUserDAO() {
return userDAO;
}
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
public void add(User u) {
this.userDAO.save(u);
}
}
#读取xml文件,及利用反射机制,加载类的代码:
package org.zeng.spring;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
public class ClassPathXMLApplicationContext implements BeanFactory {
private Map<String, Object> beans = new HashMap<String, Object>();
public ClassPathXMLApplicationContext() throws Exception {
SAXBuilder sb = new SAXBuilder();
Document doc = sb.build("src/beans.xml"); // 构造文档对象
Element root = doc.getRootElement(); // 获取根元素
List list = root.getChildren("bean");// 先遍历所有的bean,放入beans的map对象中
for (int i = 0; i < list.size(); i++) {
Element element = (Element) list.get(i);
String id = element.getAttributeValue("id");
String clazz = element.getAttributeValue("class");
Object o = Class.forName(clazz).newInstance();
beans.put(id, o);
}
for (int i = 0; i < list.size(); i++) { //在遍历一次xml文件,将含有property元素的bean的属性bean也装载进来(重复扫描,需要优化)
Element element = (Element) list.get(i);
String id = element.getAttributeValue("id");
String clazz = element.getAttributeValue("class");
Object o = Class.forName(clazz).newInstance();
List list1 = element.getChildren("property");
for (int j = 0; j < list1.size(); j++) {
Element e = (Element) list1.get(j);
String name = e.getAttributeValue("name");
String bean = e.getAttributeValue("bean"); // userDAO
Object object = beans.get(bean);// userDAOImpl的实例
System.out.println(object);
String methodName = "set" + name.substring(0, 1).toUpperCase()
+ name.substring(1, name.length());
Method m = o.getClass().getMethod(methodName,
object.getClass().getInterfaces()[0]); //第二个参数是参数类型,即获取该类实现的接口的类型
m.invoke(o, object);
beans.put(id, o);
}
}
}
public Object getBean(String beanName) {
return beans.get(beanName);
}
}
该类实现的接口(bean工厂)代码:
package org.zeng.spring;
import java.io.IOException;
import org.jdom.JDOMException;
public interface BeanFactory {
public Object getBean(String beanName);
}
总结: spring其中的一个重要核心IOC控制反转
(也基本同于与DI依赖注入
),大体是说,一些常被用到的类的实现,不需要开发者自己去控制,而全部交给sprintContext(理解为运行环境)去控制。
下面说一说自己理解的好处:
- 一般的,一个项目中至少有
实体类层
(Entity层)、业务逻辑层
(Service层)、数据访问层
(DAO层)这三层,而每层会有很多类,且这些类的成员属性也可能是其他类。如上代码,UserService下有UserDAO类型的属性,如果按照传统的方法,需要先new UserService()记为对象s,再new UserDAO()记为对象d,然后再将d对象set到对象s中,才能调用d对象的save方法!(很繁琐)。如果将这些类都配置在文件中,每次项目移动时一一的去装载这些类,放入到容器中,什么时候需要,什么时候取就可以了,没必要再new新的对象了。 - 在
面向接口编程
时,比如UserDAO的接口是专门操作User对象CRUD的接口,而实现类UserDAOImpl1确可以是mysql,或者是oracle的方式。
如果要切换数据库环境时,对访问数据层DAO来讲有3种方法:
(1). 要新增一个对应实现类UserDAOImpl2,并将调用UserDAO1地方全部改为调用UserDAO2。这样就会要改很多处代码,可能增加新的bug,显得并不安全。
(2). 干脆将UserDAOImpl2的代码完全copy到UserDAOImpl1上替换到原来的内容。即使将原来的内容进行备份,但对整个代码管理角度来讲,这种做法很不优雅。
(3). 在spring的配置文件中,将对UserDAO1的配置改为UserDAO2的配置(修改bean的路径即可)。这种是最好的做法,很简单,对源代码的改变也是最小的。
附注: 以上列出的是核心代码,还有User实体类,UserDAO接口,及其实现类UserDAOImpl代码,全部源代码在我的github上