SpringBean的生命周期——doGetBean函数探究
Posted 杨 戬
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBean的生命周期——doGetBean函数探究相关的知识,希望对你有一定的参考价值。
文章目录
- SpringBean的生命周期:doGetBean
- 全流程
SpringBean的生命周期:doGetBean
下面我们来具体看一下这个doGetBean函数
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
阶段1:处理名称,检查缓存
- 先把别名解析为实际名称,再进行后续处理
- 若要 FactoryBean 本身,需要使用 & 名称获取
- singletonObjects 是一级缓存,放单例成品对象
- singletonFactories 是三级缓存,放单例工厂
- earlySingletonObjects 是二级缓存,放单例工厂的产品,可称为提前单例对象
Bean小案例:别名获取时机
先看一个小案例,我们测试带入一下SpringBean世界:
package com.yyl.bean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
import org.springframework.context.support.GenericApplicationContext;
public class Application
public static void main(String[] args)
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("bean1", Bean1.class);
// 起别名
context.registerAlias("bean1","alias1");
context.registerAlias("bean1","alias2");
context.refresh();
System.out.println(context.getBean("bean1"));
System.out.println(context.getBean("alias1"));
System.out.println(context.getBean("alias2"));
static class Bean1
static class Bean2
运行结果如下:因为都是单例嘛,所以用bean1和别名取到的都是一个对象
那么大家思考一个问题?
他们是什么时候去替换这个别名的呢?
测试一下呗,咱们把断点加在获取别名上:
执行查看执行的方法:
可以看到调用了ApplicationContext里的方法,但是从refresh函数里知道,application内部管理bean还是调用BeanFactory去管理,所以我们继续跟踪源码:
我们直接跟进getBean的源码
找到AbstractBeanFactory中getBean的实现,我们可以看到他是调用了doGetBean方法来创建(获取Bean)
跟进这个函数,我们让Debug运行到这:
我们可以看到传进来的name就是别名alias1
继续往下跟踪,可以看到在这个函数里就找到了真正的bean1
获取工厂Bean
我们修改下上面的代码,添加一个工厂Bean:
package com.yyl.bean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
import org.springframework.context.support.GenericApplicationContext;
public class Application
public static void main(String[] args)
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("bean1", Bean1.class);
// 起别名
context.registerAlias("bean1","alias1");
context.registerAlias("bean1","alias2");
// 配置工厂Bean
context.registerBean("bean2",Bean2FactoryBean.class);
context.refresh();
// 获取工厂Bean
System.out.println(context.getBean("bean2"));
System.out.println(context.getBean("bean2"));
static class Bean1
static class Bean2
// 工厂Bean
static class Bean2FactoryBean implements FactoryBean<Bean2>
@Override
public Bean2 getObject() throws Exception
return new Bean2();
@Override
public Class<?> getObjectType()
return Bean2.class;
可以看到拿到的都是工厂Bean,虽然是配置的Bean2FactoryBean,但是他实现的是FactoryBean,他会获取传入的真正的bean 的类型也就是Bean2
那么我就不想要这个产品了呢?我就要工厂本身那怎么办呢?
我们只需要在bean名前加上个&就能获取工厂本身了:
再运行:
获取工厂Bean流程探究
最后肯定是执行的doGetBean,那我们把断点加在那!
首先进入到的是名字转换:
下一过程,他就转化完了,他还会保留之前的name值
然后他就会去我们BeanFactory中的singletonObjects中去对比找我们的bean2
跟进一下getSingleton源码可以看到他在singletonObjects中去获取
拿到这个工厂之后,再调用getObjectForBeanInstance去看你是想要工厂(Bean2FactoryBean)还是要产品(Bean2),我们再跟进一下getObjectForBeanInstance源码
/**
* 获取给定 bean 实例的对象,无论是 bean实例本身或其创建的对象(或者是 FactoryBean)
* @param beanInstance 共享 bean 实例
* @param name 可能包含工厂取消引用前缀的名称
* @param beanName 规范的 bean 名称
* @param mbd 合并的 bean 定义
* @return 要为 bean 公开的对象
*/
protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd)
// 如果 bean 不是工厂,不要让调用代码尝试 取消引用工厂。
if (BeanFactoryUtils.isFactoryDereference(name))
if (beanInstance instanceof NullBean)
return beanInstance;
if (!(beanInstance instanceof FactoryBean))
throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
// 现在我们有了 bean 实例,它可能是普通的 bean 或 FactoryBean。
// 如果是 FactoryBean,我们用它来创建一个 bean 实例,除非
// 调用者实际上想要一个对工厂的引用
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name))
return beanInstance;
Object object = null;
if (mbd == null)
object = getCachedObjectForFactoryBean(beanName);
if (object == null)
// 从工厂返回 bean 实例
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
// 如果是单例,则缓存从 FactoryBean 获得的对象
if (mbd == null && containsBeanDefinition(beanName))
mbd = getMergedLocalBeanDefinition(beanName);
boolean synthetic = (mbd != null && mbd.isSynthetic());
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
return object;
可以看到最后能根据传入的 name,beanName判断是工厂还是产品,最终返回。
SpringBean三级缓存
刚才在查询Bean的时候调用了下面的这个方法,调试的时候是直接在this的beanmap中获取
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
但是getSingleton的内部还有二级缓存,三级缓存的调用,我们进来查看一下:
protected Object getSingleton(String beanName, boolean allowEarlyReference)
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName))
synchronized (this.singletonObjects)
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference)
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null)
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
return singletonObject;
他首先会调用:this.singletonObjects.get(beanName);
方法查询singletonObjects有没有传进来的bean,这个就算是一级缓存,也可以叫单例池,如果一级缓存里有,那么就会跳过if,直接返回,但是如果没有,他就会继续查询二级三级缓存,下面我们来看一下:
来看一下谁是二级三级
-
earlySingletonObjects:二级缓存
-
singletonFactory:三级缓存,存工厂对象,它生产的产品会存到二级缓存中
当我们有循环依赖的时候才会用到他们,这里先不讲解。
这里也用到了一个经典的双重检查锁,值得一瞅:
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName))
synchronized (this.singletonObjects)
if (singletonObject == null && allowEarlyReference)
......
阶段2:检查父工厂
处理父子容器
- 父子容器的 bean 名称可以重复
- 优先找子容器的 bean,找到了直接返回,找不到继续到父容器找
父子容器初始案例
package com.yyl.bean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.support.GenericApplicationContext;
public class Application2
public static void main(String[] args)
GenericApplicationContext parent = new GenericApplicationContext();
parent.registerBean("bean1",Bean1.class,()->new Bean1("parent bean"));
parent.refresh();
GenericApplicationContext child = new GenericApplicationContext();
child.registerBean("bean2",Bean2.class);
child.refresh();
// 获取Bean
System.out.println(child.getBean("bean2"));
System.out.println(parent.getBean("bean1"));
static class Bean1
private String name;
public Bean1(String name)
this.name=name;
@Override
public String toString()
return "Bean1" +
"name='" + name + '\\'' +
'';
static class Bean2
我们这样运行是完全没问题的:
但是我们要是调用child的bean1会发现报错
// 直接获取会报错 NoSuchBeanDefinitionException 找不到bean1的定义
System.out.println(child.getBean("bean1"));
因为没有这种关联关系,下面我们来增加一个父子容器关系
// 建立父容器
child.setParent(parent);
再运行就没有问题了:
PS:类有有参构造,在注册时,不传入参数,会报错:
当Spring发现需要通过有参构造方法的方式构建对象实例时,就会挨个处理构造方法中的参数,且必须先对这些参数进行构建,因此会按照参数类型从
beanDefinitionNames
集合中寻找,看看容器中有没有相关的bean存在,如果找不到就会报错。
下面我们来具体探究一下源码流程,老规矩打个断点:
直接走到doGetBean的getSingleton函数这
我们可以看到此时单例池只有bean2,也就是子bean,还没有parent bean
那么此时他执行下一步是找不到的,找不到他就会执行else逻辑,else逻辑的中间部分我们可以看到就是处理父容器的部分
接着执行
BeanFactory parentBeanFactory = getParentBeanFactory();
看看有没有父容器,在此时结果是有的
右键我们也可以查看一下这个parentBeanFactory里面的详细信息:
可以看到是能检索到bean1的详细信息的:
得到了父容器,下面就进行了父容器的doGetBean 方法:
此时他能找到父容器的单例池中的singleObjects
再往下走,可以看到又到了doGetBean 方法,不过这里是父容器的doGetBean
再往下走getSingleton就能拿到我们的bean1(parent bean)了
再往下就和单个bean的doGetBean 一样了,就不再探究了
但是假如我们在子容器里注册
child.registerBean("bean1",Bean1.class,()->new Bean1("child create parent bean"));
那么他就不会再去找父容器的了
运行试试,可以看到就是找的子容器创建的bean
这里的bean1就不算单例了其实,因为在父类里也有,在子类里也有,面试中就说 spring中没啥单例,他和设计模式中的单例模式不一样,bean名称可以重复
阶段3:检查 DependsOn
检查 DependsOn初始案例
package com.yyl.bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.GenericApplicationContext;
public class Application3
public static void main(String[] args)
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("bean1",Bean1.class);
context.registerBean("bean2",Bean2.class);
context.registerBean("bean3",Bean3.class);
context.refresh();
static class Bean1
public Bean1()
System.out.println("Bean1");
static class Bean2
public Bean2()
System.out.println("Bean2");
static class Bean3
public Bean3()
System.out.println("Bean3");
static class Bean4
@Autowired
private Bean1 bean1
运行结果如下:
调换一个注册顺序
context.registerBean("bean2",Bean2.class);
context.registerBean("bean1",Bean1.class);
context.registerBean("bean3",Bean3.class);
这里可以看到,虽然这里的Bean4依赖了Bean1,但是Bean注册的执行顺序不能很好的进行控制,并没有一个依赖关系的影响
那么我们想有这种依赖的影响去创建,或者自定义创建的依赖关系去影响注册顺序呢?
这就是DependsOn要做的
咱们改造一下代码,给bean1加上依赖
context.registerBean("bean1",Bean1.class,bd ->
bd.setDependsOn("bean2","bean3");
);
再运行,可以看到(“bean2”,“bean3”)先创建
我们来看一下元阿莫
DependsOn部分的源码在这一块(如下图所示)
首先会去找到所有的依赖bean,找到了还是调用一个getBean,再调用doGetBean
这里就不调试了,对DependsOn方法总结一下吧直接:
DependsOn源码总结
要点 | 总结 |
---|---|
dependsOn 时的 bean 初始化顺序 | dependsOn 用在非显式依赖的 bean 的创建顺序控制 |
@Conditional 的解析时机 | @Conditional 由 ConditionEvaluator 解析看是否满足装配条件 |
beanName 的解析时机 | beanName 的解析分情况组件扫描 - AnnotationBeanNameGenerator@Import - FullyQualifiedAnnotationBeanNameGenerator@Bean - ConfigurationClassBeanDefinitionReader |
@Bean 的解析 | @Bean 相当于工厂方法,所在类相当于工厂 |
@DependsOn,@Lazy,@Primary 的解析时机 | 这些注解由 AnnotationConfigUtils 补充为 BeanDefinition |
@Scope 代理的解析 | Scope 标注的 bean 会为之生成 ScopedProxyFactoryBean 的 BeanDefinition 取代原有,原有 BeanDefinition 成为内嵌定义 |
阶段4:按 Scope 创建 bean
scope 理解为从 xxx 范围内找这个 bean 更加贴切
- singleton scope 表示从单例池范围内获取 bean,如果没有,则创建并放入单例池
- prototype scope 表示从不缓存 bean,每次都创建新的
- request scope 表示从 request 对象范围内获取 bean,如果没有,则创建并放入 request …
创建 singleton
举个栗子
singleton初始案例
代码很简单,我们主要是看看他都干了啥
package com.yyl.bean.scope;
import org.springframework.context.support.GenericApplicationContext;
public class SingletonTest
public static void main(String[] args)
GenericApplicationContext context = new GenericApplicationContext以上是关于SpringBean的生命周期——doGetBean函数探究的主要内容,如果未能解决你的问题,请参考以下文章