零基础带你看Spring源码——IOC控制反转
本章开始来学习下Spring的源码,看看Spring框架最核心、最常用的功能是怎么实现的。
网上介绍Spring,说源码的文章,大多数都是生搬硬推,都是直接看来的观点换个描述就放出来。这并不能说有问题,但没有从一个很好的、容易切入的角度去了解学习。博主来尝试抛弃一些所知,从使用上入手,步步回溯源码去了解学习。
很多人会混乱IOC和DI的两个概念,其实这两者是层面的不同。
具体的区别的区别:IOC是DI的原理。依赖注入是向某个类或方法注入一个值,其中所用到的原理就是控制反转。
所以说到操作层面的时候用DI,原理层的是说IOC,下文亦同。
对于DI最新使用方法,现在都是建议用Java注解去标识。但是相信笔者,不要用这种方式去看源码。笔者本来是想从Java注解入手去一步步看源码,debug看看发生什么了。但发现更多时间是在调SpringBoot和AOP的源码。在看了一天后,还是换一种思路吧,因为AOP是打算在下一章再讲的。
所以我用XML的方式,搭了一个最简单的Spring项目来学习其中IOC的源码。建议大家把代码拉下来,跟着笔者思路来一起看。
源码在此:https://github.com/Zack-Ku/spring-ioc-demo
搭建内容
maven的依赖,只添加了spring-context模板,用的是4.3.11版本(部分代码)
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.11.RELEASE</version>
</dependency>
</dependencies>
作为Bean的Service(部分代码)
public class TestBeanServiceImpl implements TestBeanService {
public String getBean() {
return "a test bean";
}
}
配置XML(部分代码)
<bean id="testBeanService" class="com.zack.demo.TestBeanServiceImpl"/>
启动类。只是加载了下spring的xml配置,然后从context中拿出Bean,这就是完整IOC的过程了。(部分代码)
public class Application {
public static void main(String[] args) {
// 加载xml配置
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath:application.xml");
// IOC获取Bean
TestBeanService testBeanService = context.getBean(TestBeanService.class);
System.out.println(testBeanService.getBean());
}
}
最后启动就能获取这个bean,看到getMessage()打印的内容了。
这样就是一个比较纯粹的Spring-IOC的项目了。我们直接从启动类开始看起
Bean的含义
前置先解释下这个Bean的含义,因为会贯穿整个流程。
通俗地讲,Bean就是IOC的容器。如上面的例子,将TestBeanService注册到Spring里,那么TestBeanService就是Spring的里面的一个Bean。Demo里面context.getBean()就是从Spring中取出这个Bean,完成控制反转的。
所以我们的重点就是要看看Spring到底是怎么生成管理这些Bean的。
ClassPathXmlApplicationContext
启动类中,加载配置的ClassPathXmlApplicationContext肯定就是完成IOC的核心。不知道它到底是怎么做的,怎么入手呢?
先来看看它的类图

先分析下这个类图,
ClassPathXmlApplicationContext类是AbstractApplicationContext抽象类的子类AbstractApplicationContext类是ApplicaionContext接口的实现。ApplicaionContext接口集合了非常多的内容,其中和IOC比较相关的就是ListableBeanFactory接口和HierarchicalBeanFactory接口ListableBeanFactory接口和HierarchicalBeanFactory接口是继承BeanFactory
从此分析可以看出,ClassPathXmlApplicationContext是什么,了解下ApplicaionContext;它怎么和IOC有关,要了解BeanFactory。
所以后面我们先来看看ApplicaionContext与BeanFactory。
ApplicationContext

从该接口的注解描述可知,ApplicationContext是整个项目的配置,Spring项目在启动或运行的时候都需要依赖到它。
其中Bean管理相关的则是ListableBeanFactory和HierarchicalBeanFactory。
BeanFactory
ListableBeanFactory和HierarchicalBeanFactory都是继承BeanFactory的。
先看看BeanFactory的文件注解

从上图可知,BeanFactory就是获取Bean容器的地方。而且他可以提供单例的对象或者是独立的对象

从这段可以得知,HierarchicalBeanFactory是一个分层的Bean,如果实现了这个接口,所有方法都会经过父类的工厂。所以这个是个拓展的类,暂时先不看它。
接下来看看ListableBeanFactory注解说明

这个接口是要实现预先加载Bean的配置,生成好实例,直接管理Bean的实例,而不是来一个请求,生成一个。
好了,以上就是基本的概念和认知,现在带着这些概念,我们回头看看ClassPathXmlApplicationContext的执行流程,看看它到底怎么的生成管理Bean的。
初始化IOC容器
从ClassPathXmlApplicationContext的构造函数看,最核心的就是refresh()函数,其他只是设一些值。
而这个refresh()是调用父类AbstractApplicationContext中的refresh()。
根据它的注解可知它是加载刷新了整个context,并且加载所有Bean定义和创建对应的单例。

看下这个方法做了什么

里面有许多步骤,重点看下obtainFreshBeanFactory()(重新获取一个BeanFactory)。
它里面有个核心的方法refreshBeanFactory()

如果已有BeanFactory,先删除所有Bean,然后关闭BeanFactory。
然后创建一个新的ListableBeanFactory,上面说到这个工厂里会预先加载所有的Bean。
最后核心的就是loadBeanDefinitions(beanFactory),它是加载Bean的定义。实现交给了子类。

用的是XmlBeanDefinitionReader直接读配置文件加载Bean Definition(Bean定义)到BeanFactory。它里面一步步把xml的配置文件拆解读取,把一个个Bean Definition加载到BeanFactory里。
至此,已经有用一个加载好Bean Definition的BeanFactory了。
其他方法也是围绕BeanFactory后置处理和Context的配置准备。内容太多,想更深入了解的话建议顺着以上思路,找到对应代码阅读以下。
依赖注入
回到启动类中,看看怎么从context中获取bean的。
context.getBean(TestBeanService.class)
是根据类去拿bean的,当然也可以根据id。
其对应的源码实现,在DefaultListableBeanFactory中,上文有说到对应的BeanFactory选型。

NamedBeanHolder是里面包含一个实例化的对象,和bean的名字。resolveNamedBean()是怎么拿出Bean的关键。
一步步Debug,可以看到,它是遍历BeanFactory里面维护的beanDefinitionNames和manualSingletonNames成员变量,找出命中的beanName返回。

然后拿着这个beanName去找具体的bean实例。这里的代码比较长,在AbstractBeanFactory里面的doGetBean()中实现。
大意是先尝试去找手动添加bean的单例工厂里找有没有对应的实例,没有的话就往父类beanFactory里面找,最后没有的话就生成一个。
spring中一个bean是如何加载和如何注入大致如此,更细节的内容,可以自己debug看看源码。
控制反转的优点
最后来以我个人观点谈谈控制反转的优点吧。
举个例子,我要装修房子,需要门、浴具、厨具、油漆、玻璃等材料。
decorateHouse(Door,BathThing,CookThing,....)
但是我作为一个装修工人,我需要去制造门、制造浴具,合成玻璃油漆吗?
不需要,也不关心其建造的过程,对应的会有人去做这些东西。
door = buildDoor();
glass = buildGlass();
所有材料放到建材商城里面,装修工人需要什么材料就去建材商城里面取。
对应Spring的IOC,门、玻璃等材料就是Bean,建材商城就是IOC容器,把材料放到建材商城就是Bean加载,去商城拿材料就是依赖注入的过程。
程序开发发展至今,一个简答的项目或许也要分几个模板,几个人去开发。划分好职责,设计好接口,面向接口编程。每个人只需要完成好自己那部分的工作,依赖调用就可以了。这样做同时有助于降低项目的耦合度,让项目有更好的延伸性。由此Spring的IOC就是基于以上的需求所诞生的。
总结
回顾下全文的内容
- ApplicationContext是Spring项目的核心配置,项目运行依赖于它,其中包含许多方面的内容。
- BeanFactory是Context包含的内容之一,它负责管理Bean的加载,生成,注入等内容。
- Spring控制反转为了降低项目耦合,提高延伸性。
本文讲Spring IOC还比较浅显,仅仅讲了如何加载的重点和注入的重点,关于生命周期,BeanFactory的处理由于篇幅问题并没有细讲。有兴趣的读者可以用Demo跑起来,一步步Debug看看。因为Demo基本是最小化的Spring IOC了,所以这个Debug不会太难,很容易就能看清楚整个流程做了什么。
Demo:https://github.com/Zack-Ku/spring-ioc-demo
更多技术文章、精彩干货,请关注
博客:zackku.com
微信公众号:Zack说码

零基础带你看Spring源码——IOC控制反转的更多相关文章
- Spring源码——IOC控制反转
1.基础知识 Spring有两个核心功能,分别是ioc和aop,其中ioc是控制反转,aop是切面编程. 在ioc中,还有一个名次叫DI,也就是依赖注入.嗯,好像IOC和DI是指同一个,好像又感觉他俩 ...
- Spring源码分析(一):从哪里开始看spring源码(系列文章基于Spring5.0)
概述 对于大多数第一次看spring源码的人来说,都会感觉不知从哪开始看起,因为spring项目源码由多个子项目组成,如spring-beans,spring-context,spring-core, ...
- Spring源码-IOC部分-Spring是如何解决Bean循环依赖的【6】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码-IOC部分-容器简介【1】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码-IOC部分-Bean实例化过程【5】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码-IOC部分-容器初始化过程【2】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码-IOC部分-Xml Bean解析注册过程【3】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码-IOC部分-自定义IOC容器及Bean解析注册【4】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码-IOC部分-循环依赖-用实例证明去掉二级缓存会出现什么问题【7】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
随机推荐
- import pymongo exceptions.ImportError: No module named pymongo
最近用Scrapy写爬虫,将爬取的数据存入Mongodb中,使用的是pymongo这个库,但是运行的时候报错如标题所示 搜了好多网站包括stackoverflow都没有解决,后来发现自己用的是虚拟环境 ...
- Eclipse中的Web项目自动部署到Tomcat的webapp目录下
Eclipse中的Web项目自动部署到Tomcat 原因 很长时间没用Eclipse了,近期由于又要用它做个简单的JSP项目,又要重新学习了,虽然熟悉的很快,但记忆总是很模糊,偶尔犯错,以前很少写 ...
- 公共css
* { margin: 0; padding: 0; word-break: break-all; font-family: Microsoft YaHei, tahoma, arial, Hirag ...
- ubuntu12.04 Qt WebKit编译
转载自:http://my.oschina.net/u/257674/blog/167050 官方文档: http://trac.webkit.org/wiki/BuildingQtOnLinux#D ...
- CSS垂直居中小结
1.设置子元素: { ... position :absolute; margin:auto; top:; right:; bottom:; left:; } 2.设置子元素:(height必须是固定 ...
- javaScript中的this关键字解析
this是JavaScript中的关键字之一,在编写程序的时候经常会用到,正确的理解和使用关键字this尤为重要.接下来,笔者就从作用域的角度粗谈下自己对this关键字的理解,希望能给到大家一些启示, ...
- java消息中间件入门
消息中间件来解耦服务调用 比如1个登录系统,登录的话需要调用很多系统的其他服务,如果中间调用失败,可能会导致登录信息一致无法返回,同时也增加了系统的耦合度.而用消息中间件的话,则是不发送服务到其他系统 ...
- lucene、solr、nutch三者的关系
lucene是一个做搜索用的类库. nutch和solr都是基于lucene的,二者都是可直接运行的应用程序: 直接在业务上使用lucene的倒是不太多见. solr主要提供了建立索引(用户可以直接p ...
- 【BZOJ】1571: [Usaco2009 Open]滑雪课Ski
[算法]动态规划 [题解]yy出了O(1w log 1w)的算法. 将雪坡排序预处理出g[i]表示能力值为i的最短时长雪坡. 这样就可以定义work(t,c)表示时长t能力c的最多滑雪数量,work( ...
- win7中下载和安装redis
下载地址:https://github.com/MicrosoftArchive/redis/releases 这里我下载的是3.2.100版本,下载zip,然后解压.解压后如图所示: 开启redis ...