
本文是spring框架源码学习系列的第二篇。
文章内容基于上一篇《bean定义加入ioc容器》,即完成注册bean定义之后的流程。
主要是关于bean的生命周期和依赖注入(Dependency Injection)的问题,二者同为需要特别关注的spring核心概念。
与其他语言、框架类似,这里bean的生命周期也指对象从出生到销毁的周期性活动;而依赖注入则是spring控制反转思想(Inverse Of Control)的主要表现形式。
spring根据bean定义信息创建bean组件,并加入ioc容器。在这个过程中,bean的生命周期钩子会被触发,其中就包括通过依赖注入的方式,为bean设置属性的节点(依赖注入是bean生命周期的一部分),分析其流程和运作方式是本文的主要目标。
一、项目结构
为了能以同样简单的方式去分析,沿用了上一篇文章里的代码。
.
|___debug-5.iml
|___pom.xml
|___src
| |___test
| | |___java
| |___main
| | |___resources
| | | |___application.properties
| | |___java
| | | |___com
| | | | |___err0l
| | | | | |___spring
| | | | | | |___debug5
| | | | | | | |___HelloWorld.java
| | | | | | | |___Debug5Application.java
| | | | | | | |___TestController.java
| | | | | | | |___AppConfig.java
但也做了一点改变:为TestController类中添加了helloWorld属性,以便分析bean注入依赖的代码。
// 配置类; 启用组件扫描功能;
@Configuration
@ComponentScan
public class AppConfig {
}
// 启动类
public class Debug5Application {
public static void main(String[] args) {
// 使用基于注解的ApplicationContext
final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
final String[] definitionNames = context.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name);
}
}
}
@Component
public class HelloWorld {
public String key = "hello, ";
public String value = "world";
}
@Controller
public class TestController {
@Autowired
private HelloWorld helloWorld;
}
二、注册AutowiredAnnotationBeanPostProcessor
spring总是会将某种功能划分到某一个拓展点中,所以类似的,首先要向容器中注册实现了相关功能的组件。
其名为AutowiredAnnotationBeanPostProcessor,是BeanPostProcessor接口的实现类,同时也是spring核心组件之一,用于处理注入相关的事宜,如使用@Autowired、@Value、@Inject等注解注入依赖。
支持构造注入(Autowired Constructors),方法注入(Autowired Method)和字段注入(Autowired Fields),在使用上有一些差异,具体可查看文档,此处图简洁&遍历,所以以字段注入为例。
还请注意,虽然看上去差不多,但BeanPostProcessor和上一篇文章中提到的BeanFactoryPostProcessor是两个不同的接口,BeanPostProcessor也是spring中的一个重要拓展点,是专门用于修改bean实例的接口,而BeanFactoryPostProcessor是专门用于修改bean定义的接口。
注册bean定义的方式,与注册ConfigurationClassPostProcessor的流程基本一致,甚至还在同一个方法内进行。
但是二者的执行时机不同,因为BeanFactoryPostProcessor是用于处理bean定义的接口,包括修改和新增,它于bean定义加入容器之后,bean实例化之前调用;BeanPostProcessor则是用于拦截每个bean的创建,在初始化前后调用。
还有一个不同点,BeanFactoryPostProcessor只是处理一些特定的bean定义,即只针对部分;而BeanPostProcessor则是应用于所有bean,每创建一个bean,BeanPostProcessor的方法都会被调用。
- new AnnotationConfigApplicationContext(AppConfig.class);
-- this();
--- new AnnotatedBeanDefinitionReader(this);
---- AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
// 如果容器内没有的话,就注册一个
if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
def.setSource(source);
// 将bean定义注册进registry
beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
}
更准确的说,它是一个InstantiationAwareBeanPostProcessor类型,重写了postProcessProperties(),一个专门用于处理属性值的方法,在bean实例化完成之后执行。
default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
return pvs;
}
图2-1 AutowiredAnnotationBeanPostProcessor类重写的postProcessProperties()方法
在方法内设置断点后,启动应用。
三、创建bean
注意,这里的“创建bean”是指,bean从”不可用“到”可用“的完整流程,与后面的“实例化bean”做个区分。
默认情况下,bean一般都为单例(Single Bean),这是spring的默认作用域设置,指的是在同一个容器中,一个类只对应一个实例。
创建bean的代码比较靠后,除了注册bean定义外,还有注册&实例化BeanPostProcessor。
比方说,前面注册的AutowiredAnnotationBeanPostProcessor是在以下步骤中完成的:
registerBeanPostProcessors(beanFactory)
以及初始化广播器和注册监听器等,这部分就不过多关注了。
initApplicationEventMulticaster();
registerListeners();
总而言之,就是要等到spring的环境都准备好后,才开始创建bean。
- new AnnotationConfigApplicationContext(AppConfig.class);
-- refresh();
--- invokeBeanFactoryPostProcessors(beanFactory);
--- registerBeanPostProcessors(beanFactory);
--- finishBeanFactoryInitialization(beanFactory);
---- beanFactory.preInstantiateSingletons(); // 开始位置
先获取到所有的bean定义名称(这是在将bean定义加入ioc容器时添加的与之一一对应的id),通过名称获取到bean定义信息,再创建bean。
// org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
for (String beanName : beanNames) {
// 获取到bean定义信息
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 如果不是抽象的,且是单例模式时,调用preInstantiateSingleton()
if (!mbd.isAbstract() && mbd.isSingleton()) {
CompletableFuture<?> future = preInstantiateSingleton(beanName, mbd);
if (future != null) {
futures.add(future);
}
}
}
方法内部再进行一次判断,如果不是懒加载模式的话,调用创建方法:
图3-1 创建bean
不过,虽然定义为"instantiateSingleton",但其实方法里又调用了getBean(),仅从方法名称上推断,其作用应该是"获取bean",但结合两者的话,也不难得出,getBean()有两种作用。
一种是创建bean,另一种是返回bean
private void instantiateSingleton(String beanName) {
if (isFactoryBean(beanName)) {
// 略
}
else {
getBean(beanName);
}
}
四、getBean()
而在此处,调用方法后,并没有接收返回值,所以更多的是指创建bean的作用。
源码比较复杂,为了简化逻辑,这里只贴出创建一般bean的主要流程
图4-1 内部调用doGetBean()
在java中,doXXX()方法,有种“真正要这么做了”的意思
图4-2 调用getSingleton()
在getSingleton()方法里,先例行检查缓存中是否已经存在目标bean,如果不存在的话,才调用这个拉姆达表达式创建bean。
图4-3 调用singletonFactory.getObject()
五、bean的生命周期
在sring中,bean的生命周期分为以下7个阶段:
阶段 | 说明 |
---|---|
1. 实例化(Instantiation) | Spring 容器创建 Bean 的实例(通过构造函数或工厂方法) |
2. 属性赋值(Populate Properties) | 依赖注入(DI):为 Bean 设置属性值和其他 Bean 的引用 |
3. 初始化前(Before Initialization) | 调用 BeanPostProcessor.postProcessBeforeInitialization() |
4. 初始化(Initialization) | 调用初始化方法(如 @PostConstruct 、InitializingBean.afterPropertiesSet() 、自定义 init-method ) |
5. 初始化后(After Initialization) | 调用 BeanPostProcessor.postProcessAfterInitialization() |
6. 使用中(In Use) | Bean 就绪,可以被应用程序使用 |
7. 销毁(Destruction) | 容器关闭时,调用销毁方法(如 @PreDestroy 、DisposableBean.destroy() 、自定义 destroy-method ) |
全程由ioc容器管理,也可以介入,比方说前面提到的BeanPostProcessor接口,其实现类AutowiredAnnotationBeanPostProcessor,就是作用于bean初始化之后。
以下将挑几个与本文相关度较高的阶段来分析。
1、实例化bean
也就是从外部传入的拉姆达表达式。
图5-1 调用createBean()
# 方法所在类: org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
- createBean()
-- Object beanInstance = doCreateBean(beanName, mbdToUse, args)
--- instanceWrapper = createBeanInstance(beanName, mbd, args); // 实例化bean
---- instantiateBean(beanName, mbd)
----- getInstantiationStrategy().instantiate(mbd, beanName, this)
2、属性赋值
或者说依赖注入。
虽然spring花费了比较多的篇幅来描述这个概念,但在我看来,就是“spring为对象实例填充属性,而不需要开发者参与其中”的这么个事
在此之前...
还需要提前把需要注入的参数列表解析出来,如使用@Autowired标注的属性。简单来说,这一步要把需要注入的属性都找出来,为后续注入做准备。
而负责这部分工作的正是AutowiredAnnotationBeanPostProcessor组件。
- doCreateBean(beanName, mbdToUse, args)
-- applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName)
--- processor.postProcessMergedBeanDefinition(mbd, beanType, beanName)
---- findInjectionMetadata(beanName, beanType, beanDefinition)
----- findAutowiringMetadata(beanName, beanType, null)
若没有缓存,则为当前clazz创建注入元数据(即需要注入的参数列表),并加入缓存,供后续使用。
图5-2 创建注入参数列表-1
大致的意思是:通过反射获取到clazz的成员变量,并根据@Autowired、@Value等条件筛选过滤,最后包装为AutowiredElement类型。
其中,由AutowiredElement类中的inject()方法负责注入属性值。
图5-3 创建注入参数列表-2
方法注入同理,也是包装为AutowiredElement类型。
最终,buildAutowiringMetadata()方法返回一个InjectionMetadata实例。
// ...
return InjectionMetadata.forElements(elements, clazz);
创建注入参数列表到这里就结束了。
前置工作完成后,开始进行依赖注入。
调用populateBean()方法,就是所谓的DI。
图5-4 populateBean()
先创建一个空的key-value表。
图5-5 new MutablePropertyValues()
然后再次运行AutowiredAnnotationBeanPostProcessor组件方法,只不过,这次调用的是来自InstantiationAwareBeanPostProcessor接口postProcessProperties()方法。
InjectionMetadata可直接从缓存中获取,因为在上一步中已经设置好了。
图5-6 开始注入
遍历上一步解析的参数列表,并调用inject()方法。
顺便一提,这里InjectedElement有两种子类型,一种是AutowiredMethodElement,另一种是AutowiredFieldElement,分别对应方法和字段注入元素。
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
// ...
if (!elementsToIterate.isEmpty()) {
for (InjectedElement element : elementsToIterate) {
// 为方法或字段注入属性
element.inject(target, beanName, pvs);
}
}
}
解析过程中,若容器中没有找到对应类型的值,则创建。
类似与方法调用,“如果方法里还调用了方法,就继续调用,直至返回”。
图5-7 解析字段值
- resolveFieldValue(field, bean, beanName)
-- beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter)
--- doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter)
---- getBean(dependencyName)
----- doGetBean(name, null, null, false)
------ ...
如果ioc容器中不存在对应的bean,则创建bean,这跟前面描述创建过程基本一致。
唯一的不同点是返回值,创建testController的时候,丢弃返回值;而创建testController的依赖,helloWorld时,接收了返回值。
该返回值将会被应用于字段中:
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
图5-8 创建(依赖)bean
3、初始化bean
涵盖了bean生命周期的3-5阶段,简单来说,就是调用各种方法,包括Aware、BeanPostProcessor、InitializingBean等一系列接口指定的方法。
不过一般情况下,这个初始化方法里基本上都是spring的默认实现,“什么都不会做,只是按流程跑一遍”。
有特定需求时才会考虑这些。
exposedObject = initializeBean(beanName, exposedObject, mbd);
图5-9 initializeBean()
六、将bean加入ioc容器
控制权返回DefaultSingletonBeanRegistry#getSingleton(String beanName, ObjectFactory<?> singletonFactory),并从singletonFactory.getObject()拿到返回值,同时将其标志为新bean,作为后续将bean加入ioc容器的判断条件。
图6-1 bean创建完成
若newSingleton为true,则执行添加操作。
图6-2 将bean加入容器-1
从图6-3截图中,可以看到,一级缓存里确实不存在testController,添加之,并将其从二级、三级缓存中移除,如果存在的话。
至此,bean加入ioc的流程基本分析完毕。
图6-3 将bean加入容器-2
七、结语
继上一篇文章中分析了bean定义的注册后,本文又简单分析了bean加入ioc容器的源码。
这个过程包含两个核心概念:bean的生命周期和依赖注入(DI)。
spring创建bean组件时,会触发bean的整个生命周期:先实例化,再填充属性(DI),然后初始化,最终将bean加入ioc容器;依赖注入部分则由AutowiredAnnotationBeanPostProcessor组件负责,先解析出需要注入的参数列表,再逐个进行赋值,这个步骤可能会触发bean的创建过程。
现在终于是把spring扫描&创建组件的流程串在一起了,可喜可贺XD。
以上。