Spring 源码学习(四) bean 的加载(中)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 既然我们 Spring 辛辛苦苦将 bean 进行了注册,当然需要拿出来进行使用,在使用之前还需要经过一个步骤,就是 bean 的加载。在第一篇笔记提到了,完成 bean 注册到 beanDefinitionMap 注册表后,还调用了很多后处理器的方法,其中有一个方法 finishBeanFactoryInitialization(),注释上面写着 Instantiateall remaining(non-lazy-init)singletons,意味着非延迟加载的类,将在这一步实例化,完成类的加载。而我们使用到 context.getBean("beanName")方法,如果对应的

对于第四个步骤,委托给 getObjectFromFactoryBean 方法进行处理不深入分析,但里面有三个方法值得一说:

  1. // 单例操作,前置操作
  2. beforeSingletonCreation(beanName);
  3. try{
  4.    object = postProcessObjectFromFactoryBean(object, beanName);
  5. }
  6. catch(Throwable ex){
  7.    thrownewBeanCreationException(beanName,
  8.            "Post-processing of FactoryBean's singleton object failed", ex);
  9. }
  10. finally{
  11.    // 单例模式,后置操作
  12.    afterSingletonCreation(beanName);
  13. }

代码中在类的加载时,有前置操作和后置操作,之前在第一篇笔记看过,很多前置和后置操作都是空方法,等用户自定义扩展用的。

但在这里的不是空方法,在两个方法是用来保存和移除类加载的状态,是用来对循环依赖进行检测的。

同时,这两个方法在不同 scope 加载 bean 时也有使用到,也是个高频方法。

  1. try{
  2.    object= postProcessObjectFromFactoryBean(object, beanName);
  3. }
  4. catch(Throwable ex){
  5.    thrownewBeanCreationException(beanName,"Post-processing of FactoryBean's object failed", ex);
  6. }

这是一个执行后处理的方法,我接触的不多,先记下概念:

Spring 获取 bean 的规则中有一条:尽可能保证所有 bean 初始化后都会调用注册的 BeanPostProcessor 的 postProcessAfterInitialization 方法进行处理。在实际开发中,可以针对这个特性进行扩展。


获取单例

现在来到时序图中的 1.3 步骤:

  1. // Create bean instance. 创建 bean 实例
  2. // singleton 单例模式(最常使用)
  3. if(mbd.isSingleton()){
  4.    // 第二个参数的回调接口,接口是 org.springframework.beans.factory.ObjectFactory#getObject
  5.    // 接口实现的方法是 createBean(beanName, mbd, args)
  6.    sharedInstance = getSingleton(beanName,()->{
  7.        return createBean(beanName, mbd, args);
  8.        // 省略了 try / catch
  9.    });
  10.    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
  11. }

来看 getSingleton 方法做了什么:

  1. publicObject getSingleton(String beanName,ObjectFactory<?> singletonFactory){
  2.    Assert.notNull(beanName,"Bean name must not be null");
  3.    // 注释 4.7 全局变量,加锁
  4.    synchronized(this.singletonObjects){
  5.        // 检查是否已经被加载了,单例模式就是可以复用已经创建的 bean
  6.        Object singletonObject =this.singletonObjects.get(beanName);
  7.        if(singletonObject ==null){
  8.            // 初始化前操作,校验是否 beanName 是否有别的线程在初始化,并加入初始化状态中
  9.            beforeSingletonCreation(beanName);
  10.            boolean newSingleton =false;
  11.            boolean recordSuppressedExceptions =(this.suppressedExceptions ==null);
  12.            if(recordSuppressedExceptions){
  13.                this.suppressedExceptions =newLinkedHashSet<>();
  14.            }
  15.            // 初始化 bean,这个就是刚才的回调接口调用的方法,实际执行的是 createBean 方法
  16.            singletonObject = singletonFactory.getObject();
  17.            newSingleton =true;
  18.            if(recordSuppressedExceptions){
  19.                this.suppressedExceptions =null;
  20.            }
  21.            // 初始化后的操作,移除初始化状态
  22.            afterSingletonCreation(beanName);
  23.            if(newSingleton){
  24.                // 加入缓存
  25.                addSingleton(beanName, singletonObject);
  26.            }
  27.        }
  28.        return singletonObject;
  29.    }
  30. }

来梳理一下流程:

  • 检查缓存是否已经加载过
  • 没有加载,记录 beanName 的加载状态
  • 调用回调接口,实例化 bean
  • 加载单例后的处理方法调用:这一步就是移除加载状态
  • 将结果记录到缓存并删除加载 bean 过程中所记录到的各种辅助状态

对于第二步和第四步,在前面已经提到,用来记录 bean 的加载状态,是用来对 循环依赖 进行检测的,这里先略过不说。

关键的方法在于第三步,调用了 ObjectFactorygetObject() 方法,实际回调接口实现的是 createBean() 方法,需要往下了解,探秘 createBean()


准备创建 bean

对于书中,有句话说的很到位:

Spring 源码中,一个真正干活的函数其实是以 do 开头的,比如 doGetBeandoGEtObjectFromFactoryBean,而入口函数,比如 getObjectFromFactoryBean,其实是从全局角度去做统筹工作

有了这个概念后,看之后的 Spring 源码,都知道这个套路,在入口函数了解整体流程,然后重点关注 do开头的干活方法。

按照这种套路,我们来看这个入口方法 createBean()

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])

  1. protectedObject createBean(String beanName,RootBeanDefinition mbd,@NullableObject[] args){
  2.    RootBeanDefinition mbdToUse = mbd;
  3.    // 有道翻译:确保此时bean类已经被解析,并且克隆 bean 定义,以防动态解析的类不能存储在共享合并 bean 定义中。
  4.    // 锁定 class,根据设置的 class 属性或者根据 className 来解析 Class
  5.    Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
  6.    if(resolvedClass !=null&&!mbd.hasBeanClass()&& mbd.getBeanClassName()!=null){
  7.        mbdToUse =newRootBeanDefinition(mbd);
  8.        mbdToUse.setBeanClass(resolvedClass);
  9.    }
  10.    // Prepare method overrides.
  11.    // 验证及准备覆盖的方法
  12.    mbdToUse.prepareMethodOverrides();
  13.    // 让 beanPostProcessor 有机会返回代理而不是目标bean实例。
  14.    Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
  15.    if(bean !=null){
  16.        // 短路操作,如果代理成功创建 bean 后,直接返回
  17.        return bean;
  18.    }

  19.    // 创建 bean
  20.    Object beanInstance = doCreateBean(beanName, mbdToUse, args);
  21.    return beanInstance;
  22. }

先来总结这个流程:

  • 根据设置的 class 属性或者根据 className 来解析 Class
  • 验证及准备覆盖的方法 这个方法是用来处理以下两个配置的:我们在解析默认标签时,会识别 lookup-methodreplaced-method 属性,然后这两个配置的加载将会统一存放在 beanDefinition 中的 methodOverrides 属性里。
  • 应用初始化前的后处理器,解析指定 bean 是否存在初始化前的短路操作
  • 创建 bean

下面来讲下这几个主要步骤


处理 Override 属性

  1. publicvoid prepareMethodOverrides()throwsBeanDefinitionValidationException{
  2.    // Check that lookup methods exists.
  3.    if(hasMethodOverrides()){
  4.        Set<MethodOverride> overrides = getMethodOverrides().getOverrides();
  5.        synchronized(overrides){
  6.            for(MethodOverride mo : overrides){
  7.                // 处理 override 属性
  8.                prepareMethodOverride(mo);
  9.            }
  10.        }
  11.    }
  12. }

可以看到,获取类的重载方法列表,然后遍历,一个一个进行处理。具体处理的是 lookup-methodreplaced-method 属性,这个步骤解析的配置将会存入 beanDefinition 中的 methodOverrides 属性里,是为了待会实例化做准备。

如果 bean 在实例化时,监测到 methodOverrides 属性,会动态地位当前 bean 生成代理,使用对应的拦截器为 bean 做增强处理。

(我是不推荐在业务代码中使用这种方式,定位问题和调用都太麻烦,一不小心就会弄错=-=)


实例化前的前置处理

  1. // 让 beanPostProcessor 有机会返回代理而不是目标bean实例。
  2. Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
  3. if(bean !=null){
  4.    // 短路操作,如果代理成功创建 bean 后,直接返回
  5.    return bean;
  6. }

  7. protectedObject resolveBeforeInstantiation(String beanName,RootBeanDefinition mbd){
  8.    Object bean =null;
  9.    if(!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)){
  10.        // Make sure bean class is actually resolved at this point.
  11.        if(!mbd.isSynthetic()&& hasInstantiationAwareBeanPostProcessors()){
  12.            Class<?> targetType = determineTargetType(beanName, mbd);
  13.            if(targetType !=null){
  14.                // 执行前置拦截器的操作
  15.                bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
  16.                if(bean !=null){
  17.                    // 执行后置拦截器的操作
  18.                    bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
  19.                }
  20.            }
  21.        }
  22.        mbd.beforeInstantiationResolved =(bean !=null);
  23.    }
  24.    return bean;
  25. }

doCreateBean 方法前,有一个短路操作,如果后处理器成功,将会返回代理的 bean

resolveBeforeInstantiation 方法中,在确保 bean 信息已经被解析完成,执行了两个关键方法,从注释中看到,一个是前置拦截器的操作,另一个就是后置拦截器的操作。

如果第一个前置拦截器实例化成功,就已经将单例 bean 放入缓存中,它不会再经历普通 bean 的创建过程,没有机会进行后处理器的调用,所以在这里的第二个步骤,就是为了这个 bean 也能应用后处理器的 postProcessAfterInitialization 方法。

创建 bean

终于到了关键的干活方法:doGetBean。在通过上一个方法校验,没有特定的前置处理,所以它是一个普通 bean, 常规 bean 进行创建在 doGetBean 方法中完成。

  1. protectedObject doCreateBean(finalString beanName,finalRootBeanDefinition mbd,final@NullableObject[] args){
  2.    // Instantiate the bean.
  3.    BeanWrapper instanceWrapper =null;
  4.    if(mbd.isSingleton()){
  5.        instanceWrapper =this.factoryBeanInstanceCache.remove(beanName);
  6.    }
  7.    if(instanceWrapper ==null){
  8.        // 注释 4.8 根据指定 bean 使用对应的策略创建新的实例 例如跟进方法去看,有工厂方法,构造函数自动注入,简单初始化
  9.        instanceWrapper = createBeanInstance(beanName, mbd, args);
  10.    }
  11.    finalObject bean = instanceWrapper.getWrappedInstance();
  12.    Class<?> beanType = instanceWrapper.getWrappedClass();
  13.    if(beanType !=NullBean.class){
  14.        mbd.resolvedTargetType = beanType;
  15.    }
  16.    // 允许后处理程序修改合并的bean定义
  17.    synchronized(mbd.postProcessingLock){
  18.        if(!mbd.postProcessed){
  19.            applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
  20.            mbd.postProcessed =true;
  21.        }
  22.    }
  23.    // 是否需要提前曝光,用来解决循环依赖时使用
  24.    boolean earlySingletonExposure =(mbd.isSingleton()&&this.allowCircularReferences &&
  25.            isSingletonCurrentlyInCreation(beanName));
  26.    if(earlySingletonExposure){
  27.        // 第二个参数是回调接口,实现的功能是将切面动态织入 bean
  28.        addSingletonFactory(beanName,()-> getEarlyBeanReference(beanName, mbd, bean));
  29.    }
  30.    Object exposedObject = bean;
  31.    // 对 bean 进行填充,将各个属性值注入
  32.    // 如果存在对其它 bean 的依赖,将会递归初始化依赖的 bean
  33.    populateBean(beanName, mbd, instanceWrapper);
  34.    // 调用初始化方法,例如 init-method
  35.    exposedObject = initializeBean(beanName, exposedObject, mbd);

  36.    if(earlySingletonExposure){
  37.        Object earlySingletonReference = getSingleton(beanName,false);
  38.        // earlySingletonReference 只有在检测到有循环依赖的情况下才 不为空
  39.        if(earlySingletonReference !=null){
  40.            if(exposedObject == bean){
  41.                // 如果 exposedObject 没有在初始化方法中被改变,也就是没有被增强
  42.                exposedObject = earlySingletonReference;
  43.            }
  44.            elseif(!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)){
  45.                String[] dependentBeans = getDependentBeans(beanName);
  46.                Set<String> actualDependentBeans =newLinkedHashSet<>(dependentBeans.length);
  47.                for(String dependentBean : dependentBeans){
  48.                    // 检查依赖
  49.                    if(!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)){
  50.                        actualDependentBeans.add(dependentBean);
  51.                    }
  52.                }
  53.                // bean 创建后,它所依赖的 bean 一定是已经创建了
  54.                // 在上面已经找到它有依赖的 bean,如果 actualDependentBeans 不为空
  55.                // 表示还有依赖的 bean 没有初始化完成,也就是存在循环依赖
  56.                if(!actualDependentBeans.isEmpty()){
  57.                    thrownewBeanCurrentlyInCreationException(beanName);
  58.            }
  59.        }
  60.    }
  61.    // Register bean as disposable.
  62.    // 根据 scope 注册 bean
  63.    registerDisposableBeanIfNecessary(beanName, bean, mbd);
  64.    return exposedObject;
  65. }

看到这么长的代码,感觉有点头晕,所以先来梳理这个方法的流程:

  1. 如果加载的 bean 是单例,要清除缓存
  2. 实例化 bean,将 BeanDifinition 转化成 BeanWrapper
  3. 后处理器修改合并后的 bean 定义:bean 合并后的处理,Autowired 注解正式通过此方法实现诸如类型的预解析
  4. 依赖处理
  5. 属性填充:将所有属性填充到 bean 的实例中
  6. 循环依赖检查
  7. 注册 DisposableBean:这一步是用来处理 destroy-method 属性,在这一步注册,以便在销毁对象时调用。
  8. 完成创建并返回

从上面流程可以看出,这个方法做了很多事情,以至于代码超过了 100 多行,给人的阅读体验差,所以尽量还是拆分小方法,在入口方法尽量简洁,说明做的事情,具体在小方法中完成。

因为这个创建过程的代码很多和复杂,我挑重点来理解和学习,详细的还有待深入学习


相关文章
|
24天前
|
XML 安全 Java
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
76 2
|
1天前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
1天前
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
|
7天前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
36 6
|
8天前
|
XML Java 数据格式
🌱 深入Spring的心脏:Bean配置的艺术与实践 🌟
本文深入探讨了Spring框架中Bean配置的奥秘,从基本概念到XML配置文件的使用,再到静态工厂方式实例化Bean的详细步骤,通过实际代码示例帮助读者更好地理解和应用Spring的Bean配置。希望对你的Spring开发之旅有所助益。
55 3
|
22天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
41 2
|
1月前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
22天前
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
32 1
|
1月前
|
监控 IDE Java
如何在无需重新启动服务器的情况下在 Spring Boot 上重新加载我的更改?
如何在无需重新启动服务器的情况下在 Spring Boot 上重新加载我的更改?
57 8