深入理解Spring--动手实现一个简单的SpringIOC容器
接触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包与项目结构已经在上图中介绍了,这个项目所能实现的功能如下:
- IOC容器能管理对象的创建以及对象之间的依赖关系.
- 能够实现数据的自动类型转换(借助BeanUtils).
- 能够实现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=""></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>
实体类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;
}
}
用于封装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;
} }
用与封装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;
}
}
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表达式,取出所有Bean元素
String xpath="//bean"; //4.对Bean元素继续遍历
List<Element> list = doc.selectNodes(xpath);
if(list!=null){
//4.1将Bean元素的name/class属性封装到bean类属性中 // 4.3将属性name/value/ref分装到类Property类中
for (Element bean : list) {
Bean b=new Bean();
String name=bean.attributeValue("name");
String clazz=bean.attributeValue("class");
String scope=bean.attributeValue("scope");
b.setName(name);
b.setClassName(clazz);
if(scope!=null){
b.setScope(scope);
}
// 4.2获得bean下的所有property子元素
List<Element> children = bean.elements("property"); // 4.3将属性name/value/ref分装到类Property类中
if(children!=null){
for (Element child : children) {
Property prop=new Property();
String pName=child.attributeValue("name");
String pValue=child.attributeValue("value");
String pRef=child.attributeValue("ref");
prop.setName(pName);
prop.setRef(pRef);
prop.setValue(pValue);
// 5.将property对象封装到bean对象中
b.getProperties().add(prop);
}
}
//6.将bean对象封装到Map集合中,返回map
map.put(name, b);
}
} return map;
} }
BeanFactory接口:
package com.wang.main;
public interface BeanFactory {
//核心方法getBean
Object getBean(String name);
}
ClassPathXmlApplicationContext类:
package com.wang.main; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry; import org.apache.commons.beanutils.BeanUtils;
import org.junit.Test; import com.wang.config.Bean;
import com.wang.config.Property;
import com.wang.config.parse.ConfigManager;
import com.wang.entity.Student;
//import com.wang.utils.BeanUtils;
import com.wang.utils.BeanUtil; public class ClassPathXmlApplicationContext implements BeanFactory { // 获得读取的配置文件中的Map信息
private Map<String, Bean> map;
// 作为IOC容器使用,放置sring放置的对象
private Map<String, Object> context = new HashMap<String, Object>(); public ClassPathXmlApplicationContext(String path) {
// 1.读取配置文件得到需要初始化的Bean信息
map = ConfigManager.getConfig(path);
// 2.遍历配置,初始化Bean
for (Entry<String, Bean> en : map.entrySet()) {
String beanName = en.getKey();
Bean bean = en.getValue(); Object existBean = context.get(beanName);
// 当容器中为空并且bean的scope属性为singleton时
if (existBean == null && bean.getScope().equals("singleton")) {
// 根据字符串创建Bean对象
Object beanObj = createBean(bean); // 把创建好的bean对象放置到map中去
context.put(beanName, beanObj);
}
} } // 通过反射创建对象
private Object createBean(Bean bean) {
// 创建该类对象
Class clazz = null;
try {
clazz = Class.forName(bean.getClassName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("没有找到该类" + bean.getClassName());
}
Object beanObj = null;
try {
beanObj = clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("没有提供无参构造器");
}
// 获得bean的属性,将其注入
if (bean.getProperties() != null) {
for (Property prop : bean.getProperties()) {
// 注入分两种情况
// 获得要注入的属性名称
String name = prop.getName();
String value = prop.getValue();
String ref = prop.getRef();
// 使用BeanUtils工具类完成属性注入,可以自动完成类型转换
// 如果value不为null,说明有
if (value != null) {
Map<String, String[]> parmMap = new HashMap<String, String[]>();
parmMap.put(name, new String[] { value });
try {
BeanUtils.populate(beanObj, parmMap);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("请检查你的" + name + "属性");
}
} if (ref != null) {
// 根据属性名获得一个注入属性对应的set方法
// Method setMethod = BeanUtil.getWriteMethod(beanObj,
// name); // 看一看当前IOC容器中是否已存在该bean,有的话直接设置没有的话使用递归,创建该bean对象
Object existBean = context.get(prop.getRef());
if (existBean == null) {
// 递归的创建一个bean
existBean = createBean(map.get(prop.getRef()));
// 放置到context容器中
// 只有当scope="singleton"时才往容器中放
if (map.get(prop.getRef()).getScope()
.equals("singleton")) {
context.put(prop.getRef(), existBean);
}
}
try {
// setMethod.invoke(beanObj, existBean);
//通过BeanUtils为beanObj设置属性
BeanUtils.setProperty(beanObj, name, existBean);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("您的bean的属性" + name
+ "没有对应的set方法");
} } }
} return beanObj;
} @Override
public Object getBean(String name) {
Object bean = context.get(name);
// 如果为空说明scope不是singleton,那么容器中是没有的,这里现场创建
if (bean == null) {
bean = createBean(map.get(name));
} return bean;
} }
最后就是一个测试类TestBean:
package com.wang.main; import org.junit.Test; import com.wang.entity.Person;
import com.wang.entity.Student;
import com.wang.entity.Teacher; public class TestBean { @Test
public void func1(){ BeanFactory bf=new ClassPathXmlApplicationContext("/applicationContext.xml");
Person s=(Person)bf.getBean("person");
Person s1=(Person)bf.getBean("person");
System.out.println(s==s1);
System.out.println(s1);
Student stu1=(Student) bf.getBean("student");
Student stu2=(Student) bf.getBean("student");
String name=stu1.getName();
System.out.println(name);
System.out.println(stu1==stu2);
}
}
深入理解Spring--动手实现一个简单的SpringIOC容器的更多相关文章
- 自己动手实现一个简单的 IOC容器
控制反转,即Inversion of Control(IoC),是面向对象中的一种设计原则,可以用有效降低架构代码的耦合度,从对象调用者角度又叫做依赖注入,即Dependency Injection( ...
- 动手写一个简单版的谷歌TPU-指令集
系列目录 谷歌TPU概述和简化 基本单元-矩阵乘法阵列 基本单元-归一化和池化(待发布) TPU中的指令集 SimpleTPU实例: (计划中) 拓展 TPU的边界(规划中) 重新审视深度神经网络中的 ...
- 动手写一个简单版的谷歌TPU-矩阵乘法和卷积
谷歌TPU是一个设计良好的矩阵计算加速单元,可以很好的加速神经网络的计算.本系列文章将利用公开的TPU V1相关资料,对其进行一定的简化.推测和修改,来实际编写一个简单版本的谷歌TPU.计划实现到行为 ...
- 动手写一个简单的Web框架(模板渲染)
动手写一个简单的Web框架(模板渲染) 在百度上搜索jinja2,显示的大部分内容都是jinja2的渲染语法,这个不是Web框架需要做的事,最终,居然在Werkzeug的官方文档里找到模板渲染的代码. ...
- 动手写一个简单的Web框架(Werkzeug路由问题)
动手写一个简单的Web框架(Werkzeug路由问题) 继承上一篇博客,实现了HelloWorld,但是这并不是一个Web框架,只是自己手写的一个程序,别人是无法通过自己定义路由和返回文本,来使用的, ...
- 动手写一个简单的Web框架(HelloWorld的实现)
动手写一个简单的Web框架(HelloWorld的实现) 关于python的wsgi问题可以看这篇博客 我就不具体阐述了,简单来说,wsgi标准需要我们提供一个可以被调用的python程序,可以实函数 ...
- (2)自己写一个简单的servle容器
自己写一个简单的servlet,能够跑一个简单的servlet,说明一下逻辑. 首先是写一个简单的servlet,这就关联到javax.servlet和javax.servlet.http这两个包的类 ...
- 一个简单的servlet容器
[0]README 0.1)本文部分文字转自 “深入剖析Tomcat”,旨在学习 一个简单的servlet容器 的基础知识: 0.2)for complete source code, pleas ...
- Tomcat详解系列(1) - 如何设计一个简单的web容器
Tomcat - 如何设计一个简单的web容器 在学习Tomcat前,很多人先入为主的对它的认知是巨复杂的:所以第一步,在学习它之前,要打破这种观念,我们通过学习如何设计一个最基本的web容器来看它需 ...
随机推荐
- 【大型网站技术实践】初级篇:借助LVS+Keepalived实现负载均衡
一.负载均衡:必不可少的基础手段 1.1 找更多的牛来拉车吧 当前大多数的互联网系统都使用了服务器集群技术,集群即将相同服务部署在多台服务器上构成一个集群整体对外提供服务,这些集群可以是Web应用服务 ...
- 【原】AFNetworking源码阅读(三)
[原]AFNetworking源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇的话,主要是讲了如何通过构建一个request来生成一个data tas ...
- Oracle Database 12c Data Redaction介绍
什么是Data Redaction Data Redaction是Oracle Database 12c的高级安全选项之中的一个新功能,Oracle中国在介绍这个功能的时候,翻译为“数据编纂”,在EM ...
- 从netty-example分析Netty组件
分析netty从源码开始 准备工作: 1.下载源代码:https://github.com/netty/netty.git 我下载的版本为4.1 2. eclipse导入maven工程. netty提 ...
- JS继承之寄生类继承
原型式继承 其原理就是借助原型,可以基于已有的对象创建新对象.节省了创建自定义类型这一步(虽然觉得这样没什么意义). 模型 function object(o){ function W(){ } W. ...
- 数据的双向绑定 Angular JS
接触AngularJS许了,时常问自己一些问题,如果是我实现它,会在哪些方面选择跟它相同的道路,哪些方面不同.为此,记录了一些思考,给自己回顾,也供他人参考. 初步大致有以下几个方面: 数据双向绑定 ...
- ASP.NET中画图形验证码
context.Response.ContentType = "image/jpeg"; //生成随机的中文验证码 string yzm = "人口手大小多少上中下男女天 ...
- 【C#公共帮助类】 Utils 10年代码,最全的系统帮助类
为大家分享一下个人的一个Utils系统帮助类,可能有些现在有新的技术替代,自行修改哈~ 这个帮助类主要包含:对象转换处理 .分割字符串.截取字符串.删除最后结尾的一个逗号. 删除最后结尾的指定字符后的 ...
- 【干货分享】流程DEMO-费用报销
流程名: 费用报销 业务描述: 流程发起时,要选择需要关联的事务审批单,会检查是否超申请,如果不超申请,可以直接发起流程,如果超了申请,需要检查预算,如果预算不够,将不允许发起报销申请,如果预算够用, ...
- Xamarin.Android广播接收器与绑定服务
一.前言 学习了前面的活动与服务后,你会发现服务对于活动而言似乎就是透明的,相反活动对于服务也是透明的,所以我们还需要一中机制能够将服务和活动之间架起一座桥梁,通过本节的学习,你将会学到广播与绑定服务 ...