一、概念梳理
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
2.2 IoC Service Provider的职责
- 业务对象的构建管理。在IoC场景中,业务对象无需关心所依赖的对象如何构建如何取得,但这部分工作始终需要有人来做。所以,IoC Service Provider需要将对象的构建逻辑从客户端对象那里剥离出来,以免这部分逻辑污染业务对象的实现。
- 业务对象间的依赖绑定。对于IoC Service Provider来说,这个职责是最艰巨也是最重要的,这是它的最终使命之所在。如果不能完成这个职责,那么,无论业务对象如何的“呼喊”,也不会得到依赖对象的任何响应(最常见的倒是会收到一个NullPointerException)。IoC Service Provider通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖的对象注入绑定,从而保证每个业务对象在使用的时候,可以处于就绪状态。
2.3 IoC Service Provider 如何管理对象间的依赖关系
直接编码方式
配置文件方式
元数据方式(注解方式)
五、Bean的创建
1.IoC容器的初始化
Spring IOC容器初始化的核心过程可以简单的如下图表示,主要有四个步骤(还有一些如:后置加载器,国际化,事件广播器等一些过程不展开):
-
Bean定义的定位,Bean 可能定义在XML中,或者一个注解,或者其他形式。这些都被用Resource来定位,读取Resource获取BeanDefinition 注册到 Bean定义注册表中。
-
第一次向容器getBean操作会触发Bean的创建过程,实列化一个Bean时 ,根据BeanDefinition中类信息等实列化Bean。
-
将实列化的Bean放到单列Bean缓存内。
-
此后再次获取向容器getBean就会从缓存中获取。
扩展阅读:IoC容器初始化源码解析 Spring IOC容器初始化
2.Bean的注入方式
a.常用的注入方式
bean的注入方式是老生常谈的话题了,这里不展开细说。
目前主要有五种注入方式:SET注入,构造器注入,静态工厂,实例工厂,注解方式
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产生一个实例 |
init-method | 初始化方法 | bean创建时的初始化方法 |
destroy-method | 销毁方法 | bean销毁时调用的方法,仅仅在singleton模式下起作用 |
扩展阅读:自定义命名空间:2. xml自定义命名空间解析 常用的注解:Spring常用注解
3.Bean解析注册过程
这个过程完成Bean的从配置文件到解析注册成bean工厂的过程(对应代码在AbstactApplicationContext.obtainFreshBeanFactory)
-
通过读取XML配置文件获取 Resource 资源,获取这个资源包含了路径config/spring/local/appcontext-client.xml 文件下定义的BeanDefinition信息。
-
创建一个 BeanFactory,这里使用 DefaultListableBeanFactory。
-
创建一个载入 BeanDefinition 的解读器,这里使用 XmlBeanDefinitionReader 来载入 XML 文件形式 BeanDefinition,通过一个回调配置给 factory。
-
从定义好的资源位置读入配置信息,具体的解析过程由 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:
-
对bean生命周期了解不充分,在static方法中使用getBean方法,此时bean还未初始化。
2.Spring默认同id名的bean会被覆盖,一个解决办法是不允许同id的bean覆盖 参考:spring 同名bean问题 分析和解决
3.Spring的bean加载顺序。
忽视Spring bean加载顺序,造成多次执行程序,结果会有所不同
五、参考文献
https://blog.csdn.net/levena/article/details/52268472
https://www.jianshu.com/p/f9d18f495635