Spring第二篇 Spring IOC
目录
作为一个后端开发,我们的日常离不开Spring,尤其是Spring的IoC,但是你真的了解Spring IoC其中的细节吗?
Spring的Bean是怎么创建的呢?bean的生命周期是怎么样的?其中有什么容易踩坑的点吗?让我们带着疑问一起来看看。
一、概念梳理
1.什么是IoC和DI
IoC:控制反转(Inversion of Control)容器,这不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
IoC一些解释:
●谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
●为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
DI:依赖注入(Dependency Injection),组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。IoC和DI其实是同一个概念的不同角度描述。
传统获取对象的方式与IoC方式对比:
二、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的配置参数
属性含义说明
idbean的唯一标识
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初始化时依赖的其他beanbean工厂确保其他bean在该bean之前完成初始化。
注意:依赖项一般通过bean属性或构造器声明,这个属性对其他依赖(如静态类或启动阶段数据库的准备)是必要的。
注意:depends-on属性不会被子bean继承。
scopebean的作用域singleton:单例模式,默认选项
prototype:非单例模式
request:对于web应用,每一个请求产生一个新的实例
session:对于web应用,一个session产生一个实例
init-method初始化方法bean创建时的初始化方法
destroy-method销毁方法bean销毁时调用的方法,仅仅在singleton模式下起作用
扩展阅读:自定义命名空间:2. xml自定义命名空间解析
c.常用的注解:Spring常用注解
3.Bean解析注册过程
这个过程完成Bean的从配置文件到解析注册成bean工厂的过程(对应代码在AbstactApplicationContext.obtainFreshBeanFactory)
XML
Resource
BeanDefinition
BeanFactory
读取
解析
注册
通过读取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)
start
转换对应的beanName
尝试从缓存中加载单例
N
Y
缓存中加载的单例为空
doGetBean
transformedBeanName
getSingleton
Bean实例化
getObjectForBeanInstance
类型转换
end
原型模式依赖检查
检测是否到父类工厂加载bean
存在循环依赖且原型bean,抛异常中断加载
isPrototypeCurrentlyInCreation
parentBeanFactory != null && !containsBeanDefinition(beanName)
转化为RootBeanDefinition
getMergedLocalBeanDefinition
寻找Bean的依赖
getDependsOn
Y
N
存在依赖的bean
优先加载依赖的bean
针对不同的scope创建bean
单例模式
原型模式
bean的模式
getBean
getSingleton
根据单例模式创建bean
创建bean
bean实例化
类型转换
指定的scope上创建bean
创建bean
bean实例化
createBean
getObjectForBeanInstance
getObjectForBeanInstance
createBean
bean实例化
end
getObjectForBeanInstance
name参数可能是别名或者FactoryBean,需要一系列解析
单例的bean先从缓存中加载
加载失败再尝试从singletonFactory中加载
为避免循环依赖,先将bean的ObjectFactory加入缓存
获取的bean如果
符合requireType指定类型则直接返回
否则转化为指定类型
这里得到的是工厂bean的初始状态,我们真正想要的是工厂bean中定义的factory-method方法中返回的bean
递归调用,
优先加载当前bean的依赖bean
扩展阅读:Spring创建Bean过程源码解析:Spring-IOC源码解读Spring源码深度解析
三、Bean的生命周期
1.Bean生命周期
Bean生命周期是IOC中非常核心的内容,Spring为我们预留了很多的接口,让我们在bean实例化前后、初始化前后可以写入一些自己的逻辑。
实例化Bean对象
设置对象属性
检查Aware相关接口并设置相关依赖
BeanPostProcessor前置处理
检查是否是InitializingBean以决定是否调用afterPropertiesSet方法
检查是否配有自定义的init-method
BeanPostProcessor后置处理
注册必要的Destruction相关回调接口
使用中
是否实现DisposableBean接口
是否配置有自定义的DisposableBean接口
*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还未初始化。
2018-04-08 account-server上线大量NoClassDefFoundError错误
2.Spring默认同id名的bean会被覆盖,一个解决办法是不允许同id的bean覆盖 参考:spring 同名bean问题 分析和解决
CaseStudy-20170321-paynotice上线导致未发券
CaseStudy-20150113-支付成功后与指纹相关的数据没有发送给aegis
2018年Q4-CaseStudy-20181110-促销组-活动创建引入新事务管理器导致促销活动数据不完整或未正常开始
CaseStudy-20190108 [API研发组][bug] -indexapi读取MCC失败
3.Spring的bean加载顺序。
忽视Spring bean加载顺序,造成多次执行程序,结果会有所不同
五、参考文献