SpringBean的生命周期——doGetBean函数探究

Posted 杨 戬

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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函数探究的主要内容,如果未能解决你的问题,请参考以下文章

springbean的生命周期

SpringBean生命周期

Spring Bean 生命周期

Spring Bean 生命周期

Spring之SpringBean的生命周期详解

springBean生命周期