为了更好的理解Spring框架底层原理,我们自己先来写一个简单spring。本文章为记录学习而用,如有错误欢迎改正
主要用到:
a. dom4j技术来解析xml文件(找到对应的bean标签和property标签)
b. 反射和类加载(创建对象)
c. 内省机制(给属性赋值)
- 导入jar包
- 我们先准备几个实体类。实现getter、setter、toString等方法。
3.创建一个xml文件application.xml(名字随意),并在这个文件中把刚才创建的实体类以bean标签的方式进行存放。
- 编写一个接口名为BeanFactory,这个接口是spring中的顶层接口,代表spring本身,定义了一些基础的方法,不提供给开发者直接使用。这里我先编写一个getBean方法用于通过名字获取对象。
- 再编写一个接口名为ApplicationContext的方法并继承刚才创建的BeanFactory接口。这个接口定义一些额外的方法。
注意:实际Spring框架中ApplicationContext下面还有一个ConfigurableApplicationContext接口,包括一些扩展方法 refresh() 和 close() 。这里不做过多介绍。 - 编写接口ApplicationContext的实现类ClassPathXmlApplicationContext,代码如下(通过注解进行介绍)。
public class ClassPathXmlApplicationContext implements ApplicationContext{
//6.在多线程的情况下,会有线程安全问题,所以这里我们使用ConcurrentHashMap(底层采用分段锁),并且只有一个,用来存放我们的bean对象,也就是我们的spring容器
private static ConcurrentHashMap<String,Object> beans = new ConcurrentHashMap();
//7.我们需要在创建这个对象的时候就将我们的bean放入到容器中。并且需要传入我们的资源文件名
public ClassPathXmlApplicationContext(String resource){
try {
SAXReader saxReader = new SAXReader();
InputStream in = ClassPathXmlApplicationContext.class.getClassLoader().getResourceAsStream(resource);
if(in == null){
throw new RuntimeException("没有找到该配置文件");
}
//8.我们需要通过dom4j读取xml文件并形成一个Document文件
Document document = saxReader.read(in);
//9.通过我们的ioc方法来实现创建对象
ioc(document);
//10在我们创建好对象之后对bean对象中的属性进行赋值
di(document);
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("spring容器创建失败");
}
}
private void di(Document document) {
try {
//获取我们bean的id
//我们通过XPth获取文档下所有的bean标签
List<Node> beanlist = document.selectNodes("//bean");
//遍历所有的bean标签。获取到我们的id属性的值和class属性的值
for (Node bean : beanlist) {
//获取bean的class属性
String className = bean.valueOf("@class");
if (className == null || className.trim().equals("")) {
throw new RuntimeException("class属性不能为空");
}
//获取bean标签的id属性/name属性
String name = bean.valueOf("@id");
//判断beanId是否为空
if (name == null || name.trim().equals("")) {
//继续判断是否有name属性
name = bean.valueOf("@name");
if (name == null || name.trim().equals("")) {
//id和name都没有,但是我们需要给bean娶一个名字。把class属性的值的最后一个单词送给他
name = className.substring(className.lastIndexOf(".") + 1);
}
}
//通过我们得到beanName去容器中查找bean
Object obj = beans.get(name);
//将我们的bean转换为Element方便使用XPth查找
//List<Node> propertyList = bean.selectNodes("//property");
Element element = (Element) bean;
List<Element> elements = element.elements("property");
for (Element node : elements) {
String pName = node.valueOf("@name");
//获取该属性的属性描述器
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(pName, obj.getClass());
//判断该属性是否存在
if(propertyDescriptor == null){
throw new RuntimeException("该属性不存在");
}
//这里要判断是简单类型还是注入到spring容器中的bean类型
String refAttributeValue = node.valueOf("@ref");
if(refAttributeValue != null &&!refAttributeValue.trim().equals("")){
//查找是否有这个bean
Object target = beans.get(refAttributeValue);
if(target == null){
throw new RuntimeException("需要注入的bean不存在");
}
//通过BeanUtils将我们的名称和值映射给obj对象
BeanUtils.copyProperty(obj,pName,target);
}else {
String attributeValue = node.valueOf("@value");
if(attributeValue == null || attributeValue.trim().equals("")){
throw new RuntimeException("属性value不能为空");
}
BeanUtils.copyProperty(obj,pName,attributeValue);
}
}
}
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("DI失败");
}
}
private void ioc(Document document) {
try {
//我们通过XPth获取文档下所有的bean标签
List<Node> beanlist = document.selectNodes("//bean");
//遍历所有的bean标签。获取到我们的id属性的值和class属性的值
for (Node bean : beanlist) {
//获取bean的class属性
String className = bean.valueOf("@class");
if(className == null || className.trim().equals("")){
throw new RuntimeException("class属性不能为空");
}
//获取bean标签的id属性/name属性
String name = bean.valueOf("@id");
//判断beanId是否为空
if(name == null || name.trim().equals("")){
//继续判断是否有name属性
name = bean.valueOf("@name");
if(name == null || name.trim().equals("")){
//id和name都没有,但是我们需要给bean娶一个名字。把class属性的值的最后一个单词送给他
name = className.substring(className.lastIndexOf(".") + 1);
}
}
//拿到我们class属性的值,通过反射创建对象
Object obj = Class.forName(className).newInstance();
//将我们创建好的对象,以及获取到bean的id放入到我们创建好的容器当中
beans.put(name,obj);
}
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("IOC失败");
}
}
@Override
public Object getBean(String name) {
//直接从我们的ConcurrentHashMap中查找
Object bean = beans.get(name);
if(bean == null){
throw new RuntimeException("no such bean");
}
return bean;
}
}
最后我们进行测试。
@Test
public void test(){
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("application.xml");
CVN cvn = (CVN) classPathXmlApplicationContext.getBean("cvn");
System.out.println(cvn);
}