Spring源码:Bean的生命周期(二)
前言
让我们继续讲解Spring的Bean实例化过程。在上一节中,我们已经讲解了Spring是如何将Bean定义加入到IoC容器中,并使用合并的Bean定义来包装原始的Bean定义。接下来,我们将继续讲解Spring的 getBean()
方法,特别是针对 FactoryBean
的解析。
在 getBean()
方法中,Spring还支持对 FactoryBean
进行特殊处理。FactoryBean
是一个能够生成Bean实例的工厂Bean,其定义了 getObject()
方法,返回的是一个由工厂Bean管理的对象实例。在使用 getBean()
方法获取 FactoryBean
类型的Bean时,Spring会首先获取 FactoryBean
的实例,然后调用其 getObject()
方法来获取由工厂Bean创建的实际Bean实例。
因此,在使用 getBean()
方法获取Bean实例时,我们需要注意是否需要对 FactoryBean
进行特殊处理。如果需要获取 FactoryBean
的实例而不是它所管理的对象实例,可以在Bean名称前加上 &
符号来进行标识。例如:&myFactoryBean
表示获取 myFactoryBean
的实例。但是博主看到第一篇源码写的篇幅确实有些长,可能对于大家伙的碎片化时间掌握的不是很充分,所以以后我会尽力控制篇幅长度,既保证逻辑的连续性也保证尽快可以看完,那么接下来开始进入正题getbean方法之FactoryBean解析。
FactoryBean
所有符合过滤条件的Bean在Spring解析后都会被转化为合并后的Bean定义。尽管Spring提供了 getBean()
方法用于获取Bean实例,但实际上它底层仍然使用 createBean()
方法来创建Bean实例。在创建Bean实例之前,Spring先对当前Bean定义进行判断,以确定其是否为 FactoryBean
类型:
public void preInstantiateSingletons() throws BeansException {
if (logger.isTraceEnabled()) {
logger.trace("Pre-instantiating singletons in " + this);
}
// Iterate over a copy to allow for init methods which in turn register new bean definitions.
// While this may not be part of the regular factory bootstrap, it does otherwise work fine. List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// Trigger initialization of all non-lazy singleton beans...
for (String beanName : beanNames) {
// 获取合并后的BeanDefinition
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
// 获取FactoryBean对象
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(
(PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
// 创建真正的Bean对象(getObject()返回的对象)
getBean(beanName);
}
}
}
else {
// 创建Bean对象
getBean(beanName);
}
}
}
// 所有的非懒加载单例Bean都创建完了后
// Trigger post-initialization callback for all applicable beans...
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
StartupStep smartInitialize = this.getApplicationStartup().start("spring.beans.smart-initialize")
.tag("beanName", beanName);
SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
smartSingleton.afterSingletonsInstantiated();
return null;
}, getAccessControlContext());
}
else {
smartSingleton.afterSingletonsInstantiated();
}
smartInitialize.end();
}
}
}
他的源码逻辑大致如下:
- Spring会根据
beanName
去bean
定义Map
中获取当前合并的Bean
定义。 - Spring会对当前
Bean
定义进行判断,包括判断当前Bean
是否为抽象的、是否为单例、是否懒加载,以及是否为FactoryBean
。如果是FactoryBean
,则会走FactoryBean
的创建逻辑,否则会走单例Bean
的创建逻辑。 - 当所有单例非懒加载的
Bean
创建完成后,Spring会遍历所有单例Bean
,判断其是否为SmartInitializingSingleton
类型。如果是,则会自动调用afterSingletonsInstantiated
方法。
isFactoryBean
由于创建 Bean
的逻辑比较复杂,其中包含了许多细节,因此,在这里我们特别提到了一个方法 isFactoryBean()
。之所以要提到这个方法,是因为Spring支持使用 FactoryBean
来创建复杂对象。下面是该方法的主要源码:
public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException {
String beanName = transformedBeanName(name);
Object beanInstance = getSingleton(beanName, false);
if (beanInstance != null) {
return (beanInstance instanceof FactoryBean);
}
// No singleton instance found -> check bean definition.
if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory) {
// No bean definition found in this factory -> delegate to parent.
return ((ConfigurableBeanFactory) getParentBeanFactory()).isFactoryBean(name);
}
return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName));
}
大致逻辑如下:
transformedBeanName
的作用是不管传入的参数是&×××
还是×××
,都返回×××
。这是因为Spring标记FactoryBean
时使用&×××
作为FactoryBean
的beanName
。getSingleton
方法从单例池中获取Bean
实例,如果该实例是FactoryBean
,则直接返回该实例。- 如果
BeanFactory
中的Bean
定义Map
中不包含该beanName
的Bean
定义,并且当前BeanFactory
的父BeanFactory
实现了ConfigurableBeanFactory
接口,那么就需要查看当前父BeanFactory
中是否有该实例,并且判断该实例是否为FactoryBean
。举个例子来说:
// 创建一个父Spring容器
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
parent.register(AppConfig.class);
parent.refresh();
// 创建一个Spring容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.setParent(parent);
applicationContext.register(AppConfig1.class);
applicationContext.refresh();
UserService bean = applicationContext.getBean(UserService.class);
bean.test();
- 如果并没有实例化出来的bean,那么对bean定义进行判断。
protected boolean isFactoryBean(String beanName, RootBeanDefinition mbd) {
Boolean result = mbd.isFactoryBean;
if (result == null) {
// 根据BeanDefinition推测Bean类型(获取BeanDefinition的beanClass属性)
Class<?> beanType = predictBeanType(beanName, mbd, FactoryBean.class);
// 判断是不是实现了FactoryBean接口
result = (beanType != null && FactoryBean.class.isAssignableFrom(beanType));
mbd.isFactoryBean = result;
}
return result;
}
注释也基本写好了,基本上就是根据BeanDefinition推测Bean类型(获取BeanDefinition的beanClass属性),再根据bean类型判断是不是实现了FactoryBean接口,然后返回判断结果。
SmartFactoryBean
在 getBean
方法中,我们可以获取 FactoryBean
的实例并返回。接下来的步骤是判断当前的 FactoryBean
是否实现了 SmartFactoryBean
接口。需要注意的是,SmartFactoryBean
是 FactoryBean
接口的一个子接口。虽然我们在实现 FactoryBean
接口时不必实现 SmartFactoryBean
接口,但是如果实现了 SmartFactoryBean
接口,那么在创建 FactoryBean
时就会调用 getObject
方法返回实例。正常情况下,只有当容器启动完成后才会调用 getObject
方法。如果我们想在初始化时就调用,可以这样实现:
@Component
public class UserFactory implements SmartFactoryBean {
@Override
public Object getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return User.class;
}
@Override
public boolean isEagerInit() {
return true;
}
}
结语
FactoryBean 和 BeanFactory 是两个不同的概念。前者是一个接口,我们可以在实现该接口时通过调用 getObject
方法来返回实例,同时 FactoryBean
本身也是一个实例。后者是 Spring 容器的工厂,通过其中的 bean
定义 Map
一个一个地实例化我们通过注解等方式注入进去的 bean
工厂。在判断 FactoryBean
时,如果当前 BeanFactory
中没有对应的 bean
定义,那么就会去父容器中寻找相应的 bean
定义并进行判断。如果我们的类实现了 SmartFactoryBean
接口,那么它将会在 Spring 容器启动时就会调用 getObject
方法创建实例。接下来,我们将分几个小节来讲解 getBean
方法是如何实例化 bean
的,因为篇幅过长会影响读者的注意力和学习效果。
Spring源码:Bean的生命周期(二)的更多相关文章
- (转)Spring管理的Bean的生命周期
http://blog.csdn.net/yerenyuan_pku/article/details/52834011 bean的初始化时机 前面讲解了Spring容器管理的bean的作用域.接着我们 ...
- Spring源码解析之ConfigurationClassPostProcessor(二)
上一个章节,笔者向大家介绍了spring是如何来过滤配置类的,下面我们来看看在过滤出配置类后,spring是如何来解析配置类的.首先过滤出来的配置类会存放在configCandidates列表, 在代 ...
- Spring 容器中 Bean 的生命周期
Spring 容器中 Bean 的生命周期 1. init-method 和 destory-method 方法 Spring 初始化 bean 或销毁 bean 时,有时需要作一些处理工作,因此 s ...
- Spring实战(二)Spring容器和bean的生命周期
引入问题: 在XML配置文件中配置bean后,这些文件又是如何被加载的?它们被加载到哪里去了? Spring容器——框架核心 1.什么是Spring容器?它的功能是什么? 在基于Spring的应用中, ...
- (spring-第1回【IoC基础篇】)Spring容器中Bean的生命周期
日出日落,春去秋来,花随流水,北雁南飞,世间万物皆有生死轮回.从调用XML中的Bean配置信息,到应用到具体实例中,再到销毁,Bean也有属于它的生命周期. 人类大脑对图像的认知能力永远高于文字,因此 ...
- Spring源码--Bean的管理总结(一)
前奏 最近看了一系列解析spring管理Bean的源码的文章,在这里总结下,方便日后复盘.文章地址https://www.cnblogs.com/CodeBear/p/10336704.html sp ...
- IoC基础篇(一)--- Spring容器中Bean的生命周期
日出日落,春去秋来,花随流水,北雁南飞,世间万物皆有生死轮回.从调用XML中的Bean配置信息,到应用到具体实例中,再到销毁,Bean也有属于它的生命周期. 人类大脑对图像的认知能力永远高于文字,因此 ...
- spring源码-bean之增强初始化-3
一.ApplicationContext的中文意思是“应用上下文”,它继承自BeanFactory接口,除了包含BeanFactory的所有功能之外,在国际化支持.资源访问(如URL和文件).事件传播 ...
- spring框架中Bean的生命周期
一.Bean 的完整生命周期 在传统的Java应用中,bean的生命周期很简单,使用Java关键字 new 进行Bean 的实例化,然后该Bean 就能够使用了.一旦bean不再被使用,则由Java自 ...
- Spring 学习笔记---Bean的生命周期
生命周期图解 由于Bean的生命周期经历的阶段比较多,我们将通过一个图形化的方式进行描述.下图描述了BeanFactory中Bean生命周期的完整过程: Bean 的生命周期从Spring容器着手实例 ...
随机推荐
- lambda表达式--箭头函数
箭头函数(匿名函数):输入参数+->+函数结果(只有当函数需要执行多条语句时,才需要return关键字和花括号) 什么是Lambda? 我们知道,对于一个Java变量,我们可以赋给其一个&quo ...
- Navicat 连接MySQL数据库 报错2059
Navicat 连接MySQL数据库 报错2059 - authentication plugin 'caching_sha2_password'的解决办法 2059 - Authentication ...
- Java笔记第三弹
Map集合的获取功能 V get(Object key);//根据键获取值 Set<K> keySet();//获取所有键的集合 Collection<K> values(); ...
- Spring--注解开发+依赖注入
自动装配 数据层: 业务层: 自动装配: 结果: 若是将自动装配的注解放在set函数处: 结果: 结果相同,若是将set方法去掉的话: 结果: 这样的话,set方法也得到解放了耶! 以上都是按照类型装 ...
- 在CentOS上编译最新版linux内核(linux-5.19.9)
从官网下载最新版的Linux内核源码,本教程使用linux-5.19.9进行编译. 实验环境(CentOS-Stream-8) $ uname -a Linux localhost.localdoma ...
- CAS乐观锁(原子操作)
更多内容,前往 IT-BLOG 锁主要分为两种:乐观锁和悲观锁,而 synchronized 就属于一种悲观锁,每次在操作数据前都会加锁.乐观锁是指:乐观的认为自己在操作数据时,别人不会对当前数据进行 ...
- Synchronized 关键字详解
更多内容,前往 IT-BLOG Synchronized原理分析 加锁和释放锁的原理 深入JVM看字节码,创建如下的代码: 1 public class SynchronizedDemo2 { 2 O ...
- grub 命令使用
命令列表 ubuntu 的 iso 盘内一般有 command.lst 这个文件,里面是 grub 支持的命令 加载字体 ( 方便中文显示 ) grub> loadfont $prefix/fo ...
- 浅谈ChatGPT如何取代前端开发工程师
1.ChatGPT 是什么? ChatGPT 是一种基于深度学习的自然语言处理技术,它可以生成高质量的自然语言文本.该技术是由 OpenAI 团队 开发,旨在使计算机能够像人类一样理解和产生自然语言. ...
- 超详细!新手如何创建一个Vue项目
目录 一.在官网下载Vue.js 二.使用<script>标签直接引入本地的vue.js 三.使用CDN引入Vue.js 四.验证是否安装成功 五.安装Vue Devtools浏览器调试插 ...