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

这篇具有很好参考价值的文章主要介绍了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

Spring 填充属性和初始化流程源码剖析及扩展实现
从 Java 生成的类图中可以看出,CommonAnnotationBeanPostProcessor 继承至 InitDestroyAnnotationBeanPostProcessor,下面,我们再通过一个结构图来看它们各自负责作什么事情:

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

  • 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

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

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

  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 填充属性和初始化流程源码剖析及扩展实现
在使用 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 对象时会忽略这三个借口,主要是为了在注入属性时,不会对这三个接口进行依赖注入,这三个接口是属于 依赖项检查时 需要先排除的,如下图所示:
    Spring 填充属性和初始化流程源码剖析及扩展实现
  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 实现进行反射调用执行

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

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

大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!文章来源地址https://www.toymoban.com/news/detail-458720.html

到了这里,关于Spring 填充属性和初始化流程源码剖析及扩展实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Spring Boot 源码学习】ConditionEvaluationReport 日志记录上下文初始化器

    《Spring Boot 源码学习系列》 上篇博文《共享 MetadataReaderFactory 上下文初始化器》, Huazie 带大家详细分析了 SharedMetadataReaderFactoryContextInitializer 。而在 spring-boot-autoconfigure 子模块中预置的上下文初始化器中,除了共享 MetadataReaderFactory 上下文初始化器,还有一个尚未分析。 那么

    2024年04月13日
    浏览(43)
  • C++之初始化列表详细剖析

    初始化列表:以一个 冒号开始 ,接着是一个以 逗号分隔的数据成员列表 ,每个 \\\"成员变量\\\" 后面跟一个 放在括号中的初始值或表达式。 不知道大家有没有想过这样一个问题,成员函数明明可以在函数内部对成员变量进行赋值,那为什么还要搞出初始化列表这个东西呢?这个

    2024年02月06日
    浏览(57)
  • Kotlin 环境下解决属性初始化问题

    🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页 ——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文并茂🦖生动形象🐅简单易学!欢迎大家来踩踩~🌺 🌊 《IDEA开发秘籍专栏》 🐾 学会IDEA常用操作,工作效率翻倍~💐 🌊 《100天精通Golang(基础

    2024年02月09日
    浏览(37)
  • android 添加ro属性字段并初始化

    硬件平台:QCS6125 软件平台:Android11 需求:硬件需通过硬件电路区分为多款型号,需要初始化到相应的系统属性字段展示。   这种型号属性适合做成ro类型,类似于原生系统的ro.product.model,由于android层面拿到这个具体的型号值是内核通过传递cmdline而获取的,内核层面拿到硬

    2024年01月16日
    浏览(84)
  • 消失的死锁:从 JSF 线程池满到 JVM 初始化原理剖析

    在一次上线时,按照正常流程上线后,观察了线上报文、接口可用率十分钟以上,未出现异常情况,结果在上线一小时后突然收到jsf线程池耗尽的报警,并且该应用一共有30台机器,只有一台机器出现该问题,迅速下线该机器的jsf接口,恢复线上。然后开始排查问题。 [WARN]

    2024年02月08日
    浏览(38)
  • SIM初始化流程

    ATR(Answer To Reset):复位应答信号,有SIM卡传输给终端,包括SIM卡自身的一些信息,比如支持的传输速率,传输模式等。   SIM卡的ATR代表\\\"Answer to Reset\\\",即复位响应。当SIM卡被插入设备中时,设备会向SIM卡发送一个复位命令,以获取SIM卡的响应。 SIM卡会回复一个ATR,其中包含有

    2024年02月04日
    浏览(49)
  • TabView 初始化与自定义 TabBar 属性相关

    SWift TabView 与 UIKit 中的 UITabBarController 如出一辙.在 TabView 组件中配置对应的图片和标题; 其中,Tag 用来设置不同 TabView 可动态设置当前可见 Tab;另也有一些常用的属性与 UIKit 中的类似,具体可以按需参考 api 中属性进行单独修改定制; 在 iOS 15.0 之后还可设置角标记 .badge 对 TabBa

    2024年02月10日
    浏览(39)
  • Android 自定义view 中增加属性,初始化时读取

    因为自定义View 有正向和反向两个状态,所以需要在初始化时区分加载哪个layout 在Android中,要在自定义View中增加属性,你需要完成以下步骤: 在res/values/attrs.xml文件中定义属性。 在自定义View的构造函数中获取这些属性。 在布局文件中使用这些属性。 attrs.xml: 自定义VIEW 中

    2024年04月25日
    浏览(43)
  • Spring初始化项目

    访问地址:https://start.spring.io idea配置:https://start.spring.io 访问地址:https://start.aliyun.com/bootstrap.html idea配置:https://start.aliyun.com 官网 阿里巴巴 版本 最新 稍旧 国内软件 大部分没有(mybatis plus) 有的支持(如:mybatis plus)

    2024年02月09日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包