一. 前言

Spring家族特别庞大,对于开发人员而言,要想全面征服Spring家族,得花费不少的力气。俗话说,打蛇打七寸,那么Spring家族的“七寸”是什么呢?我心目中的答案一直都是 Spring Framework!

本篇文章记录我自己在学习Spring Framework的过程中的一小部分源码解读和梳理,来谈一谈Spring 容器在启动过程中是如何扫描Bean的。

二. 学习方法论

我相信每个想变成优秀的开发人员都想弄懂Spring源码,我亦如此。于是通过很多途径来找Spring源码的学习资料、买书、看视频等等。到头来发现只有自己静下心来一步一步跟着源码调试,一行一行的深入理解,才能深入理解Spring的奥妙!这个过程很枯燥,但优秀的猎手最能耐得住寂寞和浮躁!

我们知道,Spring容器的启动方式有多种:XML文件、注解、Java Config。在实际的使用中并不是选择其中某一种,而是相互搭配。其底层的容器启动过程是一样的,只是入口变了而已。另外,学习Spring的最佳方式就是自己将源码工程构建出来,这样便于源码阅读、备注、修改。 构建出来的工程长这样:

三. 代码入口

话不多说直接开干!代码入口如下:

@Configuration
@ComponentScan("com.leon.funddatahouse")
public class Config {
} public class MyApplication {
public static void main(String[] args) {
// 我们基于注解的方式
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Config.class);
// 如果基于XML文件配置,则也可以如下:
// ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
}
}
复制代码

在构造方法中,总共做了3件事情。这三件事情包括了整个Spring容器启动的所有过程!啃碎他们,便成功了一半!

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
// 1.调用默认构造方法,进行容器环境的准备
this();
// 2.基于配置类注册相关信息
register(componentClasses);
// 3.刷新整个容器
refresh();
}
复制代码

四. 解析之前

在解析之前,先将容器和BeanFactory的UML类图放出。原因在于它们担任的角色、具备的功能太多太强大了,同时这也增加了源码理解的难度。因此这里先放出UML类图作为手册查看,便于理解源码。

4.1 容器UML类图

4.2 BeanFactoryUML类图

五. 源码解析

5.1 构造方法解析

5.1.1 初始化容器中的BeanFactory

在构造方法中,显式的调用了this(),既无参构造方法:

public AnnotationConfigApplicationContext() {
// 1.实例化容器中的reader. 后面会详细解析
this.reader = new AnnotatedBeanDefinitionReader(this);
// 2.实例化容器中的scanner.后面会详细解析
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
复制代码

乍看一眼,这个无参构造方法做了两件事情,其实不然。它实际上等同于:

public AnnotationConfigApplicationContext() {
// 1.调用父类构造方法
super();
// 2.实例化容器中的reader. 后面会详细解析
this.reader = new AnnotatedBeanDefinitionReader(this);
// 3.实例化容器中的scanner.后面会详细解析
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
复制代码

这一点很关键, 如果没有意识到这里隐形调用了父类构造方法的话, 那么接下来的路没法走, 因为在父类构造器中做了一件大事情:

// 在父类的构造方法中, 创建了容器中的BeanFactory.至此,容器中有了第一个程序创建的属性:beanFactory
public GenericApplicationContext() {
// 初始化容器的beanFactory,类型为DefaultListableBeanFactory
this.beanFactory = new DefaultListableBeanFactory();
}
复制代码

BeanFactory 和 FacotryBean的区别, 请点击这里

5.1.2 实例化容器中的Reader

reader最主要的目的是用于辅助注册BeanDefinition,其具体的使用后文在介绍,这里我们只需知道它包含了哪些东西。

// 入参registry就是容器本身。因为通过上面的UML类图可以发现,容器间接继承了BeanDefinitionRegistry
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
// getOrCreateEnvironment() 方法最主要是获取环境。实际类型其实默认的就是StandardEnvironment类。这里的环境包括两方面:
// 1.systemEnvironment:操作系统环境。这样,Spring就可以获取到操作系统、CPU核心数等操作系统本身的数据。
// 2.systemProperties:JVM的环境变量。这样,Spring就可以获取到JVM的基础数据,比如我们在启动参数中手动设置的环境变量等。
this(registry, getOrCreateEnvironment(registry));
}
复制代码

这里通过this() 调用了reader内部另一个构造方法:

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
Assert.notNull(environment, "Environment must not be null");
// 设置registry,已经知道它的就是容器本身:AnnotationConfigApplicationContext
this.registry = registry;
// 创建条件处理器
this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
// 非常关键! 提前往容器中注册一些必要的后置处理器
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
复制代码

这个构造方法很重要, 因为它涉及到spring容器当中的两个重要成员:条件解析器和后置处理器!

5.1.2.1 实例化条件处理器

相信熟悉Spring的人一定都知道或用过@ConditionalOnBean / @ConditionalOnClass 等条件注解.而这些条件注解的解析就是ConditionEvaluator.

public ConditionEvaluator(@Nullable BeanDefinitionRegistry registry,
@Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {
// 实际上是委托给内部类ConditionContextImpl
this.context = new ConditionContextImpl(registry, environment, resourceLoader);
} // ------------分割线------------------ // 内部的ConditionContextImpl构造器
public ConditionContextImpl(@Nullable BeanDefinitionRegistry registry,
@Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {
// 再说一遍,registry的实际类型就是 AnnotationConfigApplicationCont
this.registry = registry;
// 获取beanFactory,我们也知道了beanFactory其实就是 ConfigurableListableBeanFactory
this.beanFactory = deduceBeanFactory(registry);
// 从容器中获取environment,前面介绍过,容器中的environment的封装类是 StandardEnvironment
this.environment = (environment != null ? environment : deduceEnvironment(registry));
// 资源加载器. 通过UML类图可以发现,resourceLoader就是容器, 因为容器间接继承了ResourceLoader
this.resourceLoader = (resourceLoader != null ? resourceLoader : deduceResourceLoader(registry));
// 类加载器. 实际上就是获取beanFactory的类加载器。理应如此,容器当中的类加载器肯定要一致才行
this.classLoader = deduceClassLoader(resourceLoader, this.beanFactory);
}
复制代码

后面在解析BeanDefinition时我们还会遇到ConditionEvaluator, 其具体源码解析会用专门的文章来解析,本篇文章我们只需要知道它的作用即可.

5.1.2.2 注册一部分后置处理器

ConditionEvaluator初始化完成之后,接下来就特别重要了,因为在这里将提前注入一些后置处理器:

public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
// 空壳方法,实际委托给重载的方法
registerAnnotationConfigProcessors(registry, null);
}
复制代码

重载的方法如下(高能预警):

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
// 获取容器中的beanFactory,通过前面的解析,我们知道,这里一定会获取到。因此将进入if分支
DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
if (beanFactory != null) {
// 此时beanFactory的属性dependencyComparator为null,因为初始化过程中,内部成员变量如果没有默认值,则默认为null,
// 所以如果第一次进来, 这里的判断一定成立,对dependencyComparator进行设置。
// AnnotationAwareOrderComparator继承了OrderComparator,
// 因此可以对实现了Ordered接口、打上@Order或者@Priority注解的类进行排序。
// 也就是说,在这里设置beanFactory中的orderComparator,以支持解析bean的排序功能。
if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
}
// beanFactory初始化时,默认为SimpleAutowireCandidateResolver,因此第一次进来时这里的判断也一定成立。
// ContextAnnotationAutowireCandidateResolver最主要的作用就是支持@Lazy注解的类的处理。
if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
}
} // 初始化一个bdh容器,用于盛放接下来将解析出来的后置处理器的bd。
Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8); // 容器在第一次初始化时,内部一个bd都没有的。
// 也就是说,从这里开始,容器将第一次装载bd,而这里的这些bd都是spring自带的后置处理器。 // 获取并注册ConfigurationClassPostProcessor后置处理器 的bd
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
} // 获取并注册AutowiredAnnotationBeanPostProcessor后置处理器 的bd
if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
} // 获取并注册CommonAnnotationBeanPostProcessor后置处理器 的bd
if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
} // 获取并注册PersistenceAnnotationBeanPostProcessor后置处理器 的bd
if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition();
try {
def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
AnnotationConfigUtils.class.getClassLoader()));
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
}
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
} // 获取并注册EventListenerMethodProcessor后置处理器 的bd
if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
} // 获取并注册DefaultEventListenerFactory后置处理器 的bd
if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
} return beanDefs;
}
复制代码

这个方法首次出现了BeanDefinition这个类. Spring的BeanDefinition相当于Java的Class

通过该方法之后, beanFactory中就存在了以上6个bd:

曾经有人跟我说, 掌握了Spring的后置处理器, 那么整个Spring就掌握了10%! 可见其重要性.

但是在这里先不展开后置处理器(太多了),本篇文章的主线是容器启动过程。

5.1.2.3 reader初始化过程小结

到这里reader部分的初始化终于完成了。总结一下,reader的初始化主要干了这些事情:

1.创建并设置容器当中的Environment属性。即默认为StandardEnvironment类。

2.创建并设置容器当中的条件解析器,即ConditionEvaluator,其内部实际委托给内部类ConditionContextImpl。

3.注册6个后置处理器到容器当中。注意这里仅是生成了后置处理器的BeanDefinition。还并没有进行bean解析和后置处理的执行。

5.1.3 实例化容器中的Scanner

解析完reader之后,继续解析scanner。这里的scanner的实际类型是ClassPathBeanDefinitionScanner。它最主要的目的就是扫描类路径下所有的class文件能否解析为bd。 其最终调用的构造方法如下:

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
// 1.委托给内部的另一个构造方法
this(registry, true);
}
// ------------------------分割线-------------------------
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
// 2.又委托给内部的另一个构造方法 >_<
// 从上面的入参可以知道 入参的registry实际就是容器本身, 并使用默认的filter.这个filter干什么的,下面会解析
this(registry, useDefaultFilters, getOrCreateEnvironment(registry));
}
// ------------------------分割线-------------------------
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
Environment environment) {
// 3.又委托给内部的另一个构造方法 T^T
this(registry, useDefaultFilters, environment,
(registry instanceof ResourceLoader ? (ResourceLoader) registry : null));
}
// ------------------------分割线-------------------------
// 4. 终于见到了庐山真面目(不容易) ^_^
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
Environment environment, @Nullable ResourceLoader resourceLoader) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
// 再说一遍, registry就是容器!
this.registry = registry;
// 重要!!! 是否包括默认过滤器。从上面的入参可以知道, 这里的useDefaultFilters = true,因此会进入if分支
if (useDefaultFilters) {
registerDefaultFilters();
}
// 设置环境变量
setEnvironment(environment);
// 设置资源加载器
setResourceLoader(resourceLoader);
}
复制代码

5.1.3.1 registerDefaultFilters()方法

从最终的构造方法我们知道, Scanner在扫描的过程中,会使用过滤策略,并且使用了默认的过滤策略.默认策略就是以下这个方法解析.

protected void registerDefaultFilters() {
// 扫描@Component注解的类
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
// 扫描所有@ManageBean的类
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
}
try {
// 扫描所有@Named的类
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
}
}
复制代码

这里的一个知识点:@ManageBean和@Named的作用和@Component是一样的。只是我们通常习惯使用@Component。

为什么这里没有添加默认扫描@Service、@Repository、@Controller呢?原因很简单,这些注解都间接继承了@Component了。

到这里,scanner解析完毕,它做的最主要的事情就是添加默认的过滤器策略以便在后续中可以扫描出@Component注解的类。

六 默认构造方法小结

现在我们再来看一下构造方法:

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
// 1.调用默认构造方法,进行容器环境的准备
this();
register(componentClasses);
refresh();
}
复制代码

从入口看, 就只有这三行代码, 但其中的第一行,调用默认构造方法就做了这么多准备工作,其中也牵扯出了一些Spring整个体系中最重要的几个组件,比如BeanFactory / BeanDefinition / BeanDefinitionReader / BeanDefinitionScanner / Environment / ConditionEveluator / PostProcessor等等.随便拿一个出来都够喝一壶! 这些点会各个击破, 但不是本篇文章的重点,本篇文章的重点是先梳理整个启动过程的第一步: 构造方法的执行过程.

Spring源码系列——容器的启动过程(一)的更多相关文章

  1. Spring源码系列 — 容器Extend Point(一)

    前言 前文介绍了Spring中的BeanDefinition的细节,随着Spring的启动流程,这节我们介绍Spring的后续处理过程 - Spring的扩展点: BeanFactoryPostPro ...

  2. Spring源码系列 — BeanDefinition扩展点

    前言 前文介绍了Spring Bean的生命周期,也算是XML IOC系列的完结.但是Spring的博大精深,还有很多盲点需要摸索.整合前面的系列文章,从Resource到BeanDefinition ...

  3. Ioc容器依赖注入-Spring 源码系列(2)

    Ioc容器依赖注入-Spring 源码系列(2) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostPr ...

  4. Ioc容器beanDefinition-Spring 源码系列(1)

    Ioc容器beanDefinition-Spring 源码系列(1) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器 ...

  5. Ioc容器BeanPostProcessor-Spring 源码系列(3)

    Ioc容器BeanPostProcessor-Spring 源码系列(3) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Io ...

  6. Spring源码系列 — BeanDefinition

    一.前言 回顾 在Spring源码系列第二篇中介绍了Environment组件,后续又介绍Spring中Resource的抽象,但是对于上下文的启动过程详解并未继续.经过一个星期的准备,梳理了Spri ...

  7. Spring源码分析之`BeanFactoryPostProcessor`调用过程

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 本文内容: AbstractApplicationContext#refresh前部分的一点小内容 ...

  8. 事件机制-Spring 源码系列(4)

    事件机制-Spring 源码系列(4) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProcess ...

  9. Spring源码系列 — 注解原理

    前言 前文中主要介绍了Spring中处理BeanDefinition的扩展点,其中着重介绍BeanDefinitionParser方式的扩展.本篇文章承接该内容,详解Spring中如何利用BeanDe ...

随机推荐

  1. 【转】camera tuning

    chromatix项目必须包含有效的 ADC image.png filcker: 交流电照明灯发出的光会一定频率的抖动导致sensor图像出现行方向的水波纹,称之为filcker.目前主要的交流电频 ...

  2. 牛客网数据库SQL实战解析(11-20题)

    牛客网SQL刷题地址: https://www.nowcoder.com/ta/sql?page=0 牛客网数据库SQL实战解析(01-10题): https://blog.csdn.net/u010 ...

  3. 超市管理系统C语言

    登录系统 # include <stdio.h> //头文件 # include <string.h> //字符串头文件 # include <stdlib.h> ...

  4. win10安装JDK cmd中可以运行java,但不能用javac,解决方案

    win10安装JDK cmd中可以运行java,但不能用javac 网上教程(1)新建->变量名"JAVA_HOME",变量值"C:\Java\jdk1.8.0_0 ...

  5. private protected internal public

    //C#中的访问修饰符: //private,私有访问修饰符,被private访问修饰符修饰的成员只有在当前类的内部可以访问,其他地方一律不能访问[类中成员,如果不写访问修饰符则默认都是私有的] // ...

  6. python学习笔记回忆录02

    1.for循环 依次按顺序从列表中取出值,直到遍历完整个列表为止 the_count =[1,2,3,4,5] for number in the_count: print "this is ...

  7. springboot入门遇到Whitelabel Error Page错误

    错误页面: 解决方法: 启动类要放在最外层,改成下面的

  8. Q20200511-01 翻转字符串

    需求:做一函数将字符串倒转过来 程序: package test4; public class Reverse { public static String reverse(String origin ...

  9. 性能测试1:loadrunner介绍及代理录制

    一.安装loadrunner lr安装环境要求: Lr11只支持ie9及一下,火狐30以下,不支持chrome.  操作系统只支持到win7.Lr打开时必须用管理员身份打开 在虚拟机中安装的win7, ...

  10. 安卓自动化测试工具Monkey简单使用

    一.首先安装adb 地址:http://www.downza.cn/soft/219906.html安装到D盘下,安装的过程中自己注意下不要安装上全家桶.找到这个压缩包:解压到当前文件夹: 二.将ad ...