Spring第二篇 Spring IOC

 

一、概念梳理

1.1 什么是IOC和DI

IOC:控制反转(Inversion of Control)容器,这不是什么技术,而是一种设计思想。在Java开发中,IOC意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。

IOC一些解释:

  ●谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IOC是有专门一个容器来创建这些对象,即由IOC容器来控制对象的创建;谁控制谁?当然是IOC容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

  ●为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

举个例子:

public class A {
    public B b;
    public C c;

    public A() {
        b = new B();
        c = new C();
    }

    public void function1() {
        b.fun1();  
        c.fun2();   
    }
}

类A依赖类B、C,并且在构造函数中初始化了B、C的实例,这就是传统Java SE程序设计,它的特点就是主动去获取依赖对象。在类A中,我们构造了B、C的实例,但是仅仅调用了它们一个方法,亲自去初始化一个实例是否真的有必要?对于类A来讲,它并不需要关心类B、C是哪里来的,而IOC思想让我们避免了这些大费周章,从原来的事必躬亲变成享受服务,这就叫反转。

所有的被注入对象和依赖对象现在由IoC Service Provider统一管理。被注入对象需要 什么,直接跟IoC Service Provider招呼一声,后者就会把相应的被依赖对象注入到被注入对象中,从而 达到IoC Service Provider为被注入对象服务的目的。IoC Service Provider在这里就是通常的IoC容器所充 当的角色。从被注入对象的角度看,与之前直接寻求依赖对象相比,依赖对象的取得方式发生了反转, 控制也从被注入对象转到了IoC Service Provider那。

其实IoC就这么简单!原来是需要什么东西自己去拿,现在是需要什么东西就让别人送过来。图 2-2以两种场景,形象地说明了使用IoC模式前后的差别。

传统获取对象的方式与IOC方式对比:

DI:依赖注入(Dependency Injection),组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。IOC和DI其实是同一个概念的不同角度描述。(也有人认为DI是IOC的一种实现方式,这个不重要)

二、IoC Service Provider

2.1 什么是IoC Service Provider

虽然业务对象可以通过 IoC 方式声明相应的依赖,但是最终仍然需要通过某种角色或者服务将这 些相互依赖的对象绑定到一起,而IoC Service Provider 就对应 IoC 场景中的这一角色。
 
IoC Service Provider 在这里是一个抽象出来的概念,它可以指代任何将 IoC 场景中的业务对象绑定 到一起的实现方式。它可以是一段代码(构建函数、setter方法),也可以是一组相关的类,甚至可以是比较通用的IoC 框架或者IoC 容器实现。

2.2 IoC Service Provider的职责

IoC Service Provider 的职责相对来说比较简单,主要有两个:业务对象的构建管理业务对象间的依赖绑定
  • 业务对象的构建管理。在IoC场景中,业务对象无需关心所依赖的对象如何构建如何取得,但这部分工作始终需要有人来做。所以,IoC Service Provider需要将对象的构建逻辑从客户端对那里剥离出来,以免这部分逻辑污染业务对象的实现。
  • 业务对象间的依赖绑定。对于IoC Service Provider来说,这个职责是最艰巨也是最重要的,这是它的最终使命之所在。如果不能完成这个职责,那么,无论业务对象如何的“呼喊”,也不会得到依赖对象的任何响应(最常见的倒是会收到一个NullPointerException)。IoC Service Provider通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖的对象注入绑定,从而保证每个业务对象在使用的时候,可以处于就绪状态。

2.3 IoC Service Provider 如何管理对象间的依赖关系

直接编码方式

配置文件方式

元数据方式(注解方式)

五、Bean的创建

1.IoC容器的初始化

Spring IOC容器初始化的核心过程可以简单的如下图表示,主要有四个步骤(还有一些如:后置加载器,国际化,事件广播器等一些过程不展开):

  1. Bean定义的定位,Bean 可能定义在XML中,或者一个注解,或者其他形式。这些都被用Resource来定位,读取Resource获取BeanDefinition 注册到 Bean定义注册表中。

  2. 第一次向容器getBean操作会触发Bean的创建过程,实列化一个Bean时 ,根据BeanDefinition中类信息等实列化Bean。

  3. 将实列化的Bean放到单列Bean缓存内。

  4. 此后再次获取向容器getBean就会从缓存中获取。

扩展阅读:IoC容器初始化源码解析 Spring IOC容器初始化

2.Bean的注入方式

a.常用的注入方式

bean的注入方式是老生常谈的话题了,这里不展开细说。

目前主要有五种注入方式:SET注入,构造器注入,静态工厂,实例工厂,注解方式

参考:Spring Bean注入

b.常用的Bean的配置参数

属性

含义

说明

id

bean的唯一标识

 

class

类的完全限定名

 

parent

父类bean定义的名字

如果没有任何声明,会使用父bean,但是也可以重写父类。重写父类时,子bean 必须与父bean 兼容,也就是说,接受父类的属性值和构造器声明的值。

子bean会继承父bean的构造器声明的值,属性值,并且重写父bean的方法。如果init方法,destroy方法已声明,他们会覆盖父bean相应的设置。保留的设置会直接从子bean获取,包括depends on,auto wire mode,scope,lazy init.

abstract

声明bean是否是抽象的

该属性决定该类是否会实例化。默认是false。

注意:abstract属性不会被子bean继承,所以,abstract为true时需要对每个bean显示声明。

lazy-init

决定是否延迟实例化

如果为false,则启动时会立即实例化单例模式的bean。默认是false。

注意:lazy-init属性不会被子bean继承。

autowire

决定是否自动装配bean的属性

autowire有4中模式(该属性不会被子bean继承。):

1."no":Spring默认的模式。bean的引用必须在XML文件中通过<ref/>元素或ref属性显示定义。

2."byName":通过属性名使用自动装配。如果一个Cat类拥有一个dog属性,那么Spring会根据名字dog去寻找bean,如果没有找到bean,则不会自动装配。

3."byType":如果Spring容器只有该属性类型的一个bean,会自动装配。当有多个该属性类型的bean时会报错。如果没有,则不会自动装配。

4."constructor":针对构造器引用,和byType类似。

参考:Spring中的自动装配

depends-on

该bean初始化时依赖的其他bean

bean工厂确保其他bean在该bean之前完成初始化。

注意:依赖项一般通过bean属性或构造器声明,这个属性对其他依赖(如静态类或启动阶段数据库的准备)是必要的。

注意:depends-on属性不会被子bean继承。

scope

bean的作用域

singleton:单例模式,默认选项

prototype:非单例模式

request:对于web应用,每一个请求产生一个新的实例

session:对于web应用,一个session产生一个实例

参考:Spring Bean的scope

init-method

初始化方法

bean创建时的初始化方法

destroy-method

销毁方法

bean销毁时调用的方法,仅仅在singleton模式下起作用

扩展阅读:自定义命名空间:2. xml自定义命名空间解析   常用的注解:Spring常用注解

3.Bean解析注册过程

这个过程完成Bean的从配置文件到解析注册成bean工厂的过程(对应代码在AbstactApplicationContext.obtainFreshBeanFactory)

  1. 通过读取XML配置文件获取 Resource 资源,获取这个资源包含了路径config/spring/local/appcontext-client.xml 文件下定义的BeanDefinition信息。

  2. 创建一个 BeanFactory,这里使用 DefaultListableBeanFactory。

  3. 创建一个载入 BeanDefinition 的解读器,这里使用 XmlBeanDefinitionReader 来载入 XML 文件形式 BeanDefinition,通过一个回调配置给 factory。

  4. 从定义好的资源位置读入配置信息,具体的解析过程由 XmlBeanDefinitionReader 的 loadBeanDefinitions() 方法来完成。完成整个载入和注册 Bean 定义之后,需要的 IoC 容器就建立起来可以直接使用了。

4.Bean的创建过程

这个过程完成Bean的实例化,如果是singleton类型的Bean还会放入缓存池中。(对应源码:AbstactApplicationContext.finishBeanFactoryInitialization)

扩展阅读:Spring创建Bean过程源码解析:Spring-IOC源码解读 Spring源码深度解析

三、Bean的生命周期

1.Bean生命周期

Bean生命周期是IOC中非常核心的内容,Spring为我们预留了很多的接口,让我们在bean实例化前后、初始化前后可以写入一些自己的逻辑。

 


*Bean的生命周期


1. 根据BeanDefinition信息,实例化对象,Constructor构造方法;

2. 根据BeanDefinition信息,配置Bean的所有属性(将bean的引用注入到bean对应的属性,*可能存在循环依赖问题);

3. 如果Bean实现了BeanNameAware接口,工厂调用Bean的setBeanName,参数为Bean的Id;

4. 如果Bean实现BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;

5. 如果Bean实现ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;

5. 如果存在类实现了BeanPostProcessor接口,执行这些实现类的postProcessBeforeInitialization方法,这相当于在Bean初始化之前插入逻辑 ;

6. 如果Bean实现InitializingBean接口, 执行afterPropertiesSet方法;

7. 如果Bean指定了init-method方法,就会调用该方法。例:\<bean init-method="init"> ;

8. 如果存在类实现了BeanPostProcessor接口,执行这些实现类的postProcessAfterInitialization方法,这相当于在Bean初始化之后插入逻辑 ;

9. 这个阶段Bean已经可以使用了,scope为singleton的Bean会被缓存在IOC容器中

10. 如果Bean实现了DisposableBean接口, 在容器销毁的时候执行destroy方法。

11. 如果配置了destory-method方法,就调用该方法。例:\<bean destroy-method="customerDestroy">


扩展阅读:Spring的扩展接口 :Spring 扩展接口

2.Bean实例化顺序

 

1.解析内部类,查看内部类是否应该被定义成一个Bean,如果是,递归解析。

2.解析@PropertySource,也就是解析被引入的Properties文件。

3.解析配置类上是否有@ComponentScan注解,如果有则执行扫描动作,通过扫描得到的Bean Class会被立即解析成BeanDefinition,添加进beanDefinitionNames属性中。之后查看扫描到的Bean Class是否是一个配置类(大部分情况是,因为标识@Component注解),如果是则递归解析这个Bean Class。

4.解析@Import引入的类,如果这个类是一个配置类,则递归解析。

5.解析@Bean标识的方法,此种形式定义的Bean Class不会被递归解析

6.解析父类上的@ComponentScan,@Import,@Bean,父类不会被再次实例化,因为其子类能够做父类的工作,不需要额外的Bean了。

Spring 解决bean的循环依赖:05、spring ioc-bean的循环依赖

Spring 的bean加载顺序:Spring Bean加载顺序

四、避坑指南

相关case:

  1. 对bean生命周期了解不充分,在static方法中使用getBean方法,此时bean还未初始化。

2.Spring默认同id名的bean会被覆盖,一个解决办法是不允许同id的bean覆盖 参考:spring 同名bean问题 分析和解决

3.Spring的bean加载顺序。

Spring中Bean加载顺序引起的坑

忽视Spring bean加载顺序,造成多次执行程序,结果会有所不同

 

五、参考文献

Spring官网IOC文档

Spring-IOC源码解读

Spring源码浅析及引申

spring ioc源码学习

SpringBean生命周期详解

https://blog.csdn.net/levena/article/details/52268472

https://www.jianshu.com/p/f9d18f495635

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值