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

简介: 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,订阅一波不再迷路

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


目录
相关文章
|
20天前
|
Java 应用服务中间件 Nacos
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
31 0
|
23天前
|
监控 数据可视化 安全
一套成熟的Spring Cloud智慧工地平台源码,自主版权,开箱即用
这是一套基于Spring Cloud的智慧工地管理平台源码,具备自主版权,易于使用。平台运用现代技术如物联网、大数据等改进工地管理,服务包括建设各方,提供人员、车辆、视频监控等七大维度的管理。特色在于可视化管理、智能报警、移动办公和分布计算存储。功能涵盖劳务实名制管理、智能考勤、视频监控AI识别、危大工程监控、环境监测、材料管理和进度管理等,实现工地安全、高效的智慧化管理。
|
1天前
|
监控 Java 应用服务中间件
Spring Boot 源码面试知识点
【5月更文挑战第12天】Spring Boot 是一个强大且广泛使用的框架,旨在简化 Spring 应用程序的开发过程。深入了解 Spring Boot 的源码,有助于开发者更好地使用和定制这个框架。以下是一些关键的知识点:
13 6
|
2天前
|
Java 应用服务中间件 测试技术
深入探索Spring Boot Web应用源码及实战应用
【5月更文挑战第11天】本文将详细解析Spring Boot Web应用的源码架构,并通过一个实际案例,展示如何构建一个基于Spring Boot的Web应用。本文旨在帮助读者更好地理解Spring Boot的内部工作机制,以及如何利用这些机制优化自己的Web应用开发。
12 3
|
4天前
|
Java Spring 容器
深入理解Spring Boot启动流程及其实战应用
【5月更文挑战第9天】本文详细解析了Spring Boot启动流程的概念和关键步骤,并结合实战示例,展示了如何在实际开发中运用这些知识。
13 2
|
5天前
|
存储 前端开发 Java
Spring Boot自动装配的源码学习
【4月更文挑战第8天】Spring Boot自动装配是其核心机制之一,其设计目标是在应用程序启动时,自动配置所需的各种组件,使得应用程序的开发和部署变得更加简单和高效。下面是关于Spring Boot自动装配的源码学习知识点及实战。
13 1
|
6天前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
47 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
|
13天前
|
设计模式 安全 Java
【初学者慎入】Spring源码中的16种设计模式实现
以上是威哥给大家整理了16种常见的设计模式在 Spring 源码中的运用,学习 Spring 源码成为了 Java 程序员的标配,你还知道Spring 中哪些源码中运用了设计模式,欢迎留言与威哥交流。
|
14天前
|
消息中间件 安全 Java
探索|Spring并行初始化加速的思路和实践
作者通过看过的两篇文章发现实现Spring初始化加速的思路和方案有很多类似之处,通过本文记录一下当时的思考和实践。
|
18天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)