Spring容器启动流程(源码解读)

(本文基于 Spring 的 5.1.6.RELEASE 版本)

Spring的启动流程可以归纳为三个步骤:

  • 1、初始化Spring容器,注册内置的BeanPostProcessor的BeanDefinition到容器中
  • 2、将配置类的BeanDefinition注册到容器中
  • 3、调用refresh()方法刷新容器

一、前言

Spring Framework 是 Java 语言中影响最为深远的框架之一,其中的 IOC 和 AOP 两个经典思想更是一直被程序员津津乐道,后面推出的 Spring Boot、Spring Cloud 系列也是在其基础之上开发,要想搞明白 Spring 全家桶系列,必须脚踏实地的从 Spring Framework 学习起。

本篇文章是 Spring Framework 源码解析系列的第一篇,主要是从代码层面对 Spring 框架的启动做一个完整解析,这里的思想都是笔者根据自己使用 Spring 的经验和对 Spring 的了解综合而成,以下内容谨代表个人看法,若有疑问请不吝赐教。

另外提醒一下,本篇文章是基于 5.1.6.RELEASE 版本的代码进行分析,入口代码也是采用官方推荐的 java-config 技术,而非 xml。

二、源码解析

考虑到直接看源码是一个非常枯燥无味的过程,而且 Spring 的代码设计非常优秀规范,这会导致在翻开源码时,类与类之间的跳跃会非常频繁,不熟悉的同学可能直接晕菜,所以每一个重要流程前我都会先准备一个流程图,建议大家先通过流程图了解一下整体步骤,然后再对代码硬撸,这样能够降低不少难度。

相信每一个使用过 Spring 技术的同学都知道 Spring 在初始化过程中有一个非常重要的步骤,即 Spring 容器的刷新,这个步骤固然重要,但是刷新前的初始化流程也非常重要。本篇文章将整个启动过程分为了两个部分,即容器的初始化与刷新,下面正式开始。开始之前先插播一条广告:需要开通正版IDEA的可以联系我,56元一年,正版授权激活,官网可查有效期,有需要的加我微信:poxiaozhiai6,备注:912。

1、初始化流程

①、流程分析

因为是基于 java-config 技术分析源码,所以这里的入口是 AnnotationConfigApplicationContext ,如果是使用 xml 分析,那么入口即为 ClassPathXmlApplicationContext ,它们俩的共同特征便是都继承了 AbstractApplicationContext 类,而大名鼎鼎的 refresh 方法便是在这个类中定义的,现在就不剧透了,我们接着分析 AnnotationConfigApplicationContext 类,可以绘制成如下流程图:

看完流程图,我们应该思考一下:如果让你去设计一个 IOC 容器,你会怎么做?首先我肯定会提供一个入口(AnnotationConfigApplicationContext )给用户使用,然后需要去初始化一系列的工具组件:

①:如果我想生成 bean 对象,那么就需要一个 beanFactory 工厂(DefaultListableBeanFactory);

②:如果我想对加了特定注解(如 @Service@Repository)的类进行读取转化成 BeanDefinition 对象(BeanDefinition 是 Spring 中极其重要的一个概念,它存储了 bean 对象的所有特征信息,如是否单例,是否懒加载,factoryBeanName 等),那么就需要一个注解配置读取器(AnnotatedBeanDefinitionReader);

③:如果我想对用户指定的包目录进行扫描查找 bean 对象,那么还需要一个路径扫描器(ClassPathBeanDefinitionScanner)。

通过上面的思考,是不是上面的图理解起来就轻而易举呢?

ps:图中的黄色备注可以不看,只是在这里明确展示出来 Spring 的部分内置组件是何时何地添加到容器中的,关于组件的作用在后面的系列文章中会详细分析。

②、核心代码剖析

考虑到要是对所有代码都进行解析,那么文章篇幅会过长,因此这里只对核心内容进行源码层面的分析,凡是图中标注了 ①、②、③等字样的步骤,都可以理解为是一个比较重要的步骤,下面开始进行详细分析。

org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors

根据上图分析,代码运行到这里时候,Spring 容器已经构造完毕,那么就可以为容器添加一些内置组件了,其中最主要的组件便是 ConfigurationClassPostProcessorAutowiredAnnotationBeanPostProcessor ,前者是一个 beanFactory 后置处理器,用来完成 bean 的扫描与注入工作,后者是一个 bean 后置处理器,用来完成 @AutoWired 自动注入。

org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean

这个步骤主要是用来解析用户传入的 Spring 配置类,其实也是解析成一个 BeanDefinition 然后注册到容器中,没有什么好说的。

<T> void doRegisterBean(Class<T> annotatedClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name,
		@Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {
	// 解析传入的配置类,实际上这个方法既可以解析配置类,也可以解析 Spring bean 对象
	AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
	// 判断是否需要跳过,判断依据是此类上有没有 @Conditional 注解
	if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
		return;
	}

	abd.setInstanceSupplier(instanceSupplier);
	ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
	abd.setScope(scopeMetadata.getScopeName());
	String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
	// 处理类上的通用注解
	AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
	if (qualifiers != null) {
		for (Class<? extends Annotation> qualifier : qualifiers) {
			if (Primary.class == qualifier) {
				abd.setPrimary(true);
			}
			else if (Lazy.class == qualifier) {
				abd.setLazyInit(true);
			}
			else {
				abd.addQualifier(new AutowireCandidateQualifier(qualifier));
			}
		}
	}
	// 封装成一个 BeanDefinitionHolder
	for (BeanDefinitionCustomizer customizer : definitionCustomizers) {
		customizer.customize(abd);
	}
	BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
	// 处理 scopedProxyMode
	definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);

	// 把 BeanDefinitionHolder 注册到 registry
	BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

2、刷新流程

①、流程分析

下面这一段代码则是 Spring 中最为重要的一个步骤:容器刷新,同样先看图再分析。

看完流程图,我们也先思考一下:在 3.1 中我们知道了如何去初始化一个 IOC 容器,那么接下来就是让这个 IOC 容器真正起作用的时候了:即先扫描出要放入容器的 bean,将其包装成 BeanDefinition 对象,然后通过反射创建 bean,并完成赋值操作,这个就是 IOC 容器最简单的功能了。但是看完上图,明显 Spring 的初始化过程比这个多的多,下面我们就详细分析一下这样设计的意图:

如果用户想在扫描完 bean 之后做一些自定义的操作:假设容器中包含了 a 和 b,那么就动态向容器中注入 c,不满足就注入 d,这种骚操作 Spring 也是支持的,得益于它提供的 BeanFactoryPostProcessor 后置处理器,对应的是上图中的 invokeBeanFactoryPostProcessors 操作。

如果用户还想在 bean 的初始化前后做一些操作呢?比如生成代理对象,修改对象属性等,Spring 为我们提供了 BeanPostProcessor 后置处理器,实际上 Spring 容器中的大多数功能都是通过 Bean 后置处理器完成的,Spring 也是给我们提供了添加入口,对应的是上图中的 registerBeanPostProcessors 操作。

整个容器创建过程中,如果用户想监听容器启动、刷新等事件,根据这些事件做一些自定义的操作呢?Spring 也早已为我们考虑到了,提供了添加监听器接口和容器事件通知接口,对应的是上图中的 registerListeners 操作。

此时再看上图,是不是就觉得简单很多呢,下面就一些重要代码进行分析。

②、核心代码剖析

org.springframework.context.support.AbstractApplicationContext#refresh

这个方法是对上图中的具体代码实现,可划分为12个步骤,其中比较重要的步骤下面会有详细说明。

在这里&#

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值