深入理解SpringIOC容器
转载来源:【https://www.cnblogs.com/fingerboy/p/5425813.html】
前言:
在逛博客园的时候突然发现一篇关于事务的好文章,说起spring事物就离不开AOP和动态代理,在研究的过程中突然想起spring的两大特性IOC和AOP,之前对这方面也是理解很浅,于是在网上查了一下关于springIOC的文章,发现了一篇好文章,自己也跟着手动创建了一个项目,打断点随着流程走了一遍,这篇文章对IOC的理解可以起到很大的作用,在这里转载记录一下:
主要思想:
提到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.entiy.Student" >
<property name="name" value="逝清雪"></property>
</bean>
<bean name="teacher" class="com.entiy.Teacher">
<property name="student" ref="student"></property>
</bean>
<bean name="person" class="com.entiy.Person" scope="prototype">
<property name="teacher" ref="teacher"></property>
<property name="student" ref="student"></property>
</bean>
</beans>
实体类Student,Teacher,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;
}
}
public class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Teacher {
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
}
用于封装Bean标签信息的Bean类:
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类:
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类:
public class ConfigManager {
private static Map<String, Bean> map = new HashMap<String, Bean>();
// 读取配置文件并返回读取结果
// 返回Map集合便于注入,key是每个Bean的name属性,value是对应的那个Bean对象
@SuppressWarnings("unchecked")
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接口:
public interface BeanFactory {
//核心方法getBean
Object getBean(String name);
}
ClassPathXmlApplicationContext类:
public class ClassPathXmlApplicationContext implements BeanFactory {
// 获得读取的配置文件中的Map信息
private Map<String, Bean> map;
// 作为IOC容器使用,放置sring放置的对象
private Map<String, Object> context = new HashMap<String, Object>();
public ClassPathXmlApplicationContext(){
super();
}
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);
}
}
}
// 通过反射创建对象
@SuppressWarnings("rawtypes")
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:
public class TestBean {
@Test
public void test(){
BeanFactory bf= new ClassPathXmlApplicationContext("/applicationContent.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);
}
}
测试结果:
false
com.entiy.Person@79b4d0f
逝清雪
true
深入理解SpringIOC容器的更多相关文章
- 深入理解Spring--动手实现一个简单的SpringIOC容器
接触Spring快半年了,前段时间刚用Spring4+S2H4做完了自己的毕设,但是很明显感觉对Spring尤其是IOC容器的实现原理理解的不到位,说白了,就是仅仅停留在会用的阶段,有一颗想读源码的心 ...
- 基于nutz框架理解Ioc容器
同样我们从问题入手去验证以及去理解Ioc容器都做了哪些事情: 1.nutz是有几种方式获取需要容器管理bean的信息? 第一种是使用json格式的文件进行配置,如: 第二种:使用注解@IocBean ...
- 通过单元测试理解spring容器以及dubbo+zookeeper单元测试异常处理
一.先说一个结论:单元测试与主项目的spring容器是隔离的,也就是说,单元测试无法访问主项目spring容器,需要自己加载spring容器. 接下来是代码实例,WEB主项目出于运行状态,单元测试中可 ...
- 理解docker容器和镜像(layer,ufs)和docker命令解释
博客好文1:http://blog.csdn.net/x931100537/article/details/49633107(理解docker容器和镜像,理解简单,从原理入手,什么是layer,什么是 ...
- SpringIOC容器装配Bean
Spring 的core Container(Spring的核心容器)有四大部分:bean.context.core.expression 在进行Bean的配置时候,需要添加四个jar包 如下: 分别 ...
- 高并发秒杀系统--junit测试类与SpringIoc容器的整合
1.原理是在Junit启动时加载SpringIoC容器 2.SpringIoC容器要根据Spring的配置文件加载 [示例代码] package org.azcode.dao; import org. ...
- 【转】Spring学习---SpringIOC容器的初始化过程
[原文]https://www.toutiao.com/i6594400249429623304/ SpringIOC容器的初始化过程 简单来说,IoC容器的初始化是由refresh()方法来启动的, ...
- 【原创】深入理解Docker容器和镜像 -- 分析了docker的命令含义
10张图带你深入理解Docker容器和镜像 镜像(Image)就是一堆只读层(read-only layer)的统一视角 要点:容器 = 镜像 + 读写层.并且容器的定义并没有提及是否要运行容器. 一 ...
- Spring - SpringIOC容器详解
一.什么是Spring IOC: Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想. 在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是 ...
随机推荐
- mybatis在xml文件中处理转义字符
第一种方法: 用了转义字符把>和<替换掉,然后就没有问题了. AND start_date <= CURRENT_DATE AND end_date >= CURRENT_DA ...
- Linux framebuffer测试程序
Linux framebuffer的框架非常简单, 对于应用程序就是操作一块内存(俗称帧缓存), 当然也有可能是双缓存, 一般用于高帧率场景, 一块帧在填充数据时, 另一块在显示, 接着对调过来, 那 ...
- 玩转Spring Cloud之配置中心(config server &config client)
本文内容导航: 一.搭建配置服务中心(config server) 1.1.git方式 1.2.svn方式 1.3.本地文件方式 1.4.解决配置中包含中文内容返回乱码问题 二.搭建配置消费客户端( ...
- .net后台防止API接口被重复请求
思路大概是这样的: 1.获取到发出请求的客户端的IP 2.将该IP存入Cache作为KEY,将次数作为Value初始化为0,过期时间设置为1分钟 3.每次请求都将value+1,超过指定的次数后返回f ...
- 微信ChatEmoji表情适配,对微信公众号开发有帮助
最近做微信公众号时发现微信ChatEmoji表情与接受的消息显示表情的问题, 微信表情后面的ChatEmoji显示不出,花了一些时间整理,把pc和手机的表情全部都整理了, 由于有两百多个显示可能有点长 ...
- Spring Boot Security 详解
简介 Spring Security,这是一种基于 Spring AOP 和 Servlet 过滤器的安全框架.它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和授权. 工作 ...
- log4j日志框架的使用
java.util.logging.Logger——java 中提供的日志类 实际开发 90% 都是使用 log4j 记录日志,而 Log4j 底层就是 java.util.logging.Logge ...
- 使用curl制作简易百度搜索
这几天研究了一下php中的curl类库,做了一个简单的百度搜索,先上代码 <div style="width:200px;height:100px;"> <div ...
- java:nextInt()和nextLine()一起使用出错
今天遇到一个很奇怪的事情,日常刷题中,遇到一个很简单的题: (不想看我多逼逼只想知道为什么会出错看最后) 题目: 题目描述 description 现有有N个学生的数据记录,每个记录包括学号.姓名.三 ...
- Android探究之ANR
什么是ANR ANR:Application Not Responding,即应用程序无响应. 在Android中,ActivityManagerService(简称AMS)和WindowManage ...