概叙
这篇主要总结Spring中bean的创建过程,主要分为==加载bean信息–>实例化bean–>属性填充–>初始化阶段–>后置处理等步骤,且每个步骤Spring做的事情都很多,这块源码还是很值得我们都去看一看的。==而Spring中Bean的声明周期其实就是创建到使用到销毁,使用应该没啥需要说的,销毁在第三部分也正常介绍了三种销毁的方式。
如不想看文字,可以直接看后面的流程图。
什么是 Bean?
我们来看下 Spring Framework 的官方文档:
In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your application. Beans, and the dependencies among them, are reflected in the configuration metadata used by a container.
简而言之,bean 是由 Spring IoC 容器实例化、组装和管理的对象。
什么是 Spring Bean 的生命周期?
对于普通的 Java 对象,当 new 的时候创建对象,然后该对象就能够使用了。一旦该对象不再被使用,则由 Java 自动进行垃圾回收。
而 Spring 中的对象是 bean,bean 和普通的 Java 对象没啥大的区别,只不过 Spring 不再自己去 new 对象了,而是由 IoC 容器去帮助我们实例化对象并且管理它,我们需要哪个对象,去问 IoC 容器要即可。IoC 其实就是解决对象之间的耦合问题,Spring Bean 的生命周期完全由容器控制。
Spring创建bean的详细流程
上面的流程图其实已经可以很清晰的看到bean的创建过程了,这里结合图片我们一起来详细说下这个过程,这里不贴源码,贴了源码只会让观看的人比较迷糊,若是想跟源码的可以对照上面的流程图完全能做到源码复现,bean创建的这个过程大致可以分为五步:加载bean信息,实例化bean,bean属性填充,初始化bean,后置操作,那我们就基于这五大步来看看Spring是如何创建bean的。
1.加载bean信息
被IOC注解修饰的类,或者通过xml配置的类,首先在容器启动时一refresh方法为入口,会将这些类扫描进来形成BeanDefinition信息,BeanDefinition就是包含了我们配置的一些bean的属性,比如是否单例,是否有bean依赖(DependOn),bean的名称,bean所属class的全路径等,这里存储的相当于bean的元信息,然后通过 BeanDefinitionRegistry将这些BeanDefinition加载进来后面我们就可以利用该信息了,且在Spring创建Bean的全程都需要BeanDefinition的参与,所以他很重要。
2.实例化bean
通过上面的图可以清晰看到在实例化阶段之前其实还有很多小的操作:容器会先去尝试getBean–>doGetBean–>getSingleton等操作在这些操作都拿不到对象以后才会开始着手创建对象,需要说的是getSingleton会尝试从三级缓存中依次去获取Bean,当所有缓存都获取不到时就可以确认当前bean没有被创建,然后就可以启动创建的相关动作
- 利用BeanDefinition检查是否有依赖的bean(配置了@DependOn注解)如有,需要先加载依赖bean
- 利用BeanDefinition检查是否单例bean,是走单例bean的创建流程,不是再判断是否是原型bean,是走原型bean创建,否则都是另一套路径创建
- 开始实例化,调用getSingleton,此时传入的是对象工厂(ObjectFactory)的实现类,因为对象工厂是函数式接口,这里传入的其实就是createBean‘的lamda表达式
- 将当前bean加入到正在创建bean的一个set
- 调用对象工厂的getObject方法,因为我们再上面已经传入了对象工厂(通过lamda表达式传入)这里相当于调用刚刚的lamda表达式,调用里面的createBean方法
- createBean去调了doCreateBean又调了createBeanInstance,在这里底层通过反射技术获取构造参数将对象创建了出来,此时的对象只是通过空参构造创建出来的对象,他并没有任何的属性。
- 调用addSingletonFactory将实例化完成的bean加入到三级缓存,到这里实例化就算是结束了
3.bean属性填充
属性填充其实就为自身属性进行赋值的过程,根据我们的DI注解这里会先从三个缓存中获取bean,若是获取不到,则会尝试进行bean的创建,若是走到了bean的创建,则会重新走一边bean创建的整个流程,这里是递归逻辑。
- populateBean 该方法是填充属性的入口,传入beanName和BeanDefinition
- 从BeanDefinition中获取属性注入相关信息然后判断是名称注入还是类型注入
- 调用getSingleton从容器中获取所需对象,若是获取不到则会重走对象创建的整个流程,拿到完整对象后将其给到当前bean的属性,到这里属性填充就结束了
4.初始化bean
属性填充完毕后并没有立即结束这个过程,还有一些其他的操作需要spring进行处理,比如aware接口的处理,postprocessor接口的处理,初始化的处理等操作其实这里主要就是处理这三个动作的
- 判断有无实现aware接口,如有则去执行他的实现类的实现方法,所有aware接口可以参考上图中所列的三个aware接口,在spring初始化时会对他们进行是否实现的判断
- 获取容器中所有postprocessor接口,然后开始执行他的前置方法
- 判断有无实现初始化接口InitializingBean如有则去执行初始化方法afterPropertiesSet
- 执行postprocessor的后置方法,通过前置和后置方法我们可以实现自定义的一些逻辑,不过需要注意的是这些前置和后置方法会作用到所有bean
5.后置操作
这里的后置操作,主要是完成一些清扫工作和适配工作,比如删除二级、三级缓存中无用的bean引用等,下面是具体操作。
- 将bean从创建中的集合中删除
- 将bean加入到单例池中将其从二级三级缓存中删除
- 对对象进行一些适配操作,到这里完成了初始化的所有操作,后面就是一步步返回调用的地方了
看了这五步,不知道是不是对bean的创建过程有了清晰的认识,如果还是不够清晰可以根据第一部分的流程图走下代码,代码走两遍其实就会比较清晰了。
bean的生命周期
这里我们说的 Spring Bean 的生命周期主要指的是 singleton bean,对于 prototype 的 bean ,Spring 在创建好交给使用者之后则不会再管理后续的生命周期。
我们也来复习下 Spring 中的 bean 的作用域有哪些?
singleton
: 唯一 bean 实例,Spring 中的 bean 默认都是单例的。prototype
: 每次请求都会创建一个新的 bean 实例。request
: 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。session
: 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。global-session
: 全局 session 作用域,仅仅在基于 Portlet 的 web 应用中才有意义,Spring5 已经没有了。Portlet 是能够生成语义代码(例如:HTML)片段的小型 Java Web 插件。它们基于 portlet 容器,可以像 servlet 一样处理 HTTP 请求。但是,与 servlet 不同,每个 portlet 都有不同的会话。
我们知道对于普通的 Java 对象来说,它们的生命周期就是:
- 实例化
- 该对象不再被使用时通过垃圾回收机制进行回收
而对于 Spring Bean 的生命周期来说:
- 实例化 Instantiation
- 属性赋值 Populate
- 初始化 Initialization
- 销毁 Destruction
实例化 -> 属性赋值 -> 初始化 -> 销毁
只有四个步骤,这样拆解的话是不是感觉也不难?不像其他人写的那样直接一上来就各种 BeanPostProcessor、BeanFactoryPostProcessor 全部怼进流程里去,别说读者看着头大,自己写的可能短时间内还记得流程,隔个一段时间,你可能都不知道自己写了个啥。
本来小编想通过 Bean 创建流程入口
AbstractApplicationContext#refresh() 方法的 finishBeanFactoryInitialization(beanFactory) 处带大家跟一下源码,想了想还是不带入过多的代码进来,直接给到最终的主要逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
至于销毁,是在容器关闭时调用的,详见 ConfigurableApplicationContext#close()
是不是很清爽了?至于 BeanPostProcessor、BeanFactoryPostProcessor 以及其他的类,在老周看来,只不过是对主流程四个步骤的一系列扩展点而已。
bean的生命周期其实就是从创建到销毁,上面创建已经说完了,其实只差销毁这一步了。bean销毁发生在容器关闭时对单例bean进行清除操作。在Spring中我们通常有三种方式定义bean销毁时的逻辑
1.通过PreDestroy注解修饰方法
Bean销毁时会检查有无该注解修饰的方法,如有,会对该注解修饰的方法进行执行
2.通过指定destroy-method方法
在使用xml对bean进行注入时,我们可以指定init-method方法,也可以指定destroy-method方法,同样的使用Bean注解时也是支持这两个属性的,Spring容器关闭时会寻找当前bean有无指定destroy-method,如有则会进行执行
3.实现DisposableBean接口
实现该接口重写他的destroy方法,同样的Spring容器关闭时也会检查有无实现该接口,如有实现也会执行这里的销毁方法
下面是对于三种销毁方式的测试代码
第一端是自定义Spring容器,给容器注册钩子,这样当我们关闭Spring容器时会自动调用我们的销毁方法
1 2 3 4 5 6 |
|
这一段是测试代码了,分别使用三种方式写了销毁方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
下面是启动main方法后的执行截图,可以清晰的看到三种销毁方法都是正常执行的,且他们执行顺序是固定的,即:PreDestroy–>DisposableBean–>destroy-method。
到这里其实bean整个生命周期就算是彻底结束了。
Spring Bean 的生命周期的扩展点
Spring Bean 的生命周期的扩展点超级多,老周这里不可能全部列出来,只说核心的扩展点。这也就是为什么 Spring 的扩展性很好的原因,开了很多的口子,尽可能让某个功能高内聚松耦合,用户需要哪个功能就用哪个,而不是直接来一个大而全的东西。
3.1 Bean 自身的方法
比如构造函数、getter/setter 以及 init-method 和 destory-method 所指定的方法等,也就对应着上文说的实例化 -> 属性赋值 -> 初始化 -> 销毁四个阶段。
3.2 容器级的方法(BeanPostProcessor 一系列接口)
主要是后处理器方法,比如下图的 InstantiationAwareBeanPostProcessor、BeanPostProcessor 接口方法。这些接口的实现类是独立于 Bean 的,并且会注册到 Spring 容器中。在 Spring 容器创建任何 Bean 的时候,这些后处理器都会发生作用。
3.2.1 InstantiationAwareBeanPostProcessor 源码分析
我们翻一下源码发现 InstantiationAwareBeanPostProcessor 是继承了 BeanPostProcessor
InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
调用点
Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName)
返回值:如果返回的不为null,那么后续的Bean的创建流程【实例化、初始化afterProperties】都不会执行,而是直接使用返回的快捷Bean,此时的正常执行顺序如下:
InstantiationAwareBeanPostProcessor接口中的postProcessBeforeInstantiation,在实例化之前调用。
BeanPostProcessor接口中的postProcessAfterInitialization,在实例化之后调用。
总之,postProcessBeforeInstantiation 在 doCreateBean 之前调用,也就是在 bean 实例化之前调用的,英文源码注释解释道该方法的返回值会替换原本的 Bean 作为代理,这也是 AOP 等功能实现的关键点。
InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation
调用点
boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException
正常情况下在实例化之后在执行populateBean之前调用
返回值:如果有指定的bean的时候返回false,那么后续的属性填充和属性依赖注入【populateBean】将不会执行,同时后续的postProcessPropertyValues将不会执行,但是初始化和BeanPostProcessor的仍然会执行。
public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName)
实例化之后调用,在方法applyPropertyValues【属性填充】之前
返回值:如果返回null,那么将不会进行后续的属性填充,比如依赖注入等,如果返回的pvs额外的添加了属性,那么后续会填充到该类对应的属性中。
pvs:PropertyValues对象,用于封装指定类的对象,简单来说就是PropertyValue的集合,里面相当于以key-value形式存放类的属性和值。
pds:PropertyDescriptor对象数组,PropertyDescriptor相当于存储类的属性,不过可以调用set,get方法设置和获取对应属性的值。
3.2.2 BeanPostProcessor 源码分析
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
进入初始化接口:
我们先来看
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization
- 首先获取到所有的后置处理器 getBeanPostProcessors()
- 在 for 循环中依次调用后置处理器的方法 processor.postProcessBeforeInitialization(result, beanName);
- 进入 postProcessBeforeInitialization 方法
org.springframework.context.support.ApplicationContextAwareProcessor#postProcessBeforeInitialization
进入 invokeAwareInterfaces(bean);
方法,当前 bean 实现了 ApplicationContextAware 接口。
- ApplicationContextAwareProcessor#postProcessBeforeInitialization 首先判断此 bean 是不是各种的Aware,如果是它列举的那几个 Aware 就获取 Bean 工厂的权限,可以向容器中导入相关的上下文环境,目的是为了 Bean 实例能够获取到相关的上下文,如果不是它列举的几个 Aware,那就调用 invokeAwareInterfaces(bean),向容器中添加相关接口的上下文环境。
3.3 工厂后处理器方法(BeanFactoryProcessor 一系列接口)
包括 AspectJWeavingEnabler、CustomAutowireConfigurer、ConfigurationClassPostProcessor 等。这些都是 Spring 框架中已经实现好的 BeanFactoryPostProcessor,用来实现某些特定的功能。
我们知道 Spring IoC 容器初始化的关键环节就在 org.springframework.context.support.AbstractApplicationContext#refresh 方法中 ,容器创建的主体流程都在这个方法里面,这个方法是真的重要!!!
对于工厂后处理器方法老周这里直接带你看 invokeBeanFactoryPostProcessors(beanFactory); 方法,这个方法处理的是 BeanFactoryPostProcessor 接口的 Bean。调用方法如下:
跟到最重要的方法里去,代码虽长,但逻辑中规中矩。
BeanFactoryPostProcessor
:一切处理 BeanFactory 的父接口BeanDefinitionRegistryPostProcessor
:实现了 BeanFactoryPostProcessor 接口的接口
流程说明:
- 调用 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(registry) 方法。参数 beanFactoryPostProcessors 传入的优先处理掉。然后获取容器注册的,对于这些 Bean 按照 PriorityOrdered 接口、Ordered、没有排序接口的实例分别进行处理。
- 调用 BeanFactoryPostProcessor#postProcessBeanFactory(beanFactory) 方法。备注:BeanDefinitionRegistryPostProcessor 属于 BeanFactoryPostProcessor 子接口。先处理属于 BeanDefinitionRegistryPostProcessor 接口实例的 postProcessBeanFactory(beanFactory) 方法,然后获取容器注册的。对于这些 Bean 按照 PriorityOrdered 接口、Ordered、没有排序接口的实例分别进行处理。
3.4 Bean 级生命周期方法
可以理解为 Bean 类直接实现接口的方法,比如 BeanNameAware、BeanFactoryAware、ApplicationContextAware、InitializingBean、DisposableBean 等方法,这些方法只对当前 Bean 生效。
3.4.1 Aware 类型的接口
Aware 类型的接口的作用就是让我们能够拿到 Spring 容器中的一些资源。基本都能够见名知意,Aware 之前的名字就是可以拿到什么资源,例如 BeanNameAware 可以拿到 BeanName,以此类推。调用时机需要注意:所有的 Aware 方法都是在初始化阶段之前调用的。
Aware 接口众多,这里同样通过分类的方式帮助大家记忆。Aware 接口具体可以分为两组,至于为什么这么分,详见下面的源码分析。如下排列顺序同样也是 Aware 接口的执行顺序,能够见名知意的接口不再解释。
Aware Group1
BeanNameAware
BeanClassLoaderAware
BeanFactoryAware
Aware Group2
EnvironmentAware
EmbeddedValueResolverAware
这个知道的人可能不多,实现该接口能够获取 Spring EL 解析器,用户的自定义注解需要支持 SPEL 表达式的时候可以使用,非常方便。
ApplicationContextAware(ResourceLoaderAware/ApplicationEventPublisherAware/MessageSourceAware)
这几个接口可能让人有点懵,实际上这几个接口可以一起记,其返回值实质上都是当前ApplicationContext 对象,因为 ApplicationContext 是一个复合接口,如下:
Aware 调用时机源码分析
可以看到并不是所有的 Aware 接口都使用同样的方式调用。Bean××Aware 都是在代码中直接调用的,而 ApplicationContext 相关的 Aware 都是通过 BeanPostProcessor#postProcessBeforeInitialization() 实现的。感兴趣的可以自己看一下 ApplicationContextAwareProcessor 这个类的源码,就是判断当前创建的 Bean 是否实现了相关的 Aware 方法,如果实现了会调用回调方法将资源传递给 Bean。
BeanPostProcessor 的调用时机也能在这里体现,包围住 invokeInitMethods 方法,也就说明了在初始化阶段的前后执行。
关于 Aware 接口的执行顺序,其实只需要记住第一组在第二组执行之前就行了。
3.4.2 生命周期接口
至于剩下的两个生命周期接口就很简单了,实例化和属性赋值都是 Spring 帮助我们做的,能够自己实现的有初始化和销毁两个生命周期阶段。
- InitializingBean 对应生命周期的初始化阶段,在上面源码的 invokeInitMethods(beanName, wrappedBean, mbd);方法中调用。
有一点需要注意,因为 Aware 方法都是执行在初始化方法之前,所以可以在初始化方法中放心大胆的使用 Aware 接口获取的资源,这也是我们自定义扩展 Spring 的常用方式。
除了实现 InitializingBean 接口之外还能通过注解或者 xml 配置的方式指定初始化方法,至于这几种定义方式的调用顺序其实没有必要记。因为这几个方法对应的都是同一个生命周期,只是实现方式不同,我们一般只采用其中一种方式。
- DisposableBean 类似于 InitializingBean,对应生命周期的销毁阶段,以ConfigurableApplicationContext#close()方法作为入口,实现是通过循环取所有实现了 DisposableBean 接口的 Bean 然后调用其 destroy() 方法,感兴趣的可以自行跟一下源码。
常用接口说明
4.1 BeanNameAware
该接口只有一个方法 setBeanName(String name)
,用来获取 bean 的id 或者 name
。
4.2 BeanFactoryAware
该接口只有一个方法 setBeanFactory(BeanFactory beanFactory)
,用来获取当前环境中的 BeanFactory
。
4.3 ApplicationContextAware
该接口只有一个方法 setApplicationContext(ApplicationContext applicationContext)
,用来获取当前环境中的 ApplicationContext
。
4.4 InitializingBean
该接口只有一个方法 afterPropertiesSet()
,在属性注入完成后调用
。
4.5 DisposableBean
该接口只有一个方法 destroy()
,在容器销毁的时候调用,在用户指定的 destroy-method 之前调用
。
4.6 BeanPostProcessor
该接口有两个方法:
- postProcessBeforeInitialization(Object bean, String beanName):在初始化之前调用此方法
- postProcessAfterInitialization(Object bean, String beanName):在初始化之后调用此方法
- 通过方法签名我们可以知道,我们可以通过 beanName 来筛选出我们需要进行个性化定制的 bean。
4.7 InstantiationAwareBeanPostProcessor
该类是 BeanPostProcessor 的子接口,常用的有如下三个方法:
- postProcessBeforeInstantiation(Class beanClass, String beanName):在bean实例化之前调用
- postProcessProperties(PropertyValues pvs, Object bean, String beanName):在bean实例化之后、设置属性前调用
- postProcessAfterInstantiation(Class beanClass, String beanName):在bean实例化之后调用
代码演示
思路:创建一个类 UserBean ,让其实现几个特殊的接口,并分别在接口实现的构造器、接口方法中断点,观察线程调用栈,分析出 Bean 对象创建和管理关键点的触发时机。
5.1 UserBean 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
|
5.2 InstantiationAwareBeanPostProcessor 接口实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
5.3 BeanPostProcessor 接口实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
5.4 BeanFactoryPostProcessor 接口实现类
1 2 3 4 5 6 7 |
|
5.5 applicationContext.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
5.6 测试类
1 2 3 4 5 6 7 |
|