Spring

快速理解Spring启动流程

2020-06-01  本文已影响0人  MuziBlogs

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

上一篇下一篇

猜你喜欢

热点阅读