SpringBoot启动流程(二)

76 阅读5分钟

前言

在上文中我们讲了SpringBoot的启动流程中SpringApplication的初始化、环境准备、容器创建,在本文中我会带大家来了解SpringBoot的最后一个流程:填充容器,即以下方法:

在这里插入图片描述

填充容器

prepareContext


    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        // 设置容器环境
        context.setEnvironment(environment);...(1)
        //对容器相关属性进行了填充
        this.postProcessApplicationContext(context);...(2)
        this.applyInitializers(context);...(3)
        //监听器发布上下文准备事件
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            this.logStartupInfo(context.getParent() == null);
            this.logStartupProfileInfo(context);
        }
        // 获取Bean工厂,并且注册相关对象的Bean
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }

        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }

        if (this.lazyInitialization) {
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }

        Set<Object> sources = this.getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        // 将启动类注册到Bean工厂
        this.load(context, sources.toArray(new Object)...(4)
        listeners.contextLoaded(context);
    }

进入(1): 在这里插入图片描述 该方法会通过上文中进行环境准备获取的环境,对容器环境进行设置。 进入(2): 在这里插入图片描述 该方法同样很简单,对容器的类加载器、资源加载器和bean工厂属性进行了填充设置。 进入(3): 在这里插入图片描述 该方法会获取在SpringAplication的初始化阶段加载spring.factories文件中的上下文初始化,并且进行遍历对容器进行初始化。 我们随便进入多个实现类查看: 在这里插入图片描述 在这里插入图片描述 如上两个方法会对容器属性进行填充,如第一个方法会添加Bean工厂的后置处理器。 进入(4): 在这里插入图片描述 该方法首先会获取Bean定义读取,然后调用其load方法,进入最底层查看: 在这里插入图片描述

refreshContext

进入方法最底层:

  public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();...(1)
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);...(2)

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);...(3)
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

进入(1): 在这里插入图片描述 大致可以知道该方法对容器进行了填充

进入(2): 在这里插入图片描述 该方法对BeanFactory的相关属性进行了设置,并且注册了一些单例,例如环境、系统属性等。 接下来我会带大家来深入两个比较关键的点,一个是扫描指定报下的类将其注册到Bean工厂,另一个是自动装配的原理。 进入(3):

ComponentScan扫描

在这里插入图片描述 继续进入: 在这里插入图片描述 该方法对Bean工厂做出了一些处理,添加了一些后置处理器,我们接着往下看: 在这里插入图片描述 我们进入这个方法里: 在这里插入图片描述 可以看到这里只有一个关于配置类的后置处理器,那么该方法应该就是通过后置处理器加载配置类,我们查看具体的方法: 在这里插入图片描述 继续进入: 在这里插入图片描述 在该方法中,我们可以发现创建了一个关于配置类解析的对象,并调用parse方法进行解析,并且返回一些配置类,但是parse方法传入了一个变量,我们不知道是什么,到方法上面查看: 在这里插入图片描述 在这里插入图片描述

我们就可以知道这个变量就是Bean的全类名,大家是否记得在进行刷新上下文时将主类注册到了容器中,因此获取的变量中也存在主类名。

查看parse()方法: 在这里插入图片描述 继续进入: 在这里插入图片描述 再次进入: 在这里插入图片描述

在这里插入图片描述 可以发现该方法会读取ComponentScan的配置信息,接着往下面查看: 在这里插入图片描述 我们发现这样的一个方法,该方法返回了一个Bean定义的集合,进入查看: 在这里插入图片描述 在该方法中,会对获得的ComponentScan的对象的一些属性进行配置,例如basePackages,我们上面获取的值是null,没有进行配置,使用debug进行查看: 在这里插入图片描述 其值变成了主类所在的包 在这里插入图片描述

进入该方法: 在这里插入图片描述 该方法会根据获取的包扫描指定的包,获取需要注册成Bean的对象。 在这里插入图片描述 在这里插入图片描述

自动装配

继续回到该方法: 在这里插入图片描述 往下走: 在这里插入图片描述 查看这个getImports方法: 在这里插入图片描述 继续查看: 在这里插入图片描述

该方法采用递归的形式,获取所有的Import注解的value值,即所有引入的类。 我们回到组件扫描解析方法: 在这里插入图片描述 该方法是自动配置的入口,进入: 在这里插入图片描述 接着进入: 在这里插入图片描述 进入getImports方法: 在这里插入图片描述 继续进入: 在这里插入图片描述 进入该方法: 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 该方法想必读过我上一篇文章的同学已经相当熟悉了,就是加载META-INF/spring.factories中的自动配置类。

在这里插入图片描述 然后会进行过滤,将重复的类以及根据@Condition注解排除不需要进行自动装配的Bean。

填充容器总结

填充容器前的准备:获取之前准备的环境,并设置到容器中,对容器的其他属性进行设置,并且会将主类注册到容器中

填充容器:对容器的一些属性进行填充设置,并且会解析ComponentScan注解,对其属性进行设置,扫描指定的包,将配置类注册到Bean容器,在该阶段还会进行自动装配,获取Import注解,调用该类的方法,会加载META-INF/spring.factories中的自动配置类,通过@Condition等注解对配置类进行过滤,最后注册到容器中

SpringBoot启动流程

启动流程包含四个阶段:SpringApplication的初始化、环境准备、创建容器以及填充容器。

SpringApplication的初始化:设置SpringApplication的一些属性,会根据指定的类是否存在来获取web服务的应用类型,包括NONE、RACTIVE、SERVLET,然后加载META-INF/spring.factories中的上下文初始化、注册初始化、上下文监听器,最后确定主方法类,将这些属性填充到SpringApplication中。

环境准备:首先会根据在SpringApplication初始化阶段确定的web服务应用类型,加载指定的环境类创建环境,并填充当前的系统环境、JDK环境以及手动编译的配置文件application.yaml等环境信息,填充到环境中。

创建容器:根据在SpringApplication初始化阶段确定的web服务应用类型,加载指定的容器类创建容器。

填充容器:填充容器包括填充容器前的准备和填充容器,在填充容器前的准备中会将前面进行环境准备的环境,设置到容器中,并填充容器的一些属性,还会将主类注册到容器中。在填充容器阶段会填充容器的一些其他属性,并且会解析ComponentScan注解,扫描指定包将配置类注册到容器中,并且会进行自动装配,将一些第三方jar包中需要进行自动装配的类注册到容器中