零基础带你看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的核心。不知道它到底是怎么做的,怎么入手呢?
先来看看它的类图

image

先分析下这个类图,

  1. ClassPathXmlApplicationContext类是AbstractApplicationContext抽象类的子类
  2. AbstractApplicationContext类是ApplicaionContext接口的实现。
  3. ApplicaionContext接口集合了非常多的内容,其中和IOC比较相关的就是ListableBeanFactory接口和HierarchicalBeanFactory接口
  4. ListableBeanFactory接口和HierarchicalBeanFactory接口是继承BeanFactory

从此分析可以看出,ClassPathXmlApplicationContext是什么,了解下ApplicaionContext;它怎么和IOC有关,要了解BeanFactory
所以后面我们先来看看ApplicaionContextBeanFactory

ApplicationContext

image

从该接口的注解描述可知,ApplicationContext是整个项目的配置,Spring项目在启动或运行的时候都需要依赖到它。

其中Bean管理相关的则是ListableBeanFactoryHierarchicalBeanFactory

BeanFactory

ListableBeanFactoryHierarchicalBeanFactory都是继承BeanFactory的。
先看看BeanFactory的文件注解

image

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

image

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

接下来看看ListableBeanFactory注解说明

image

这个接口是要实现预先加载Bean的配置,生成好实例,直接管理Bean的实例,而不是来一个请求,生成一个。

好了,以上就是基本的概念和认知,现在带着这些概念,我们回头看看ClassPathXmlApplicationContext的执行流程,看看它到底怎么的生成管理Bean的。

初始化IOC容器

ClassPathXmlApplicationContext的构造函数看,最核心的就是refresh()函数,其他只是设一些值。
而这个refresh()是调用父类AbstractApplicationContext中的refresh()
根据它的注解可知它是加载刷新了整个context,并且加载所有Bean定义和创建对应的单例。

image

看下这个方法做了什么

image

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

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

用的是XmlBeanDefinitionReader直接读配置文件加载Bean Definition(Bean定义)到BeanFactory。它里面一步步把xml的配置文件拆解读取,把一个个Bean Definition加载到BeanFactory里。
至此,已经有用一个加载好Bean Definition的BeanFactory了。

其他方法也是围绕BeanFactory后置处理和Context的配置准备。内容太多,想更深入了解的话建议顺着以上思路,找到对应代码阅读以下。

依赖注入

回到启动类中,看看怎么从context中获取bean的。

    context.getBean(TestBeanService.class)

是根据类去拿bean的,当然也可以根据id。
其对应的源码实现,在DefaultListableBeanFactory中,上文有说到对应的BeanFactory选型。

image

NamedBeanHolder是里面包含一个实例化的对象,和bean的名字。resolveNamedBean()是怎么拿出Bean的关键。

一步步Debug,可以看到,它是遍历BeanFactory里面维护的beanDefinitionNames和manualSingletonNames成员变量,找出命中的beanName返回。

image

然后拿着这个beanName去找具体的bean实例。这里的代码比较长,在AbstractBeanFactory里面的doGetBean()中实现。
大意是先尝试去找手动添加bean的单例工厂里找有没有对应的实例,没有的话就往父类beanFactory里面找,最后没有的话就生成一个。

spring中一个bean是如何加载和如何注入大致如此,更细节的内容,可以自己debug看看源码。

控制反转的优点

最后来以我个人观点谈谈控制反转的优点吧。
举个例子,我要装修房子,需要门、浴具、厨具、油漆、玻璃等材料。

    decorateHouse(Door,BathThing,CookThing,....)

但是我作为一个装修工人,我需要去制造门、制造浴具,合成玻璃油漆吗?
不需要,也不关心其建造的过程,对应的会有人去做这些东西。

    door = buildDoor();
    glass = buildGlass(); 

所有材料放到建材商城里面,装修工人需要什么材料就去建材商城里面取。

对应Spring的IOC,门、玻璃等材料就是Bean,建材商城就是IOC容器,把材料放到建材商城就是Bean加载,去商城拿材料就是依赖注入的过程。

程序开发发展至今,一个简答的项目或许也要分几个模板,几个人去开发。划分好职责,设计好接口,面向接口编程。每个人只需要完成好自己那部分的工作,依赖调用就可以了。这样做同时有助于降低项目的耦合度,让项目有更好的延伸性。由此Spring的IOC就是基于以上的需求所诞生的。

总结

回顾下全文的内容

  1. ApplicationContext是Spring项目的核心配置,项目运行依赖于它,其中包含许多方面的内容。
  2. BeanFactory是Context包含的内容之一,它负责管理Bean的加载,生成,注入等内容。
  3. Spring控制反转为了降低项目耦合,提高延伸性。

本文讲Spring IOC还比较浅显,仅仅讲了如何加载的重点和注入的重点,关于生命周期,BeanFactory的处理由于篇幅问题并没有细讲。有兴趣的读者可以用Demo跑起来,一步步Debug看看。因为Demo基本是最小化的Spring IOC了,所以这个Debug不会太难,很容易就能看清楚整个流程做了什么。

Demo:https://github.com/Zack-Ku/spring-ioc-demo

如果觉得还不错,请关注微信公众号:Zack说码

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339

推荐阅读更多精彩内容