接触Spring快半年了,前段时间刚用Spring4+S2H4做完了自己的毕设,但是很明显感觉对Spring尤其是IOC容器的实现原理理解的不到位,说白了,就是仅仅停留在会用的阶段,有一颗想读源码的心于是买了一本计文柯的《Spring技术内幕》,第二章没看完,就被我扔一边了,看的那是相当痛苦,深深觉得自己资质尚浅,能力还不够,昨天在网上碰巧看到一个实现简单的SpringIOC容器的视频教程,于是跟着做了一遍,竟然相当顺利,至少每一行代码都能理解,于是细心整理了一番,放在这里.
主要思想:
提到IOC,第一反应就是控制反转,我以前以为SpringIOC就是控制反转,控制反转就是SpringIOC,当然这种理解是错误的,控制反转是一种思想,一种模式,而Spring的IOC容器是实现了这种思想这种模式的一个载体.
使用过Spring的人都熟知,SpringIOC容器可以在对象生成或初始化时就直接将数据注入到对象中,如果对象A的属性是另一个对象B,还可以将这个对象B的引用注入到注入到A的数据域中.
如果在初始化对象A的时候,对象B还没有进行初始化,而A又需要对象B作为自己的属性,那么就会用一种递归的方式进行注入,这样就可以把对象的依赖关系清晰有序的建立起来.
IOC容器解决问题的核心就是把创建和管理对象的控制权从具体的业务对象手中抢过来.由IOC容器来管理对象之间的依赖关系,并由IOC容器完成对象的注入.这样就把应用从复杂的对象依赖关系的管理中解放出来,简化了程序的开发过程.
下图是这个简单IOC容器的类图(原谅我真没学过UML,凑合看吧):
程序中所有的Bean之间的依赖关系我们是放在一个xml文件中进行维护的,就是applicationContext.xml
ConfigManager类完成的功能是读取xml,并将所有读取到有用的信息封装到我们创建的一个Map<String,Bean>集合中,用来在初始化容器时创建bean对象.
定义一个BeanFactory的接口,接口中有一个getBean(String name)方法,用来返回你想要创建的那个对象.
然后定义一个该接口的实现类ClassPathXmlApplicationContext.就是在这个类的构造方法中,初始化容器,通过调用ConfigManager的方法返回的Map集合,通过反射和内省一一创建bean对象.这里需要注意,对象的创建有两个时间点,这取决与bean标签中scope属性的值:
- 如果scope="singleton",那么对象在容器初始化时就已创建好,用的时候只需要去容器中取即可.
- 如果scope="prototype",那么容器中不保存这个bean的实例对象,每次开发者需要使用这个对象时再进行创建.
使用的主要知识点:
- dom4j解析xml文件
- xpath表达式(用于解析xml中的标签)
- java反射机制
- 内省(获取Bean属性的set方法进行赋值)
项目结构图及介绍如下:
项目需要的jar包与项目结构已经在上图中介绍了,这个项目所能实现的功能如下:
1. IOC容器能管理对象的创建以及对象之间的依赖关系.
2. 能够实现数据的自动类型转换(借助BeanUtils).
3. 能够实现scope="singleton"和scope="prototype"的功能,即能够控制对象是否为单例.
下面介绍代码部分:
application.xml:
<?xml version="1.0" encoding="utf-8"?> <beans> <bean name="student" class="com.wang.entity.Student" > <property name="name" value="123"></property> </bean> <bean name="teacher" class="com.wang.entity.Teacher"> <property name="student" ref="student"></property> </bean> <bean name="person" class="com.wang.entity.Person" scope="prototype"> <property name="teacher" ref="teacher"></property> <property name="student" ref="student"></property> </bean> </beans></div>
实体类Student,Teacher,Person:
package com.wang.entity; //Student类 public class Student { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } /************************************/ package com.wang.entity; //Teacher类 public class Teacher { private Student student; public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } } /************************************/ package com.wang.entity; //Person类 public class Person { private Student student; private Teacher teacher; public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } public Teacher getTeacher() { return teacher; } public void setTeacher(Teacher teacher) { this.teacher = teacher; } }</div>
用于封装Bean标签信息的Bean类:
package com.wang.config; import java.util.ArrayList; import java.util.List; public class Bean { private String name; private String className; private String scope="singleton"; private List<Property> properties=new ArrayList<Property>(); public String getScope() { return scope; } public void setScope(String scope) { this.scope = scope; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public List<Property> getProperties() { return properties; } public void setProperties(List<Property> properties) { this.properties = properties; } }</div>
用与封装Bean子标签property内容的Property类:
package com.wang.config; public class Property { private String name; private String value; private String ref; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public String getRef() { return ref; } public void setRef(String ref) { this.ref = ref; } }</div>
ConfigManager类:
package com.wang.config.parse; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.junit.Test; import com.wang.config.Bean; import com.wang.config.Property; public class ConfigManager { private static Map<String,Bean> map=new HashMap<String,Bean>(); //读取配置文件并返回读取结果 //返回Map集合便于注入,key是每个Bean的name属性,value是对应的那个Bean对象 public static Map<String, Bean> getConfig(String path){ /*dom4j实现 * 1.创建解析器 * 2.加载配置文件,得到document对象 * 3.定义xpath表达式,取出所有Bean元素 * 4.对Bean元素继续遍历 * 4.1将Bean元素的name/class属性封装到bean类属性中 * 4.2获得bean下的所有property子元素 * 4.3将属性name/value/ref分装到类Property类中 * 5.将property对象封装到bean对象中 * 6.将bean对象封装到Map集合中,返回map */ //1.创建解析器 SAXReader reader=new SAXReader(); //2.加载配置文件,得到document对象 InputStream is = ConfigManager.class.getResourceAsStream(path); Document doc =null; try { doc = reader.read(is); } catch (DocumentException e) { e.printStackTrace(); throw new RuntimeException("请检查您的xml配置是否正确"); } // 3.定义xpath表达式,取出所有Be