如果你每次面试前都要去背一篇Spring中Bean的生命周期,请看完这篇文章
前言
当你准备去复习Spring中Bean的生命周期的时候,这个时候你开始上网找资料,很大概率会看到下面这张图:
先不论这张图上是否全面,但是就说这张图吧,你是不是背了又忘,忘了又背?
究其原因在于,你没有理解为什么需要这些步骤,也不知道为什么要按这个顺序执行
笔者在阅读完整个IOC
跟AOP
的源码后,希望通过这篇文章讲一讲我的Spring中Bean生命周期的看法,帮助大家能理解性的记忆整个流程,而不是死记硬背!
基础知识补充
所谓理解也是建立在有一定知识储备的基础上的,所以这里先补充一些基础概念
Bean创建的三个阶段
Spring在创建一个Bean时是分为三个步骤的
- 实例化,可以理解为new一个对象
- 属性注入,可以理解为调用setter方法完成属性注入
- 初始化,你可以按照Spring的规则配置一些初始化的方法(例如,
@PostConstruct
注解)
生命周期的概念
Bean的生命周期指的就是在上面三个步骤中后置处理器BeanPostprocessor
穿插执行的过程
后置处理器的分析
按照实现接口进行分类
- 直接实现了
BeanPostProcessor
接口
最简单的后置处理器,也就是说直接实现了BeanPostProcessor
接口,这种后置处理器只能在初始化前后执行
public interface BeanPostProcessor {
// 初始化前执行的方法
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
// 初始化后执行的方法
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
- 直接实现了
InstantiationAwareBeanPostProcessor
接口
在第一种后置处理的基础上进行了一层扩展,可以在Bean的实例化阶段前后执行
// 继承了BeanPostProcessor,额外提供了两个方法用于在实例化前后的阶段执行
// 因为实例化后紧接着就要进行属性注入,所以这个接口中还提供了一个属性注入的方法
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
// 实例化前执行
default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
return null;
}
// 实例化后置
default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return true;
}
// 属性注入
default PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
return pvs;
}
}
- Spring内部专用的后置处理器
可能有的小伙伴认为,第三种后置处理器肯定就是用来在属性注入前后执行了的吧。我只能说,大兄弟,太天真了,看看下面这张图
这种情况下再为属性注入阶段专门提供两个方法是不是有点多余呢?实际上第三种后置处理器是Spring为了自己使用而专门设计的
public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor {
// 推测bean的类型,例如在属性注入阶段我们就需要知道符合依赖类型的Bean有哪些
@Nullable
default Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException {
return null;
}
// 推断出所有符合要求的构造函数,在实例化对象的时候我们就需要明确到底使用哪个构造函数
@Nullable
default Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName)
throws BeansException {
return null;
}
// 获取一个提前暴露的对象,用于解决循环依赖
default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
return bean;
}
}
一般我们在探究生命周期的时候都不会考虑这种后置处理器的执行
生命周期详细介绍
在了解了上面的概念后,我们再来看看这张图
至少现在这张图上缺少了实例化前后后置处理器的执行流程,对吧?
再补充上这一点之后,我们再来看看,属性注入后紧接着已经是初始化的阶段,在初始化阶段开始前应该要调用BeanPostProcessor
的预初始化方法(postProcessBeforeInitialization
),然后调用自定义的初始化方法,最后调用postProcessAfterInitialization
,这是没有问题,但是为什么要在初始前还要调用Aware接口的方法,如果你看了源码的话可能会说,源码就是这么写的,别人就是这么设计的,但是为什么要这么设计呢?我们看源码到一定阶段后不应该仅仅停留在是什么的阶段,而应该多思考为什么,这样能帮助你更好的了解这个框架
那么为什么Aware接口非要在初始化前执行呢?
这样做的目的是因为,初始化可能会依赖Aware接口提供的状态,例如下面这个例子
@Component
public class A implements InitializingBean, ApplicationContextAware {
ApplicationContext applicationContext;
@Override
public void afterPropertiesSet() throws Exception {
// 初始化方法需要用到ApplicationContextAware提供的ApplicationContext
System.out.println(applicationContext);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
这种情况下Aware接口当然要在初始化前执行啦!
另外,在讨论Bean的初始化的时候经常会碰到下面这个问题,@PostConstruct
,afterPropertiesSet
跟XML中配置的init-method
方法的执行顺序。
请注意,@PostConstruct
实际上是在postProcessBeforeInitialization
方法中处理的,严格来说它不属于初始化阶段调用的方法,所以这个方法是最先调用的
其次我们思考下是调用afterPropertiesSet
方法的开销大还是执行配置文件中指定名称的初始化方法开销大呢?我们不妨用伪代码演示下
// afterPropertiesSet,强转后直接调用
((InitializingBean) bean).afterPropertiesSet()
// 反射调用init-method方法
// 第一步:找到这个方法
Method method = class.getMethod(methodName)
// 第二步:反射调用这个方法
method.invoke(bean,null)
相比而言肯定是第一种的效率高于第二种,一个只是做了一次方法调用,而另外一个要调用两次反射。
因此,afterPropertiesSet
的优先级高于XML配置的方式
所以,这三个方法的执行顺序为:
@PostConstruct
注解标注的方法- 实现了
InitializingBean
接口后复写的afterPropertiesSet
方法 - XML中自定义的初始化方法
在完成初始化,没什么好说的了,最后调用一下postProcessAfterInitialization
,整个Bean的生命周期到此结束
总结
本文的主要目的是想要帮助大家更好的理解整个Bean的生命周期,不过理解是建立在有一定知识存储的基础上的
你至少要对Bean的后置处理器跟Bean创建有一个大概的理解,那么通过本文你能理清一些细节方面的东西
例如,为什么Aware接口执行在初始化阶段之前?为什么初始化的三个方法会按
@PostConstruct
,afterPropertiesSet
,XML中定义的初始化方法这个顺序执行。
本文也将是我整个Spring关于IOC
跟AOP
的最后一篇文字,在这之后我打算做一个Spring事务专题,预计6到7篇文章,事务结束后关于整个Spring源码的学习也就结束啦!本来预期要一年才能完成,不过因为最近离职了,所以打算全职在家写完这个系列!
如果本文对你由帮助的话,记得点个赞吧!也欢迎关注我的公众号,微信搜索:程序员DMZ,或者扫描下方二维码,跟着我一起认认真真学Java,踏踏实实做一个coder。
我叫DMZ,一个在学习路上匍匐前行的小菜鸟!
如果你每次面试前都要去背一篇Spring中Bean的生命周期,请看完这篇文章的更多相关文章
- 面试突击80:说一下 Spring 中 Bean 的生命周期?
Java 中的公共类称之为 Bean 或 Java Bean,而 Spring 中的 Bean 指的是将对象的生命周期,交个 Spring IoC 容器来管理的对象.所以 Spring 中的 Bean ...
- 面试Spring之bean的生命周期
找工作的时候有些人会被问道Spring中Bean的生命周期,其实也就是考察一下对Spring是否熟悉,工作中很少用到其中的内容,那我们简单看一下. 在说明前可以思考一下Servlet的生命周期:实例化 ...
- 图解Java线程的生命周期,看完再也不怕面试官问了
文章首发自个人微信公众号: 小哈学Java https://www.exception.site/java-concurrency/java-concurrency-thread-life-cycle ...
- Spring学习10- bean的生命周期(阿里面试题目两次面试均提到)
找工作的时候有些人会被问道Spring中Bean的生命周期,其实也就是考察一下对Spring是否熟悉,工作中很少用到其中的内容,那我们简单看一下. 在说明前可以思考一下Servlet的生命周期:实例化 ...
- Spring第三天,详解Bean的生命周期,学会后让面试官无话可说!
点击下方链接回顾往期 不要再说不会Spring了!Spring第一天,学会进大厂! Spring第二天,你必须知道容器注册组件的几种方式!学废它吊打面试官! 今天讲解Spring中Bean的生命周期. ...
- 看完这篇Redis缓存三大问题,保你面试能造火箭,工作能拧螺丝。
前言 日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题. 一旦涉及大数据量的需求,如一些商品抢购的情景,或者主页访问量瞬间较 ...
- 面试官:展开说说,Spring中Bean对象是如何通过注解注入的?
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 章节目录(手写Spring,让你了解更多) [x] 第 01 章:开篇介绍,我要带你撸 Spr ...
- 【面试普通人VS高手】Spring 中Bean的作用域有哪些?
一个工作3年的小伙子,去面试被问到Spring里面的问题. 这个问题比较简单,但是他却没有回答上来. 虽然他可以通过搜索引擎找到答案,但是如果没有理解,下次面试还是不会! 这个面试题是: " ...
- [转帖]看完这篇文章,我奶奶都懂了https的原理
看完这篇文章,我奶奶都懂了https的原理 http://www.17coding.info/article/22 非对称算法 以及 CA证书 公钥 核心是 大的质数不一分解 还有 就是 椭圆曲线算法 ...
随机推荐
- 8种排序算法 Java实现
冒泡排序 O(n2) 两个数比较大小,较大的数下沉,较小的数冒起来. public static void bubbleSort(int[] a) { //临时变量 int temp; //i是循环次 ...
- tcpdump抓包工具的基本使用
为了更好的深入理解计算机网络等相关知识,例如TCP\UDP\IP等,我们就必须利用tcpdump.Wireshark等工具对网络进行分析.本篇博文主要记录一下tcpdump这个网络分析利器的一些基本使 ...
- Bestcoder Round8
4989Summary 既然用C++了就偷懒直接用STL大法了 #include<iostream> #include<algorithm> #include<vecto ...
- linux 上设置mysql开机自启
此方式是通过安装包安装的,如果是yum安装的rpm包,可参考yum安装MySQL8.0 三个月之前安装的mysql,记得是设置了开机自启,但是今天再次进入的时候发现,无法登录,报错如下 原因是mysq ...
- EJB JMS javax.naming.NameNotFoundException: XXX not bound
练习EJB MessageDrivenBean时部署不成功,一直报错 09:57:29,017 WARN [JmsActivation] Failure in jms activation org.j ...
- python 之 数据类型初接触
python 之 数据类型初接触 标准数据类型 Python3 中有六个标准的数据类型: Number(数字) String(字符串) List(列表) Tuple(元组) Set(集合) Dicti ...
- shiro之redis频繁访问问题
目前安全框架shiro使用较为广泛,其功能也比较强大.为了分布式session共享,通常的做法是将session存储在redis中,实现多个节点获取同一个session.此实现可以实现session共 ...
- 使用docker创建redis容器
1.拉取redis镜像
- Python实用笔记 (24)面向对象高级编程——使用@property
这显然不合逻辑.为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数: clas ...
- git和github入门指南(2.1)
2.git常用命令 2.1.git的三个区 1.工作区 工作区就是你项目所在目录,这个目录是可以非常直观的看到的,编写代码主要在这个目录进行,例如: 2.暂存区 暂存区从字面上去理解就是用来暂时保存项 ...