美文网首页Spring
快速理解Spring启动流程

快速理解Spring启动流程

作者: MuziBlogs | 来源:发表于2020-06-01 17:34 被阅读0次

    一、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有一个更清晰的认知。

    相关文章

      网友评论

        本文标题:快速理解Spring启动流程

        本文链接:https://www.haomeiwen.com/subject/rlrszhtx.html