简单的分析了一下spring bean的加载原理,属于个人的理解,源码比这个要复杂的多:
spring的配置文件的内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!-- 用户dao类,就是一个普通的pojo,里面有个addUser方法,调用会输出一行字 -->
<bean id="userDao" class=""></bean>
</beans>
非web环境下我们通常这么来加载IOC容器,获取bean:
BeanFactory ac = new ClassPathXmlApplicationContext("");
IUserDao userDao = ac.getBean("userDao");
所以,我简单的实现了一下这个BeanFactory接口和ClassPathXmlApplicationContext类(不过一般上是用ApplicationContext这个接口来接收ClassPathXmlApplicationContext,不过就本例来说没有太大的区别)。
BeanFactory接口的代码如下:
package com.qjl.study.spring.factory;
/**
* 类名称: bean工厂
* 类描述: 实例化各种bean
* 全限定性类名:
* @author MrQJL
* @date 2018年1月3日 下午9:46:30
* @version V1.0
*/
public interface BeanFactory {
/**
* 通过bean的id获取实例化的bean对象
* 获取bean的时候,该bean可能不存在,所以要抛出异常
* @param bean的名称
* @return 实例化的bean对象
*/
Object getBean(String name) throws Exception;
}
我在BeanFactory里面就写了一个getBean(String name)方法,用于输入bean的id,返回对应的实例,因为输入的bean的id可能不存在,所以要抛出异常。
ClassPathXmlApplicationContext类实现了BeanFactory接口,代码如下:
package com.qjl.study.spring.factory.impl;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import com.qjl.study.spring.factory.BeanFactory;
/**
* 类名称: 上下文对象
* 类描述: 用于获取编译路径下xml文件所对应的bean的IOC容器
* 全限定性类名:
* @author MrQJL
* @date 2018年1月3日 下午10:04:50
* @version V1.0
*/
public class ClassPathXmlApplicationContext implements BeanFactory {
/**
* 这就是那个IOC容器
*/
private Map<String, Object> beans = new HashMap<String, Object>();
public ClassPathXmlApplicationContext(String configLocation) throws Exception {
// 使用jdom的SAXBuilder读取xml文件
SAXBuilder sb = new SAXBuilder();
// 加载xml文档进内存
Document doc = sb.build(this.getClass().getClassLoader()
.getResourceAsStream(configLocation));
// 获取根节点--也就是beans
Element root = doc.getRootElement();
// 获取根节点的孩子节点--也就是bean
@SuppressWarnings("unchecked")
List<Object> childList = root.getChildren("bean");
// 循环取出每一个bean节点以及他们的id和class属性,利用反射创建一个对象
for (int i = 0; i < childList.size(); i++) {
Element child = (Element) childList.get(i);
// 获取id属性
String id = child.getAttributeValue("id");
// 获取class属性
String clazz = child.getAttributeValue("class");
// 通过反射加载类,实例化bean对象
Object obj = Class.forName(clazz).newInstance();
// 将实例化的对象放入IOC容器(map)中
beans.put(id, obj);
}
}
@Override
public Object getBean(String name) {
return beans.get(name);
}
}
下面就从这个ClassPathXmlApplicationContext的构造函数开始说起:
1.读取spring的配置文件
调用ClassPathXmlApplicationContext的有参构造方法,传入配置文件的名称。 调用SAXBuilder的build方法读取配置文件// 使用jdom的SAXBuilder读取xml文件,也可以使用dom4j,SAX读取xml配置文件
SAXBuilder sb = new SAXBuilder();
// 加载xml文档进内存,configLocation就是传入的文件名,Document是jdom包里面的类
Document doc = sb.build(this.getClass().getClassLoader().getResourceAsStream(configLocation));
// 获取根节点,也就是beans
Element root = doc.getRootElement();
// 获取根节点的孩子节点,也就是bean
List<Object> childList = root.getChildren("bean");
2.反射加载类,实例化对象,并将对象放入IOC容器
// 循环取出每一个bean节点以及他们的id和class属性,利用反射创建一个对象
for (int i = 0; i < childList.size(); i++) {
Element child = (Element) childList.get(i);
// 获取bean标签的id属性
String id = child.getAttributeValue("id");
// 获取bean标签的class属性
String clazz = child.getAttributeValue("class");
// 通过反射加载类,实例化bean对象
Object obj = Class.forName(clazz).newInstance();
// 将实例化的对象放入IOC容器(map)中
beans.put(id, obj);
}
3.通过getBean从IOC容器获取对象
@Override
public Object getBean(String name) {
return beans.get(name);
}
至此,构造函数内的业务逻辑执行完毕,配置文件中配置的bean都加载并实例化完毕,调用getBean方法获取对应的实例对象即可。
spring bean加载的大体流程就是这样,理解了基本的原理后,再阅读源码就会轻松一些了。