Spring源码 (二) - bean加入ioc容器
errol发表于2025-09-02 | 分类为 编程 | 标签为spring源码java

本文是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;
}

image

图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);
        }
    }
}

方法内部再进行一次判断,如果不是懒加载模式的话,调用创建方法:

image

图3-1 创建bean

不过,虽然定义为"instantiateSingleton",但其实方法里又调用了getBean(),仅从方法名称上推断,其作用应该是"获取bean",但结合两者的话,也不难得出,getBean()有两种作用。

一种是创建bean,另一种是返回bean

private void instantiateSingleton(String beanName) {
    if (isFactoryBean(beanName)) {
        // 略
    }
    else {
        getBean(beanName);
    }
}

四、getBean()

而在此处,调用方法后,并没有接收返回值,所以更多的是指创建bean的作用。

源码比较复杂,为了简化逻辑,这里只贴出创建一般bean的主要流程

image

图4-1 内部调用doGetBean()

在java中,doXXX()方法,有种“真正要这么做了”的意思

image

图4-2 调用getSingleton()

在getSingleton()方法里,先例行检查缓存中是否已经存在目标bean,如果不存在的话,才调用这个拉姆达表达式创建bean。

image

图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) 调用初始化方法(如 @PostConstructInitializingBean.afterPropertiesSet()、自定义 init-method
5. 初始化后(After Initialization) 调用 BeanPostProcessor.postProcessAfterInitialization()
6. 使用中(In Use) Bean 就绪,可以被应用程序使用
7. 销毁(Destruction) 容器关闭时,调用销毁方法(如 @PreDestroyDisposableBean.destroy()、自定义 destroy-method

全程由ioc容器管理,也可以介入,比方说前面提到的BeanPostProcessor接口,其实现类AutowiredAnnotationBeanPostProcessor,就是作用于bean初始化之后。

以下将挑几个与本文相关度较高的阶段来分析。

1、实例化bean

也就是从外部传入的拉姆达表达式。

image

图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创建注入元数据(即需要注入的参数列表),并加入缓存,供后续使用。

image

图5-2 创建注入参数列表-1

大致的意思是:通过反射获取到clazz的成员变量,并根据@Autowired、@Value等条件筛选过滤,最后包装为AutowiredElement类型。

其中,由AutowiredElement类中的inject()方法负责注入属性值。

image

图5-3 创建注入参数列表-2

方法注入同理,也是包装为AutowiredElement类型。

最终,buildAutowiringMetadata()方法返回一个InjectionMetadata实例。

// ...
return InjectionMetadata.forElements(elements, clazz);

创建注入参数列表到这里就结束了。


前置工作完成后,开始进行依赖注入

调用populateBean()方法,就是所谓的DI。

image

图5-4 populateBean()

先创建一个空的key-value表。

image

图5-5 new MutablePropertyValues()

然后再次运行AutowiredAnnotationBeanPostProcessor组件方法,只不过,这次调用的是来自InstantiationAwareBeanPostProcessor接口postProcessProperties()方法。

InjectionMetadata可直接从缓存中获取,因为在上一步中已经设置好了。

image

图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);
        }
    }
}

解析过程中,若容器中没有找到对应类型的值,则创建

类似与方法调用,“如果方法里还调用了方法,就继续调用,直至返回”。

image

图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);
}

image

图5-8 创建(依赖)bean

3、初始化bean

涵盖了bean生命周期的3-5阶段,简单来说,就是调用各种方法,包括Aware、BeanPostProcessor、InitializingBean等一系列接口指定的方法。

不过一般情况下,这个初始化方法里基本上都是spring的默认实现,“什么都不会做,只是按流程跑一遍”。

有特定需求时才会考虑这些。

exposedObject = initializeBean(beanName, exposedObject, mbd);

image

图5-9 initializeBean()

六、将bean加入ioc容器

控制权返回DefaultSingletonBeanRegistry#getSingleton(String beanName, ObjectFactory<?> singletonFactory),并从singletonFactory.getObject()拿到返回值,同时将其标志为新bean,作为后续将bean加入ioc容器的判断条件。

image

图6-1 bean创建完成

若newSingleton为true,则执行添加操作。

image

图6-2 将bean加入容器-1

从图6-3截图中,可以看到,一级缓存里确实不存在testController,添加之,并将其从二级、三级缓存中移除,如果存在的话。

至此,bean加入ioc的流程基本分析完毕。

image

图6-3 将bean加入容器-2

七、结语

继上一篇文章中分析了bean定义的注册后,本文又简单分析了bean加入ioc容器的源码。

这个过程包含两个核心概念:bean的生命周期依赖注入(DI)。

spring创建bean组件时,会触发bean的整个生命周期:先实例化,再填充属性(DI),然后初始化,最终将bean加入ioc容器;依赖注入部分则由AutowiredAnnotationBeanPostProcessor组件负责,先解析出需要注入的参数列表,再逐个进行赋值,这个步骤可能会触发bean的创建过程。

现在终于是把spring扫描&创建组件的流程串在一起了,可喜可贺XD。

以上。

返回