快速理解Spring启动流程

一、Spring继承结构

1、Spring容器的继承结构

Spring容器的继承结构

常见的容器的实现类有ClassPathXmlApplicationContext、AnnotationConfigApplicationContext这两个实现类。其中一种是基于XML解析的实现类,一种是基于注解扫描的实现类。

2、Spring工厂的继承结构

Spring工厂的继承结构

Spring中默认的工厂的实现类就是DefaultListableBeanFactory,还有一些别名和单例相关的接口没有在图中绘制。

3、什么是BeanDefinition?

BeanDefinition属性

在高版本的Spring中,BeanDefinition是上图中的内容,可以看到BeanDefinition中有很多属性,其中每个属性有不同的含义,有一些是标识类信息和bean名称的,有继承关系的属性,是否懒加载的属性,是否可被依赖的属性,创建销毁方法指定的属性等。这些属性在Bean的实例化创建过程中起到至关重要的作用。

二、Spring容器的启动方式

1. 通过加载XML的方式

启动Spring容器的方式,在以往WEB项目中,我们是基于web.xml中配置<context-param>标签来启动一个Spring容器的。

<!--初始化spring 容器:-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:/spring5/exercise/web01/spring.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

该启动方式是通过类路径来加载spring.xml配置文件来启动Spring容器。在启动的过程中,Spring会对配置文件中配置的一些标签来加载并保存Bean的定义,最后统一通过这些Bean的定义来实例化并缓存对象。

1.1 手动创建加载XML的Spring容器
    @Test
    public void run01(){
        // 基于加载XML配置文件的方式,启动spring容器
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath*:spring5/**/demo01/spring.xml");
        ProductService productService = (ProductService) context.getBean("productService");
        productService.show();
    }

2. 通过扫描注解的方式

当下SpringBoot大行其道,抛开XML配置,基于扫描扫描包/类注解的方式来启动Spring容器才是当下的主流。

2.1 手动创建扫描包路径的Spring容器
    @Test
    public void run02(){
        // 基于扫描注解的方式,启动spring容器
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext("com.jd.nlp.dev.muzi.spring5.exercise.demo01");
        ProductService productService = (ProductService) context.getBean("productService");
        productService.show();
    }
2.2 手动创建扫描类注解的Spring容器
    @Test
    public void run06(){
        // 基于扫描注解的方式,启动spring容器  ScanClass配置了@ComponentScan注解
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(ScanClass.class);
        ProductService productService = (ProductService) context.getBean("productService");
        productService.show();
    }

测试方法run02是传入一个包路径,Spring递归扫描包路径下的Java类,识别类上的注解,创建并保存Bean的定义,然后通过Bean的定义来统一实例化并缓存对象。

2.3 测试方法run06和run02的区别

run06加载这个类的注解信息,如果类注解信息有@Configuration,@Import,@ImportSource,@ComponentScan等注解,同样会创建保存相关的Bean的定义,然后通过Bean的定义来统一实例化并缓存对象。

三、快速理解Spring容器的加载流程

还是以最开始学习使用的ClassPathXmlApplicationContext容器为案例,摸清Spring容器的加载的脉络。

1. 容器的构造函数

首先来看ClassPathXmlApplicationContext类的构造函数。

    public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
        /**
         * 调用另一个构造函数,并刷新上下文,当前容器独立容器没有parent不需要设置父容器环境
         */
        this(new String[] {configLocation}, true, null);
    }
1.1 调用重载构造函数
  1. 先设置父容器环境
  2. 将真实的匹配到的文件路径存入容器上下文
  3. 调用refresh()方法进行Spring容器的刷新。
    public ClassPathXmlApplicationContext(
            String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
            throws BeansException {
        /**
         * 设置父容器环境
         */
        super(parent);
        /**
         * 拿到配置文件的路径的参数
         */
        setConfigLocations(configLocations);
        /**
         * 刷新上下文
         */
        if (refresh) {
            refresh();
        }
    }

2. 容器加载的核心方法 refresh()

2.1 核心方法 refresh() 介绍

主要看 refresh() 刷新上下文的方法 , “刷新上下文” 直观的讲就是初始化一个容器 ,“上下文” 我理解就代指的是 “容器”。
先来看一下refresh()这个方法的整体内容,流程中每一个方法上我都加了一些注释,注释有写不重要的我都标记了,看了也无益于对流程的理解。既然是快速理解Spring的内容,那就需要挑重点的内容去梳理。

    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            /*
             * 忽略
             * 为刷新spring 容器初始化做准备的一个方法
             */
            prepareRefresh();
            /**
             * 重要必读
             * obtainFreshBeanFactory流程
             * 配置信息到beanDefinition的转换过程分以下几步:
             *  1.创建beanFactory对象
             *  2.xml解析
             *      传统标签解析: 非传统标签等都被定义为自定义标签 bean import
             *      自定义标签解析: context:component-scan  aop:
             *          自定义标签解析流程:
             *          1。根据当前解析标签的头信息找到对应的namespaceUri
             *          2。加载spring所有资源路径下的spring.handlers文件,并建立映射关系
             *          3。根据namespaceUri从映射关系中找到对应的实现了NamespaceHandler接口的类
             *
             *  3.把解析完等xml封装成BeanDefinition对象
             *  4.BeanDefinition 简单的小装饰如果需要
             *  5.别名 -> beanName -> BeanDefinition构成三级级映射
             * 重要程度:* * * * *
             */
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            /*
             * 忽略
             * 给BeanFactory设置一些属性值
             */
            prepareBeanFactory(beanFactory);
            try {
                /*
                 * 忽略
                 */
                postProcessBeanFactory(beanFactory);
                /**
                 * 动态添加/修改我们配置的基本信息,通过实现接口(实现接口的的类需要在obtainFreshBeanFactory环节已经被注册好),完成beandefinition 的新增和修改。
                 * 功能:
                 *  完成对下述两个接口的调用
                 *  BeanDefinitionRegistryPosProcessor
                 *  BeanFactoryPostProcessor
                 * 由来:
                 * 为了解决在Sping启动加载流程缺乏修改/新增beandefinition的能力,所以才会在流程中
                 * 加上invokeBeanFactoryPostProcessors功能,帮助开发人员更好的集成/使用Spring。
                 * 优先于其他的类进行实例化。
                 * 重要程度:* * * * *
                 */
                invokeBeanFactoryPostProcessors(beanFactory);
                /**
                 * 重要: * * * * *
                 * 实现BeanPostProcessor接口实现类的注册
                 * Bean实例化过程中需要的组件、解析器、处理器  提前实例化。
                 */
                registerBeanPostProcessors(beanFactory);
                /*
                 * 忽略
                 * 国际化
                 */
                initMessageSource();
                /*
                 * 忽略
                 * 初始化事件管理类
                 */
                initApplicationEventMulticaster();
                /*
                 * 忽略
                 * 典型的钩子方法
                 * 这个方法着重理解模版设计模式,在Springboot 1.5 版本中,这个方法是用来做内嵌tomcat启动的
                 */
                onRefresh();
                /*
                 * 忽略
                 * 往时间管理类中注册事件类
                 */
                registerListeners();

                /**
                 * 重要
                 * 这个方法是Spring中最重要的方法之一
                 * 1.bean的实例化过程
                 * 2.IOC
                 * 3.注解支持
                 * 4.BeanPostProcessor的执行
                 * 5.Aop的入口
                 * 重要程度: * * * * *
                 */
                finishBeanFactoryInitialization(beanFactory);
                /*
                 * 忽略
                 * 容器加载后的一些处理
                 */
                finishRefresh();
            }
            // ............. 省略无关代码
        }
    }

2.1 核心流程 - prepareRefresh()

该方法为刷新spring 容器初始化做准备的一个方法,并非是IOC的关键内容可以忽略。

2.2 核心流程 - obtainFreshBeanFactory()

重要程度:* * * * *

obtainFreshBeanFactory()方法得到一个创建、扫描和注册后的BeanFactory。
该方法主要内容:

  1. 创建BeanFactory
  2. 解析XML
  3. 注册BeanDefinition

这个方法很重要,也是Spring源码中一块儿比较难啃的骨头,涉及到的代码层层递进,旋转跳跃,很深,需要话费一些时间去研究。

2.3 核心流程 - prepareBeanFactory(beanFactory);

该方法仅仅是给BeanFactory设置一些属性值,并没有深挖这些属性值的含义,并非是IOC的关键内容可以忽略。

2.4 核心流程 - postProcessBeanFactory(beanFactory);

该方法ClassPathXmlApplicationContext类是继承的AbstractRefreshableWebApplicationContext类的实现,注册了一个ServletContextAwareProcessor这个BeanPostProcessor,并非是IOC的关键内容可以忽略。

2.5 核心流程 - invokeBeanFactoryPostProcessors(beanFactory);

重要程度:* * * * *

BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor接口实现类的方法调用。

BeanFactoryPostProcessor介绍

接口:BeanFactoryPostProcessor
方法:postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)

实现BeanFactoryPostProcessor的类需要重写postProcessBeanFactory方法。该类的实例在Spring初始化流程执行到invokeBeanFactoryPostProcessors的时候,可以操作当前容器的beanFactory,去完成自己想要做的一些事情。

BeanDefinitionRegistryPostProcessor介绍

接口:BeanDefinitionRegistryPostProcessor
方法1:postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
方法2:postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)

BeanDefinitionRegistryPostProcessor继承了BeanFactoryPostProcessor接口,其实现类需实现两个方法postProcessBeanDefinitionRegistry和postProcessBeanFactory方法。该类的实例在Spring初始化流程执行到invokeBeanFactoryPostProcessors的时候,可以操作当前容器的beanFactory。也可以操作BeanDefinitionRegistry registry这个注册器,去增删改查BeanDefinition。

2.6 核心流程 - registerBeanPostProcessors(beanFactory);

重要程度:* * * * *

该方法实例化所有继承了 BeanPostProcessor 接口实现类并注册到容器中。

BeanPostProcessor介绍

BeanPostProcessor接口是活动在Bean实例化流程中至关重要的接口,主要功能有收集@Autowired注解的构造函数、@Autowired和@Resource注解的扫描、IOC-DI完成对@Autowired@Resource和Xml配置依赖等方式的注入、@PostConstruct的调用、AOP判断是否需要创建Bean代理对象等功能。当然我们也可以继承这个接口在Bean的实例化流程中做我们想做的工作。

2.7 核心流程 - initMessageSource();

国际化相关的内容,并非是IOC的关键内容可以忽略。

2.8 核心流程 - initApplicationEventMulticaster();

初始化事件管理类,并非是IOC的关键内容可以忽略。

2.9 核心流程 - onRefresh();

这个方法着重理解模版设计模式,在Springboot 1.5 版本中,这个方法是用来做内嵌tomcat启动的,并非是IOC的关键内容可以忽略。

2.10 核心流程 - registerListeners();

往时间管理类中注册事件类,并非是IOC的关键内容可以忽略。

2.11 核心流程 - finishBeanFactoryInitialization(beanFactory);

重要程度:* * * * *

Spring实例化Bean的流程,学习Spring源码必须要看的一个内容。这个方法通过之前扫描到的 BeanDefinition 中的信息去实例化Bean,IOC,DI,AOP等。

BeanDefinition是什么需要搞清楚,它不是一个Bean,而是存储着一个Bean创建所需要的全部信息,我们通过BeanDefinition中的信息去反射创建对象。

2.12 核心流程 - finishRefresh();

容器加载后的一些处理,并非是IOC的关键内容可以忽略。

四、总结

1. Spring源码的学习,我觉得最需要理解的内容有以下几点:

  1. BeanDefinition中有哪些属性?
  2. BeanDefinitionRegistryPostProcessor接口和BeanFactoryPostProcessor接口的调用
  3. BeanPostProcessor接口实现类实例化组册

2. 简要概括Spring加载流程

  1. 基于xml类路径找到xml文件地址
  2. 创建BeanFactory
  3. 解析XML文件并注册BeanDefinition
  4. BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor接口的方法调用
  5. BeanPostProcessor接口实现类对象实例化
  6. 遍历BeanName集合,基于BeanDefinition实例化对象
  7. DI依赖注入 populateBean
  8. 初始化后处理 @PostConstruct init-method initializeBean
  9. AOP 判断对象是否有切面 , 是否需要代理 。

本文是属于对Spring启动流程的大致描述,上述每一个过程都有很多很多跳来跳去的代码需要看,后续的文章中会详细的梳理,希望观看这些文章会使得你对Spring有一个更清晰的认知。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351