写点什么

大佬带你深入理解 Spring 依赖注入原理:bean 的注册及实例化

  • 2023-06-16
    湖南
  • 本文字数:3668 字

    阅读完需:约 12 分钟

Spring 依赖注入原理分析

Spring 中关于依赖注入的代码实现非常丰富,涉及大量类和组件之间的协作与交互。从原理上讲,任何一个框架都存在一条核心执行流程,只要抓住这条主流程,我们就能把握框架的整体代码结构,Spring 也不例外。


无论采用何种依赖注入机制,前提都是 Spring IoC 容器正常启动。因此,IoC 容器初始化就是我们理解和把握依赖注入实现机制的前提。


本节结合 Bean 的生命周期,把 IoC 容器初始化过程梳理成两大步骤,即 Bean 的注册和 Bean 的实例化。这两个步骤就构成了一条代码主流程。

Bean 的注册

在使用 Spring 时,我们可以通过获取一个应用上下文(ApplicationContext)对象来操作各种 Bean,示例代码如下所示,相信你对这段代码不会陌生。

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
复制代码

这里的 ApplicationContext 接口代表的就是一个 Spring IoC 容器,而在 Spring 中存在一大批 ApplicationContext 接口的实现类。如果使用基于注解的配置方式,就可以使用上述代码中的 AnnotationConfigApplicationContext 来初始化容器上下文对象。


在刚开始阅读 Spring 源码时,我建议你直接从 AnnotationConfigApplicationContext 的启动流程切入,这一流程位于它的构造函数中,如下所示:

public AnnotationConfigApplicationContext(Class<?>...annotatedClasses) {	this();	//根据注解配置类注册Bean	register(annotatedClasses);	//刷新容器	refresh();}public AnnotationConfigApplicationContext(String... basePackages) {	this();	//根据包路径配置扫描Bean	scan(basePackages);	//刷新容器	refresh();}
复制代码

这两个构造函数的作用很明确,一个是根据注解配置类注册 Bean,另一个则是根据包路径配置扫描 Bean。这里我们以 register()方法为例,来讨论 Bean 的注册过程,该方法如下所示。

public void register(Class<?>... annotatedClasses) {	Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");	this.reader.register(annotatedClasses);}
复制代码

这里依赖 AnnotatedBeanDefinitionReader 工具类来完成 Bean 的注册。


AnnotatedBean-DefinitionReader 会遍历所有传入的 annotatedClasses 注解类,然后通过如下所示的 doRegisterBean()方法完成注册。(由于该方法的代码较长,我们对重要逻辑添加了注释,对不重要的代码做了省略。)

<T> void doRegisterBean(...) {	//将注解配置类信息转换成一种BeanDefinition	AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);	//获取Bean的作用域元数据,解析Bean作用域	ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);	//将Bean的作用域写回BeanDefinition	abd.setScope(scopeMetadata.getScopeName());	//生成beanName	String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));	//解析AnnotatedBeanDefinitionReader中的@Lazy、@Primary等注解	AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);	//处理@Qualifier注解	if (qualifiers != null) {		for (Class<? extends Annotation> qualifier : qualifiers) {			if (Primary.class == qualifier) {				// 如果设置了@Primary注解,则设置当前Bean为首选Bean				abd.setPrimary(true);			}			else if (Lazy.class == qualifier) {				//如果设置了@Lazy注解,则设置当前Bean为延迟加载模式 abd.setLazyInit(true);			}			else {				//其他注解,则添加到BeanDefinition中				abd.addQualifier(new				AutowireCandidateQualifier(qualifier));			}		}	}…	//注册Bean对象	BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder,this.registry);}
复制代码

这段代码包含 Bean 注册过程中的三个核心步骤,如下图所示:

首先,我们构建用来描述 Bean 实例信息的 BeanDefinition 对象,这需要将注解配置类信息转成 AnnotatedGenericBeanDefinition 数据结构,而 AnnotatedGenericBeanDefinition 就是一种 BeanDefinition,包含了 Bean 的构造函数参数、各种属性值以及所添加的注解信息。


然后,我们设置 BeanDefinition 属性,这一步骤完成了对 @Scope、@Primary、@Lazy 等注解的处理。最后,通过 registerBeanDefinition()方法完成 Bean 的注册,该方法内部通过 Listable-BeanFactory 的实现类 DefaultListableBeanFactory 将 Bean 定义信息注册到 Spring IoC 容器中。ListableBeanFactory 是 Spring 中常用的一个 BeanFactory,通过这个接口,我们可以一次获取多个 Bean。

Bean 的实例化

请注意,到现在为止,Spring IoC 容器对 Bean 的创建过程并没有完成,我们只是将 Bean 的定义加载到了容器中而已。但是容器本身可能已经存在这些 Bean 的定义,所以我们还需要调用 ApplicationContext 接口的抽象实现类 AbstractApplicationContext 中的 refresh()方法刷新容器,正如我们在前面看到的 AnnotationConfigApplicationContext 构造函数所执行的那样。


可以说,refresh()方法是整个 Spring 容器中最为核心的一个方法,值得我们详细讨论。但因为这里关注的是依赖注入,所以我们只列出 refresh()方法中与该主题相关的代码,如下所示:

@Overridepublic void refresh() throws BeansException, IllegalStateException {	synchronized (this.startupShutdownMonitor) {		// 提取配置信息转化为BeanDefinition并注册到BeanFactory中		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();		try {			...			// 初始化所有的单例Bean			finishBeanFactoryInitialization(beanFactory);			...		}	}}
复制代码

可以看到,obtainFreshBeanFactory()方法完成 BeanDefinition 的注册并返回一个 Bean-Factory。对于 AnnotationConfigApplicationContext 而言,这一步实际上就是将 BeanDefinition 注册到 DefaultListableBeanFactory 而已,我们在前面已经介绍了这一步骤。


而 finishBeanFactoryInitialization()方法才是真正的完成 Bean 实例化的入口。在这个方法中,完成 Bean 的实例化代码实际上位于它的子类 DefaultListableBeanFactory 中,如下所示:

@Overridepublic void preInstantiateSingletons() throws BeansException {	List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);	// 触发所有非懒加载的单例Bean的初始化操作	for (String beanName : beanNames) {		...		//获取Bean		getBean(beanName);		...	}}
复制代码

接下来,我们就进入到 getBean()方法了,这个方法可以从 BeanFactory 中获取一个 Bean,而 Bean 的初始化过程也被封装在这个方法中。在 getBean()方法中,我们一路跟踪代码会发现需要深入分析的实际上是如下所示的一个 createBean()抽象方法:

protected abstract Object createBean(String beanName,

RootBeanDefinition mbd, @Nullable Object[] args) throws

BeanCreationException;


请注意,在 Spring 中,实现这个抽象方法的唯一 BeanFactory 是 AbstractAutowireCap-ableBeanFactory。从命名上看,我们就可以联想到 @Autowired 注解。在 AbstractAutowire-CapableBeanFactory 中,真正完成 Bean 的创建是在 doCreateBean()方法中。doCreateBean()方法比较长,为了显示得更简洁,我们对代码做了大量裁剪之后得到如下所示的结构。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {	//1. 初始化Bean	instanceWrapper = createBeanInstance(beanName, mbd, args);	//2. 初始化Bean实例	populateBean(beanName, mbd, instanceWrapper);	//3. 执行初始化Bean实例的回调	exposedObject = initializeBean(beanName, exposedObject, mbd);	return exposedObject;}
复制代码

可以看到这里包含三个核心子方法,它们的名称和作用如图所示:

在以上三个步骤中,createBeanInstance()方法用于根据配置生成具体的 Bean,最终通过基于构造器的反射方法实现这一目标。请注意,执行完这一步之后,Bean 已经被创建了,但还不完整,因为属性还没有被注入。


接下来的 populateBean()方法就是用于实现属性的自动注入,包含 byName、byType 类型的自动装配,以及基于 @Autowired、@Value 注解的属性设值。执行完这一步之后,可以说 Bean 已经是完整的了。

而最后的 initializeBean()方法则更多是一种扩展性的实现机制,用于在 Bean 初始化完成之后执行一些定制化操作。


至此,针对整个 Bean 的注入过程(即 Bean 的注册和实例化),我们围绕核心流程做了剖析和总结。在这个过程中,比较容易碰到的一个问题就是陷入代码的细节而忽略了主体步骤。因此,如果想要跳出源码阅读的困境,快速掌握框架的实现原理,我们就必须从核心流程来看待框架。


接下来,我们将对 Spring 依赖注入中典型的循环依赖问题进行系统分析。


可以说,想要理解循环依赖问题以及对应的解决方案,掌握本节阐述的 Bean 的注册和实例化原理至关重要。

用户头像

加VX:bjmsb02 凭截图即可获取 2020-06-14 加入

公众号:程序员高级码农

评论

发布
暂无评论
大佬带你深入理解Spring依赖注入原理:bean的注册及实例化_互联网架构师小马_InfoQ写作社区