Spring Bean到底是如何创建的?(上)

简介: Spring Bean核心创建流程分析

大家好,我是三友~~

前言

众所周知,spring对于java程序员来说是一个及其重要的后端框架,几乎所有的公司都会使用的框架,而且深受广大面试官的青睐。所以本文就以常见的一个面试题"spring bean的生命周期"为切入点,从源码的角度带领大家来看一看 spring bean到底是如何创建的 。spring bean的生命周期非常重要 ,因为几乎所有的跟spring整合的框架,比如说mybatis 、dubbo 等框架基本上都是通过bean的生命周期来实现跟spring的整合。后面我也会单独写文章,剖析mybatis源码以及是怎么跟spring整合,dubbo我也可能会出一些文章,剖析dubbo3.0的源码。如果有可能的话,spring cloud 源码我也会讲解的,当然这都是以后的打算了。

本文是基于spring源码的5.1版本

在讨论spring创建bean的源码之前,我先大概介绍一下spring的ioc和aop的概念。

公众号:三友的java日记

ioc

(Inversion of Control,缩写为IoC)就是控制翻转的意思,简单来说就是你按照spring提供的配置bean的方式将bean的创建流程交给spring来完成,比如以xml的方式声明bean,以@Bean的注解声明bean,以@Componet注解方式声明bean,当你用这些方式来声明bean的时候,spring在启动的时候就知道要为这个类创建一个对象,接下来spring会按照自己的流程来一步一步完成bean的生成过程,也就是本文的主题,spring bean的创建流程。

aop

aop(Aspect Oriented Programming)英文意思就是面向切面编程,说白了其实就是一个动态代理的过程,只不过spring将生成动态代理的过程给封装到框架的内部,开发者其实只需要声明一下对哪个对象的哪个方法进行代理(pointcut)和在被代理的方法该执行什么样的代码(advice),这样就实现了aop。

有时大家可能会很好奇怎么实现动态代理的过程,怎么我配置了一下切面,就给我代理了呢?我给大家简单解释一下,aop的实现离不开ioc,当在spring bean创建的过程,在某个环节(后面会说到)spring框架会去判断,你有没有配置过给创建的对象进行代理,怎么判断很简单,就是根据你配置的切点表达式判断一下,如果有就给你创建一个代理对象返回给你,这样你拿到的就是代理对象,接下来你对这个对象方法调用就会走你写的那个advice所对应的代码,如果判断没有,就会返回给你原来的对象,就这么简单。

大家不妨去了解一下静态代理,这会有助于大家了解动态代理,动态代理其实跟静态代理差不多,只不过静态代理需要你手动写对象的代理,属于硬编码的方式,有多少个类就得写多少个类的代理类,很麻烦,而动态代理是动态生成代理类,但是本质都是加一些特殊的功能,这里就不再过多赘述了。

好了说完了spring ioc和aop的基本概念之后,接下来就来进入spring ioc中的bean的生命周期源码分析。

Spring Bean 元信息(BeanDefinition)读取阶段

spring容器刚启动的时候,spring会按照你的声明bean的方式(以xml的方式声明bean,以@Bean的注解声明bean,以@Componet注解方式声明bean等(其实也可以通过Properties资源配置))去读取你声明的信息,然后封装在一个叫BeanDefinition对象里面,BeanDefinition可以看成你配置信息的一个封装对象,就跟你平时new的User是一个概念,后面在创建对象的时候,spring拿出来BeanDefinition,基于你配置的信息来创建对象。

Spring Bean 注册阶段

Spring Bean 元信息读取阶段结束后会为每一个bean生成一个BeanDefinition,你配置了那么多的bean就有那么多BeanDefinition吧,怎么都得有一个地方存吧,可以很好想到的,java中数据结构还是很多的,比如说list,map等,所以spring选择通过map来存,只不过spring考虑的比较完善,封装了一个类,叫BeanDefinitionRegistry,直接实现就是DefaultListableBeanFactory这个类(这个类是spring ioc核心类,是BeanFactory基本上算是唯一的实现,非常非常的重要),通过registerBeanDefinition方法进行注册

image.png

在此为止,你通过xml或者各种方式声明的bean已经注册到ioc容器中了,所谓的注册,就是表明,这些对象需要spring来帮你创建,虽然你只配置了一下,但是spring得去做很多的事来读取配置。所以接下来就是通过spring 来获取到你注册bean,就会进入spring bean的创建阶段

bean的获取阶段

为什么先讲获取。因为spring源码中是先从ioc容器中获取对象,获取不到再创建的。所以这里先从DefaultListableBeanFactory的doGetBean方法入手,它会委派给它的父类来处理

image.png

image.png

先根据getSingleton方法去获取对象 ,这里就牵扯出三级缓存解决循环依赖的问题。

spring是如何解决循环依赖的?

三级缓存

  • 第一级缓存 singletonObjects ,成熟的,彻彻底底可用的实例
  • 第二级缓存 singletonFactories ,这getObject的时候会去对bean创建一个代理对象
  • 第三级缓存 earlySingletonObjects ,这里是存储早期对象

假设A 和 B 产生循环依赖了。假设A正在创建,他在创建的实话会去往第一级缓存注册一个 ObjectFactory , 然后在属性赋值(后面会说的)阶段注入了B , 此时B 要按照Bean创建的流程进行创建,跟A一样注册一个 OjbectFactory(B的注册不重要,我是为了让大家清楚跟A创建流程一样),当B在属性赋值阶段的时候要注入A,此时会发现,A正在创建,于是乎他就会从 singletonFactories 拿出A的ObjectFactory ,调用getObject方法,获取到A(此时的A仅仅创建出来,处于早期的状态,不是一个完整的Bean,但是可能被代理了),然后将获取到的A放入到earlySingletonObjects ,然后把早期的A的bean注入B,然后B继续执行。B执行完之后,注入给A,然后A继续执行,执行到最后A完完全全创建好之后。会把singletonFactories 和earlySingletonObjects 缓存清空,在singletonObjects 中放入自己成熟的bean,这样A B 就都创建完成了,也就解决了循环依赖的问题。

如果出现循环依赖的问题,这里就会获取到bean,只不过这个bean还没有被初始化,仅仅只是实例化出来的而已,如果需要被代理,这里其实也会被代理

假设没获取到,那么此时往下走else,尝试从父容器中获取bean

image.png

bean的实例化阶段

从这个阶段开始,bean就会一步一步被创建出来父容器也没有,就要自己去创建这个对象,在创建之前合并BeanDefinition 和 注册依赖的bean,@DependsOn注解就是在这个阶段发挥作用的

image.png

接下来就是对bean的作用返回进行判断,从这里可以看出,其实spring对于bean的作用范围中的单例和多例其实是采用硬编码的方式来进行完成的,其余的bean的作用范围,比如在web环境中的bean作用域session、springcloud环境中的@RefreshScope注解等都是通过扩展org.springframework.beans.factory.config.Scope的实现来完成的。大家有兴趣可以看看SessionScope和RefreshScope这两个实现类。

image.png

补充一点,肯定会有人好奇,我的代码明明没有动过,我的Controller一直在那,怎么做到的一个session一个Controller,其实原理很简单,就是你看见的Controller其实是个代理对象,每次调用的时候都会根据session的不同去重新创建一个新的真正的Controller对象去调用,这里涉及到spring aop的知识,有机会我们再讲。不过从这里可以看出,spring 的 ioc和aop是spring的核心功能,spring实现的其他机制,很多都是通过这两个特性展开的。

接着我们顺着创建单例bean继续往下看,把创建单例bean的重要的每个环节都看一遍,从这我们就开始深入bean的生命周期源码阶段。从createBean方法开始

bean class 的加载阶段

image.png

因为可能是通过xml文件来声明bean的,所以要把bean class加载一下

bean实例化之前阶段

这个阶段主要是回调所有的InstantiationAwareBeanPostProcessor对象的postProcessBeforeInstantiation方法,这个阶段如果有返回对象,直接不走下面的生命周期了(因为返回值不为null,直接return了),所以一般没有人这么玩。

BeanPostProcessor组件体系

InstantiationAwareBeanPostProcessor,这个接口是BeanPostProcessor的子类,BeanPostProcessor接口及其衍生的接口(接下来我称为BeanPostProcessor组件)是bean生命周期中一个非常核心的类体系,因为spring bean在创建过程中不同的阶段都会回调BeanPostProcessor组件的方法,这样就可以达到扩展的目的。因为只要你自己实现了BeanPostProcessor组件,就可以在生命周期的不同阶段可以对你的bean进行不同的操作,达到自己的目的。比如说阿里开源的dubbo中@DubboReference注解(2.7.7版本推出的注解,取代@Reference注解,功能没有什么变化)在整合spring的过程中主要是通过ReferenceAnnotationBeanPostProcessor来实现的,这个接口就是BeanPostProcessor的实现。说实话,bean的生命周期一大部分都是通过BeanPostProcessor组件来完成扩展的。

我们继续往下看

image.png

进入resolveBeforeInstantiation方法

image.png

进入applyBeanPostProcessorsBeforeInstantiation方法

image.png

bean的实例化阶段

这个阶段是根据你的class对象,来创建一个实例对象出来。

进入doCreateBean方法

image.png

进入createBeanInstance方法,对象就在这个方法创建的

image.png

@Bean的构建方式、构造器注入创建对象的方式,这两个创建的细节就不研究了

image.png

通过带构造参数的实例化构造方法来实例化我们就不看了。那么就进入instantiateBean方法

image.png

从这里可以看出,不论怎么走,都是通过getInstantiationStrategy方法获取实例化对象的策略然后调用instantiate来实例化对象。点进getInstantiationStrategy方法会发现其实是获取的CglibSubclassingInstantiationStrategy,那么我们就进入instantiate方法

image.png

这里我们可以看出,其实是获得了class的默认构造器,然后调用BeanUtils.instantiateClass(constructorToUse)来实例化对象,这是这内部就是简单的反射调用构造器创建对象。就不点进去了。

其实从这里我们可以看出,其实spring在创建对象实例的时候,最简单的方式其实就是通过反射直接通过调用的构造方法进行实例化。其实spring对象的实例化还有其他的方式,比如我上面图片标注的@Bean的构建方式、构造器注入创建对象的方式都不是走这。

在后面就是对创建创建出来的对象包装成BeanWrapper对象,直接返回。至此,bean的对象就被实例化出来了。

bean 的实例化之后阶段

接着往下看。

image.png

这是一个很重要的一步,主要是为了解决循环依赖的,跟文章最前面说的解决循环依赖是能够相呼应上的。

接下来看populateBean方法

image.png

image.png

看看,这里就是继续回调BeanPostProcessor组件体系的方法,所以回调完就表明spring bean的创建阶段完成。至于这个阶段为什么叫spring的bean的实例化之后阶段,你可以看看回调的方法的名字,翻译过来就是后置处理在bean实例化之后,所以叫这个阶段。

这个方法回调完之后下面代码就是bean生命周期中又一个核心的阶段,那就是属性赋值阶段,什么@Autowired依赖注入之类的其实就是在下面代码给你完成的。但是你有没有发现,postProcessAfterInstantiation如果这个方法返回false,下面的代码就不会执行了,所以一般扩展也没有返回false的,没人这么玩。其实你可以发现,spring在bean的创建过程中提供了非常多的可扩展点,你可以在每个阶段改变bean的创建行为,虽然可能没有人去这么做,但是spring依然提供了这些点。其实这也是读源码的有趣的地方,你可以看见各种扩展点,自己就可以去使用扩展点,进行各种骚操作。

总结

我们把这篇文章总结一下,最开始根据配置bean的方式封装到BeanDefinition中注册到BeanDefinitionRegistry中,然后说讲了bean的获取,自己的容器获取不到就会从父容器获取,如果都没获取到就会自己创建。说创建之前,简单的说明了spring是如何通过三级缓存解决循环依赖的问题。创建的时候会根据bean的作用域不同,进行了不同的创建。接下来我们选择了深入单例bean的创建源码,进入了bean创建的生命周期创建阶段,bean class 的加载,bean的实例化阶段,详细分为实例化之前阶段、实例化阶段、实例化之后阶段,顺便插入了对BeanPostProcessor组件体系的讲解。至于spring bean的生命周期的其它阶段,比如属性赋值阶段,初始化阶段,我会再写一篇文章来讲述剩下的阶段。预知后事如何,就请听下回分解吧。谢谢大家。

最后,如果本篇文章对你所有帮助,欢迎转发、点赞、收藏、在看,非常感谢。

相关文章
|
15天前
|
XML 安全 Java
|
1月前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
13天前
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
26 1
|
2月前
|
XML Java 数据格式
Spring从入门到入土(bean的一些子标签及注解的使用)
本文详细介绍了Spring框架中Bean的创建和使用,包括使用XML配置文件中的标签和注解来创建和管理Bean,以及如何通过构造器、Setter方法和属性注入来配置Bean。
76 9
Spring从入门到入土(bean的一些子标签及注解的使用)
|
2月前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
3月前
|
XML Java 数据格式
spring复习02,xml配置管理bean
详细讲解了Spring框架中基于XML配置文件管理bean的各种方式,包括获取bean、依赖注入、特殊值处理、属性赋值、集合类型处理、p命名空间、bean作用域及生命周期和自动装配。
spring复习02,xml配置管理bean
|
2月前
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细解析Spring Bean的生命周期及其核心概念,并深入源码分析。Spring Bean是Spring框架的核心,由容器管理其生命周期。从实例化到销毁,共经历十个阶段,包括属性赋值、接口回调、初始化及销毁等。通过剖析`BeanFactory`、`ApplicationContext`等关键接口与类,帮助你深入了解Spring Bean的管理机制。希望本文能助你更好地掌握Spring Bean生命周期。
125 1
|
2月前
|
Java Spring
获取spring工厂中bean对象的两种方式
获取spring工厂中bean对象的两种方式
46 1
|
2月前
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细介绍了Spring框架中的核心概念——Spring Bean的生命周期,包括实例化、属性赋值、接口回调、初始化、使用及销毁等10个阶段,并深入剖析了相关源码,如`BeanFactory`、`DefaultListableBeanFactory`和`BeanPostProcessor`等关键类与接口。通过理解这些核心组件,读者可以更好地掌握Spring Bean的管理和控制机制。
102 1
|
3月前
|
XML Java 数据格式
spring复习03,注解配置管理bean
Spring框架中使用注解配置管理bean的方法,包括常用注解的标识组件、扫描组件、基于注解的自动装配以及使用注解后的注意事项,并提供了一个基于注解自动装配的完整示例。
spring复习03,注解配置管理bean