在一开始学习 Spring 的时候,我们就接触 IoC 了,作为 Spring 第一个最核心的概念,我们在解读它源码之前一定需要对其有深入的认识,记住一句话:所谓 IOC ,就是由 Spring IOC 容器来负责对象的生命周期和对象之间的关系,最终目的还是为了解耦!!
——程序员代码的追求目标:解耦、清晰、高可用
注意:Java 使用 DI 方式实现 IoC 的不止 Spring,包括 Google 的 Guice,还有一个冷门的 PicoContainer(极度轻量,但只提供 IoC)。
Spring 的 IoC 设计支持以下功能:
其中,最重要的就是依赖注入,从 XML 的配置上说, 即 ref 标签。对应 Spring RuntimeBeanReference 对象。
对于 IoC 来说,最重要的就是容器。容器管理着 Bean 的生命周期,控制着 Bean 的依赖注入。
那么, Spring 如何设计容器的呢?
Spring 作者 Rod Johnson 设计了两个接口用以表示容器。
BeanFactory
ApplicationContext
BeanFactory 粗暴简单,可以理解为就是个 HashMap,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put),获取(get)这两个功能。我们可以称之为 “低级容器”。
ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具备了更多的功能。例如资源的获取,支持多种消息(例如 JSP tag 的支持),对 BeanFactory 多了工具级别的支持等待。所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。该接口定义了一个 refresh 方法,此方法是所有阅读 Spring 源码的人的最熟悉的方法,用于刷新整个容器,即重新加载/刷新所有的 bean。
当然,除了这两个大接口,还有其他的辅助接口,但我今天不会花太多篇幅介绍他们。
为了更直观的展示 “低级容器” 和 “高级容器” 的关系,我这里通过常用的 ClassPathXmlApplicationContext 类,来展示整个容器的层级 UML 关系。
有点复杂? 先不要慌,我来解释一下。
最上面的 BeanFactory 知道吧?我就不讲了。
下面的 3 个绿色的,都是功能扩展接口,这里就不展开讲。
看下面的隶属 ApplicationContext 粉红色的 “高级容器”,依赖着 “低级容器”,这里说的是依赖,不是继承哦。他依赖着 “低级容器” 的 getBean 功能。而高级容器有更多的功能:支持不同的信息源头,可以访问文件资源,支持应用事件(Observer 模式)。
通常用户看到的就是 “高级容器”。 但 BeanFactory 也非常够用啦!
左边灰色区域的是 “低级容器”, 只负载加载 Bean,获取 Bean。容器其他的高级功能是没有的。例如上图画的 refresh 刷新 Bean 工厂所有配置。生命周期事件回调等。
好,解释了低级容器和高级容器,我们可以看看一个 IoC 启动过程是什么样子的。说白了,就是 ClassPathXmlApplicationContext 这个类,在启动时,都做了啥。
下图是 ClassPathXmlApplicationContext 的构造过程,实际就是 Spring IoC 的初始化过程。
用文字来描述这个过程:
用户构造 ClassPathXmlApplicationContext(简称 CPAC)
CPAC 首先访问了 “抽象高级容器” 的 final 的 refresh 方法,这个方法是模板方法。所以要回调子类(低级容器)的 refreshBeanFactory 方法,这个方法的作用是使用低级容器加载所有 BeanDefinition 和 Properties 到容器中。
低级容器加载成功后,高级容器开始处理一些回调,例如 Bean 后置处理器。回调 setBeanFactory 方法。或者注册监听器等,发布事件,实例化单例 Bean 等等功能,这些功能,随着 Spring 的不断升级,功能越来越多,很多人在这里迷失了方向 :)。
简单说就是:
所以,一定要把 “低级容器” 和“高级容器” 的区别弄清楚。不能一叶障目不见泰山。
好,当我们创建好容器,就会使用 getBean 方法,获取 Bean,而 getBean 的流程如下:
从图中可以看出,getBean 的操作都是在低级容器里操作的。其中有个递归操作,这个是什么意思呢?
(先简单解释一下ref–>)
<bean id="userAction" class="com.neusoft.gmsbs.gms.user.action.UserAction"
scope="prototype">
<property name="userBO" ref="userBO" />
</bean>
Spring的依赖注入也叫控制反转。bean的id也就是你说的bean的id,通过id找你想要调用的bean,ref不是一个单纯的值,ref是你的UserAction 里面有个属性叫userBO,而这个userBO是引用的你的配置文件中的这个类的;
为什么不是在加载的时候,就直接注入呢?因为加载的顺序不同,很可能 Bean_A 依赖的 Bean_B 还没有加载好,也就无法从容器中获取,你不能要求用户把 Bean 的加载顺序排列好,这是不人道的。
所以,Spring 将其分为了 2 个步骤:
这就是 IoC。
所以 ApplicationContext refresh 方法里面的操作不只是 IoC,是高级容器的所有功能(包括 IoC),IoC 的功能在低级容器里就可以实现。
说了这么多,不知道你有没有理解Spring IoC? 这里小结一下:IoC 在 Spring 里,只需要低级容器就可以实现,2 个步骤:
a. 加载配置文件,解析成 BeanDefinition 放在 Map 里。
b. 调用 getBean 的时候,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 方法 —— 完成依赖注入。
上面就是 Spring 低级容器(BeanFactory)的 IoC。
至于高级容器 ApplicationContext,他包含了低级容器的功能,当他执行 refresh 模板方法的时候,将刷新整个容器的 Bean。同时其作为高级容器,包含了太多的功能。一句话,他不仅仅是 IoC。他支持不同信息源头,支持 BeanFactory 工具类,支持层级容器,支持访问文件资源,支持事件发布通知,支持接口回调等等。
可以预见,随着 Spring 的不断发展,高级容器的功能会越来越多。
诚然,了解 IoC 的过程,实际上为了了解 Spring 初始化时,各个接口的回调时机。例如 InitializingBean,BeanFactoryAware,ApplicationListener 等等接口,这些接口的作用(在下面的spring bean的生命周期中会讲到)
但是请注意,实现 Spring 接口代表着你这个应用就绑定死 Spring 了!代表 Spring 具有侵入性!要知道,Spring 发布时,无侵入性就是他最大的宣传点之一 —— 即 IoC 容器可以随便更换,代码无需变动。而现如今,Spring 已然成为 J2EE 社区准官方解决方案,也没有了所谓的侵入性这个说法。因为他就是标准,和 Servlet 一样,你能不实现 Servlet 的接口吗?
只有四个!
是的,Spring Bean的生命周期只有四个阶段。把这四个阶段和每个阶段对应的扩展点糅合在一起虽然没有问题,但是这样非常凌乱,难以记忆。要彻底搞清楚Spring的生命周期,首先要把这四个阶段牢牢记住。实例化和属性赋值对应构造方法和setter方法的注入,初始化和销毁是用户能自定义扩展的两个阶段。在这四步之间穿插的各种扩展点,稍后会讲。
实例化 Instantiation
属性赋值 Populate
初始化 Initialization
销毁 Destruction
实例化 -> 属性赋值 -> 初始化 -> 销毁
前言: Spring作为当前Java最流行、最强大的轻量级框架,受到了程序员的热烈欢迎。准确的了解Spring Bean的生命周期是非常必要的。我们通常使用ApplicationContext作为Spring容器。这里,我们讲的也是 ApplicationContext中Bean的生命周期。而实际上BeanFactory也是差不多的,只不过处理器需要手动注册。
一、生命周期流程图
Spring Bean的完整生命周期从创建Spring容器开始,直到最终Spring容器销毁Bean,这其中包含了一系列关键点。
若容器注册了以上各种接口,程序那么将会按照以上的流程进行。当然没有注册的也就不会执行相应的步骤了。
为了好理解,下面为各种接口方法分个类
二、各种接口方法分类
Bean的完整生命周期经历了各种方法调用,这些方法可以划分为以下几类:
1、Bean自身的方法:这个包括了Bean本身调用的方法和通过配置文件中的init-method和destroy-method指定的方法
2、Bean级生命周期接口方法:这个包括了BeanNameAware、BeanFactoryAware、InitializingBean和DiposableBean这些接口的方法
3、容器级生命周期接口方法:这个包括了InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“后处理器”。
4、工厂后处理器接口方法:这个包括了AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer等等非常有用的工厂后处理器 接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。
三、上面的生命周期比较复杂,面试按照下面链接中的回答就可以了
SpringBean的生命周期链接:点击我即可查看
最后:附上一些SpringIOC的常见面试题以及答案:
IoC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦:即把各个对象类封装之后,通过IoC容器来关联这些对象类。这样对象与对象之间就通过IoC容器进行联系,但对象与对象之间并没有什么直接联系。
软件系统在没有引入IoC容器之前,对象A依赖对象B,那么A对象在实例化或者运行到某一点的时候,自己必须主动创建对象B或者使用已经创建好的对象B,其中不管是创建还是使用已创建的对象B,控制权都在我们自己手上。
如果软件系统引入了Ioc容器之后,对象A和对象B之间失去了直接联系,所以,当对象A实例化和运行时,如果需要对象B的话,IoC容器会主动创建一个对象B注入到对象A所需要的地方。
通过前面的对比,可以看到对象A获得依赖对象B的过程,由主动行为变成了被动行为,即把创建对象交给了IoC容器处理,控制权颠倒过来了,这就是控制反转的由来!
所谓依赖注入,就是由IoC容器在运行期间,动态地将某种依赖关系注入到对象之中。
依赖注入(DI)和控制反转(IoC)是从不同的角度描述的同一件事情,就是指通过引入IoC容器,利用依赖关系注入的方式,实现对象之间的解耦。
1、IoC生成对象的方式转为外置方式,也就是把对象生成放在配置文件里进行定义,这样,当我们更换一个实现子类将会变得很简单,只要修改配置文件就可以了,完全具有热插拨的特性。
2、可维护性比较好,非常便于进行单元测试,便于调试程序和诊断故障。
IOC容器要生成的对象是通过配置文件中给出定义的,用java反射技术,根据配置文件中给出的类名生成相应的对象。
在Spring IoC中经常用到一个设计模式,即工厂模式。
IOC容器要生成的对象是通过配置文件中给出定义的,用java反射技术,根据配置文件中给出的类名生成相应的对象。
在Spring IoC中经常用到一个设计模式,即工厂模式。
普通工厂模式的缺点是当我们再添加一个子类的时候,就需要修改工厂类了。如果我们添加太多的子类的时候,改动就会很多。而IOC是使用反射机制实现工厂模式:这样就算我们添加任意多个子类的时候,工厂类都不需要修改。使用反射机制实现的工厂模式可以通过反射取得接口的实例,但是需要传入完整的包和类名。而且用户也无法知道一个接口有多少个可以使用的子类,所以我们通过属性文件的形式配置所需要的子类,也在一定的程度上实现了解耦。
1、程序中由于引入了第三方IOC容器,生成对象的步骤变得有些复杂,本来是两者之间的事情,又凭空多出一道手续,所以,我们在刚开始使用IOC框架的时候,会感觉系统变得不太直观。
2、由于IOC容器生成对象是通过反射方式,在运行效率上有一定的损耗。如果你要追求运行效率的话,就必须对此进行权衡。
properties: 复杂度不够,不支持中文,其主要用途是对数据库的配置
json: 结构太轻且不够严谨,没有约束,其主要用于前后端数据的交互(不在乎格式,只在乎内容是什么)
XML: 结构严谨,可以被约束(缺点是文件太大)