2020-09-25

Spring第二篇 Spring IOC

目录

一、概念梳理

1.什么是IoC和DI

二、Bean的创建

1.IoC容器的初始化

2.Bean的注入方式

3.Bean解析注册过程

4.Bean的创建过程

三、Bean的生命周期

1.Bean生命周期

2.Bean实例化顺序

四、避坑指南

五、参考文献

作为一个后端开发,我们的日常离不开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注入,构造器注入,静态工厂,实例工厂,注解方式

参考:Spring Bean注入

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产生一个实例

参考:Spring Bean的scope

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失败

门票客诉排序不生效case原因分析

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

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