Spring 填充属性和初始化流程源码剖析及扩展实现

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: Spring 填充属性和初始化流程源码剖析及扩展实现

前言

在上一篇博文
讲解 Spring 实例化的不同方式及相关生命周期源码剖析
介绍了 Spring 实例化的不同方式,本文主要围绕实例化过后对象的填充属性和初始化过程进行详细流程剖析

回顾前言知识,doCreateBean->createBeanInstance,通过 Supplier 接口、FactoryMethod、构造函数反射 invoke,创建好实例对象

填充属性

前置工作

之前在 DefaultListableBeanFactory#preInstantiateSingletons 方法冻结了所有 BeanDefinition 不可再被修改,为了确保实例化能够正常完成,但是现在已经到了初始化阶段,允许最后一次修改:合并 BeanDefinition 信息.

synchronized (mbd.postProcessingLock) {
    if (!mbd.postProcessed) {
        try {
            // MergedBeanDefinitionPostProcessor 后置处理器修改合并 bean 定义
            applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
        }
        catch (Throwable ex) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                            "Post-processing of merged bean definition failed", ex);
        }
        mbd.postProcessed = true;
    }
}

实现了 MergedBeanDefinitionPostProcessors 接口的 BeanPostProcessor 到指定的 beanDefinition 中,执行 postProcessMergedBeanDefinition 方法

protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) {
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
      if (bp instanceof MergedBeanDefinitionPostProcessor) {
        MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp;
        bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName);
      }
    }
  }

Spring 通过此方法找出所有需要注入的字段,同时做缓存;在这里主要介绍它下面三个实现类:InitDestroyAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、AutowiredAnnotationBeanPostProcessor

从 Java 生成的类图中可以看出,CommonAnnotationBeanPostProcessor 继承至 InitDestroyAnnotationBeanPostProcessor,下面,我们再通过一个结构图来看它们各自负责作什么事情:

  • InitDestroyAnnotationBeanPostProcessor: CommonAnnotationBeanPostProcessor 父类,该类主要处理的是初始化、销毁的逻辑,用于处理 @PostConstruct、@PreDestroy 注解的,在这个阶段只是提前将该阶段的注解元数据扫描出来加入到缓存集合中,在后续调用 postProcessBeforeInitialization 方法时触发标注了对应注解的方法,一般我们使用这两个注解,是为了注入我们业务中想要单独使用的东西而在 Spring 设计范围内无法为我们所提供的时候,比如:在容器启动时先刷新一次我们的业务重试表,对数据进行一次预处理或调用属性时想通过静态的方式进行调用时,就可以在 @PostConstruct 标注的方法内帮我们完成工作.
  • CommonAnnotationBeanPostProcessor:该类用于处理 @Resource 注解,它根据 beanName 进行注入,一般我们标注在要注入的实例属性上;当它标注在方法上进行注入时,若没有参数,标注 @Resource 会抛出异常

@Resource annotation requires a single-arg method

比如:下面这么写,它就会在注入时出现这个错误

public class A {
      @Resource
      public B b() {
          return new B();
      }
}
  • AutowiredAnnotationBeanPostProcessor:该类用于处理 @Autowired 和 @Value 注解,在解析阶段,分别会有一个属性节点内部类:AutowiredFieldElement、方法节点内部类:AutowiredMethodElement 为属性完成赋值工作.

以上三个类处理 postProcessMergedBeanDefinition 方法时,先构建好自动装配的属性、方法元数据存入缓存中,再对其进行检查后更新 BeanDefinition 属性,也就是追加元素> RootBeanDefinition#externallyManagedConfigMembers 集合中

实际干活

一切的前置工作做完以后,接下来就是触发实际干活的工作了

对 Bean 属性进行填充,将各个属性值注入,其中可能存在依赖于其他 bean 属性,则会递归初始化依赖 bean

对以上流程进行如下阐述:

  1. 首先检查 beanWrapper 是否为空,若为空时 BeanDefinition 中还有属性还需要注入,直接抛出异常:Cannot apply property values to null instance;否则继续向下执行
  2. InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation 实例化之后调用的方法:BeanDefinition 未被 AOP 修饰 & 工厂拥有 InstantiationAwareBeanPostProcessor 处理器,获取所有实现 InstantiationAwareBeanPostProcessor 接口实现类,依次调用 postProcessAfterInstantiation 方法,若该方法返回 false,就提前结束 填充属性 工作,若返回 true 则会继续处理后面的工作,在此方法中可以自定义需要设置的属性
  3. AutowireMode:BeanDefinition 默认的自动装配模式是 no,在配置 Bean 标签时可以通过设置 autowire 属性指定是 byType 或 byName;无论指定哪一种,首先要进行一层过滤筛选出合适的属性集合:AbstractAutowireCapableBeanFactory#unsatisfiedNonSimpleProperties,然后再去获取实例;byType 通过属性 setter 方法封装成依赖描述符去进行一层层解析->doResolveDependency,这里面处理的细节工作很多;byName 处理工作比较简单,只是通过属性名调用 getBean 方法,每当处理完一个属性,都会记录一条当前属性 Bean 与外部 Bean 之间的依赖关系

过滤条件分为以下四点:

1、bw 有写入属性的方法(setter)

2、类型属于非简单类型:基本数据类型(包含自身、值类型)如 Map、数组,这些类型都不满足

3、BeanDefinition 中的 PropertyValues 没有该 PropertyDescriptor 属性,意味着还未解析过

4、PropertyDescriptor 不是被排除在依赖项检查之外的

注意的一点:如果是 Map<String,Object> 这种类型,指定 by-type 进行自动注入的话,会将 Spring 下是此类型的 bean 都注入,其中包含了 systemProperties、systemEnvironment …

在注解开发中,@Autowire 通过类型注入、@Resource 通过名称注入,注解相关的处理工作是在第四点 InstantiationAwareBeanPostProcessor#postProcessProperties 进行处理的,从这里对比来说,@Resource 会比 @Autowired 注入速度更快,因为通过类型注入会先进行推断流程,找到一个最合适的构造器进行注入以及类型对比,而通过名称注入直接调用 getBean 方法 去获取实例.

  1. InstantiationAwareBeanPostProcessor#postProcessProperties 属性注入的方法 :获取所有实现 InstantiationAwareBeanPostProcessor 接口的实现类,依次调用 postProcessProperties 方法,标注了 @Resource、@Value、@Autowired 标注的属性、方法进行注解元数据处理,在前置工作处理时,已经将这些注解都进行了解析,提前存入了元数据缓存中;在此处,将元数据缓存取出,然后进行属性注入
  2. applyPropertyValues:应用给定的属性值,解决任何在这个 Bean 工厂运行时其他 Bean 引用。必须使用深拷贝,因为我们永久不会修改这个属性;遍历所有属性,获取到 name、value 属性,调用 resolveValueIfNecessary 方法解析 value 获取具体值,再判别该值是否要进行转换,需要的话会调用具体的转换器进行转换后赋值,添加到深拷贝的属性集合中,处理完所有的属性以后,再把深拷贝的属性集合设置到 BeanWrapper 实例中!

总结

在使用 Spring 时,有可能既配置了注解,又在配置文件中设置了属性,在执行 postProcessProperties 方法时,会将注解相关的属性进行注入,配置文件中设置的属性交由 applyPropertyValues 方法进行处理,两者相互独立开来,互相不影响,但是注解和 properties 标签同时配置了相同的属性,会以配置文件>标签配置的优先.

初始化 Bean

目前创建实例、填充属性阶段都已解析完成,只差初始化 Bean 了;在实例化阶段,所有属性值都是赋予的默认值,比如:Object->null、String->null、int->0、Integer->null 等等

源码部分

以下是 initializeBean 初始化方法源码,初始化给定的 Bean 实例,应用工厂回调以及 init 方法、BeanPostProcessors

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
    // 如果安全管理器不为空
    if (System.getSecurityManager() != null) {
        // 以特权的方式执行回调bean中 Aware 接口方法
        AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
            invokeAwareMethods(beanName, bean);
            return null;
        }, getAccessControlContext());
    }
    else {
        // Aware 接口处理器,调用 BeanNameAware、BeanClassLoaderAware、beanFactoryAware
        invokeAwareMethods(beanName, bean);
    }
    Object wrappedBean = bean;
    //如果mdb不为null || mbd不是"synthetic"。一般是指只有AOP相关的 pointCut 配置或者 Advice 配置才会将 synthetic 设置为true
    if (mbd == null || !mbd.isSynthetic()) {
        // 将BeanPostProcessors应用到给定的现有Bean实例,调用它们的postProcessBeforeInitialization初始化方法。
        // 返回的Bean实例可能是原始Bean包装器
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }
    try {
        //调用初始化方法,先调用bean的InitializingBean接口方法,后调用bean的自定义初始化方法
        invokeInitMethods(beanName, wrappedBean, mbd);
    }
    catch (Throwable ex) {
        //捕捉调用初始化方法时抛出的异常,重新抛出Bean创建异常:调用初始化方法失败
        throw new BeanCreationException(
            (mbd != null ? mbd.getResourceDescription() : null),
            beanName, "Invocation of init method failed", ex);
    }
    //如果mbd为null || mbd不是"synthetic"
    if (mbd == null || !mbd.isSynthetic()) {
        // 将BeanPostProcessors应用到给定的现有Bean实例,调用它们的postProcessAfterInitialization方法。
        // 返回的Bean实例可能是原始Bean包装器
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }
    //返回包装后的Bean
    return wrappedBean;
}
  1. invokeAwareMethods:会执行 BeanNameAware#setBeanName、BeanClassLoaderAware#setBeanClassLoader、BeanFactoryAware#setBeanFactory 这三个 Aware 接口的方法;在 AbstractApplicationContext#refresh 方法继而会调用 obtainFreshBeanFactory 方法中会实例化 AbstractAutowireCapableBeanFactory 对象时会忽略这三个借口,主要是为了在注入属性时,不会对这三个接口进行依赖注入,这三个接口是属于 依赖项检查时 需要先排除的,如下图所示:
  2. applyBeanPostProcessorsBeforeInitialization:初始化方法调用前需要处理的逻辑,一般 BeanPostProcessor 实现类没有作任何工作,直接返回原有的 Bean 实例,只有 ApplicationContextAwareProcessor、InitDestroyAnnotationBeanPostProcessor 实现类才需要进行特殊处理

1、ApplicationContextAwareProcessor:只要有实现这些 Aware 接口(EnvironmentAware、EmbeddedValueResolverAware、ResourceLoaderAware、ApplicationEventPublisherAware、 MessageSourceAware、ApplicationContextAware) Bean 对象,才需要执行对应的 Aware 方法,在 AbstractApplicationContext#prepareBeanFactory 方法会忽略这些 Aware 接口注入,其目的是为了不影响依赖注入的工作,属于 依赖项检查时 需要先排除的,如下图所示 ⬇️

2、InitDestroyAnnotationBeanPostProcessor:在上面填充属性的前置工作时,已经将 @PostConstruct、@PreDestroy 注解元数据提前进行了解析并存入了元数据缓存中,在这里就是将它们取出来,并对其 @PostConstruct 实现进行反射调用执行

  1. invokeInitMethods:调用初始化方法,先调用 Bean 实现 InitializingBean 接口的 afterPropertiesSet 方法,后调用 Bean 自定义初始化的方法>通过 init-method 标签属性或 @Bean 注解的 initMethod 属性来指定调用哪个方法
  2. applyBeanPostProcessorsAfterInitialization:初始化方法之后要调用的方法 postProcessAfterInitialization,一般初始化方法之后调用的都是创建代理对象,实现类>AbstractAutoProxyCreator,此处在博主的 AOP 文章有进行详细讲解:Spring AOP 执行流程及源码分析;但它在此处并不一定是会进行代理实现的,若发现此 Bean 实例发生了循环依赖问题,那么它后续才会通过 AbstractAutoProxyCreator#getEarlyBeanReference 方法创建好一个完整的代理对象,Spring 循环依赖问题博主也有详细进行介绍:Spring 循环依赖问题解决方案以及简要源码流程剖析

扩展部分

假定通过一个学生类,来自定义实现 InitializingBean 接口,调用初始化方法

public class Student implements InitializingBean {
   private Integer age;
   private String name;
    public Student() {
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        this.setName("vnjohn");
    }
}

那么它会在初始化 Bean>执行 invokeInitMethods 方法过程完成 name 属性的赋值,当然不仅这一种实现方式,还可以通过配置 <bean> 标签 init-method 属性@Bean 注解 initMethod 属性

在 Spring 非注解开发时,init-method 相当于 @PostConstruct 注解、destroy-method 相当于 @PreDestroy 注解,但是它们的执行顺序不同,注解执行在前(在调用初始化之前的方法执行的)标签属性执行在后(在执行 invokeInitMethods 方法时执行的)

避坑:在日常开发中,若你对 Spring 这套生命周期体系不熟悉的话,可能谁先执行谁后执行,你都不清楚,当你在一万个为什么的时候,看一下这个你可能就不会再犯这种错误了,因为执行的顺序你可能会以为是 Bug~

执行顺序->说明工作中常用的:

1、实现了 BeanNameAware、BeanFactoryAware 接口的 Bean 执行

2、实现了 ApplicationContextAware 接口的 Bean 执行,执行标注了 @PostConstruct 注解的方法

3、实现了 InitializingBean 接口的 Bean,执行 afterPropertiesSet 方法

4、会执行 DestructionAwareBeanPostProcessors#postProcessBeforeDestruction 方法来完成销毁前的调用

5、实现了 DisposableBean 接口的 Bean,执行 destroy 方法

第 4、5 点,Bean 实例是到此方法阶段:AbstractApplicationContext#refresh 才去进行销毁的,一般我们会在此处作 Redis 缓存的清理工作或 Kafka 监听器的取消订阅。

总结

博主在此篇文讲解完了生命周期剩下的部分:填充属性、初始化 Bean,前期是填充属性阶段需要作的准备工作,提前解析好 @Resource、@Autowired、@PostConstruct 注解的元数据,并对 BeanDefinition 作了最后一次的合并属性工作;在填充属性实际干活时用到了 @Resource、@Autowired 元数据信息,简单对比了它们之间的注入复杂性;最后部分讲解了初始化 Bean 详细的过程,主要分析的是它们的执行顺序,防止在实际工作中因为各种扩展实现造成混浊,说明它们处理这些工作时来自于哪里

最后,总结一下 Spring 正常创建 Bean 整个生命周期过程

  1. 实例化 bean 对象:getBean->doGetBean->createBean->doCreateBean->createBeanInstance
  2. 填充对象属性
  3. 检查 Aware 相关接口并设置相关依赖
  4. BeanPostProcessors 初始化前置处理方法 postProcessBeforeInitialization
  5. 检查是否有实现 InitializingBean 以决定是否调用 afterPropertiesSet 方法
  6. 检查是否配置有自定义的 init-method,调用执行
  7. BeanPostProcessors 初始化后置处理方法 postProcessAfterInitialization
  8. 实现 DestructionAwareBeanPostProcessor 接口的类,销毁前调用方法 postProcessBeforeDestruction
  9. 检查是否有实现 DisposableBean 以决定是否调用 destroy 方法
  10. 检查是否配置有自定义的 destroy-method,调用执行

如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!

推荐专栏:Spring,订阅一波不再迷路

大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!


目录
相关文章
|
29天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
67 2
|
1月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
13天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
30 2
|
1月前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
1月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
59 9
|
2月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
142 5
|
2月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
Java Spring 容器
Spring 源码解析 | populateBean 属性填充(上)
本文主要是讲 Spring 容器的启动过程主要是以题干的方式进行可能不回涉及过多的细节,希望大家阅读本文能对 Spring 源码有一个简单的了解。 环境介绍: Spring 5.3.10 Jdk 11
227 0
Spring 源码解析 | populateBean 属性填充(上)
|
Java Spring 容器
Spring 源码解析 | populateBean 属性填充(下)
本文主要是讲 Spring 容器的启动过程主要是以题干的方式进行可能不回涉及过多的细节,希望大家阅读本文能对 Spring 源码有一个简单的了解。 环境介绍: Spring 5.3.10 Jdk 11
181 0
|
Java 数据库连接 Spring
Spring 属性填充(下)
Spring 的属性填充主要是在 Bean 被创建后,通过 populateBean 方法来完成对象属性赋值以逐步完成 Bean 的初始化工作。
204 0