zoukankan      html  css  js  c++  java
  • springboot启动类源码探索一波

    源码如何切分?

    SpringApplication中的静态run()方法并不是一步完成的,最终执行的源码如下:

    //org.springframework.context.ConfigurableApplicationContext
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
      return new SpringApplication(primarySources).run(args);
     }

    很显然分为两个步骤,分别是创建SpringApplication和执行run()方法,下面将分为这两个部分介绍。

    如何创建SpringApplication?

    创建即是new对象了,DEBUG跟进代码,最终执行的SpringApplication构造方法如下图:

    如上图中标注的注释,创建过程重用的其实分为这三个阶段,下面将会一一介绍每个阶段做了什么事。

    设置应用类型

    这个过程非常重要,直接决定了项目的类型,应用类型分为三种,都在WebApplicationType这个枚举类中,如下:

    1. NONE:顾名思义,什么都没有,正常流程走,不额外的启动web容器,比如Tomcat
    2. SERVLET:基于servlet的web程序,需要启动内嵌的servletweb容器,比如Tomcat
    3. REACTIVE:基于reactive的web程序,需要启动内嵌reactiveweb容器,作者不是很了解,不便多说。

    判断的依据很简单,就是加载对应的类,比如加载了DispatcherServlet等则会判断是Servlet的web程序。源码如下:

    static WebApplicationType deduceFromClasspath() {
      if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
        && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
       return WebApplicationType.REACTIVE;
      }
      for (String className : SERVLET_INDICATOR_CLASSES) {
       if (!ClassUtils.isPresent(className, null)) {
        return WebApplicationType.NONE;
       }
      }
      return WebApplicationType.SERVLET;
     }

    这里我引入了spring-boot-starter-web,肯定是Servlet的web程序。

    设置初始化器(Initializer)

    初始化器ApplicationContextInitializer是个好东西,用于IOC容器刷新之前初始化一些组件,比如ServletContextApplicationContextInitializer

    那么如何获取初始化器呢?跟着上图中的代码进入,在SpringApplication中的如下图中的方法:

    相对重要的就是第一步获取初始化器的名称了,这个肯定是全类名了,详细源码肯定在loadFactoryNames()方法中了,跟着源码进入,最终调用的是#SpringFactoriesLoader.loadSpringFactories()方法。

    loadSpringFactories()方法就不再详细解释了,其实就是从类路径META-INF/spring.factories中加载ApplicationContextInitializer的值。

    spring-boot-autoconfigurespring.factories文件中的值如下图:

    上图中的只是一部分初始化器,因为spring.factories文件不止一个。

    下图中是我的demo中注入的初始化器,现实项目中并不止这些。

    这也告诉我们自定义一个ApplicationContextInitializer只需要实现接口,在spring.factories文件中设置即可。

    设置监听器(Listener)

    监听器(ApplicationListener)这个概念在Spring中就已经存在,主要用于监听特定的事件(ApplicationEvent),比如IOC容器刷新、容器关闭等。

    Spring Boot扩展了ApplicationEvent构建了SpringApplicationEvent这个抽象类,主要用于Spring Boot启动过程中触发的事件,比如程序启动中、程序启动完成等。如下图:

    监听器如何获取?从源码中知道其实和初始化器(ApplicationContextInitializer)执行的是同一个方法,同样是从META-INF/spring.factories文件中获取。

    spring-boot-autoconfigurespring.factories文件中的值如下图:

    spring.factories文件不止一个,同样监听器也不止以上这些。

    作者demo中注入的一些监听器如下图:

    总结

    SpringApplication的构建都是为了run()方法启动做铺垫,构造方法中总共就有几行代码,最重要的部分就是设置应用类型、设置初始化器、设置监听器。

    「注意」:初始化器和这里的监听器都要放置在spring.factories文件中才能在这一步骤加载,否则不会生效,因此此时IOC容器还未创建,即使将其注入到IOC容器中也是不会生效的。

    作者简单的画了张执行流程图,仅供参考,如下:

    执行run()方法

    上面分析了SpringApplication的构建过程,一切都做好了铺垫,现在到了启动的过程了。

    作者根据源码将启动过程分为了「8步」,下面将会一一介绍。

    1. 获取、启动运行过程监听器

    SpringApplicationRunListener这个监听器和ApplicationListener不同,它是用来监听应用程序启动过程的,接口的各个方法含义如下:

    public interface SpringApplicationRunListener {

        // 在run()方法开始执行时,该方法就立即被调用,可用于在初始化最早期时做一些工作
        void starting();
        // 当environment构建完成,ApplicationContext创建之前,该方法被调用
        void environmentPrepared(ConfigurableEnvironment environment);
        // 当ApplicationContext构建完成时,该方法被调用
        void contextPrepared(ConfigurableApplicationContext context);
        // 在ApplicationContext完成加载,但没有被刷新前,该方法被调用
        void contextLoaded(ConfigurableApplicationContext context);
        // 在ApplicationContext刷新并启动后,CommandLineRunners和ApplicationRunner未被调用前,该方法被调用
        void started(ConfigurableApplicationContext context);
        // 在run()方法执行完成前该方法被调用
        void running(ConfigurableApplicationContext context);
        // 当应用运行出错时该方法被调用
        void failed(ConfigurableApplicationContext context, Throwable exception);
    }

    如何获取运行监听器?

    SpringApplication#run()方法中,源码如下:

    //从spring.factories中获取监听器
    SpringApplicationRunListeners listeners = getRunListeners(args);

    跟进getRunListeners()方法,其实还是调用了loadFactoryNames()方法从spring.factories文件中获取值,如下:

    org.springframework.boot.SpringApplicationRunListener=
    org.springframework.boot.context.event.EventPublishingRunListener

    最终注入的是EventPublishingRunListener这个实现类,创建实例过程肯定是通过反射了,因此我们看看它的构造方法,如下图:

    这个运行监听器内部有一个事件广播器(SimpleApplicationEventMulticaster),主要用来广播特定的事件(SpringApplicationEvent)来触发特定的监听器ApplicationListener

    EventPublishingRunListener中的每个方法用来触发SpringApplicationEvent中的不同子类。

    如何启动运行监听器?

    SpringApplication#run()方法中,源码如下:

    //执行starting()方法
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    执行SpringApplicationRunListenersstarting()方法,跟进去其实很简单,遍历执行上面获取的运行监听器,这里只有一个EventPublishingRunListener。因此执行的是它的starting()方法,源码如下图:

    上述源码中逻辑很简单,其实只是执行了multicastEvent()方法,广播了ApplicationStartingEvent事件。至于multicastEvent()内部方法感兴趣的可以看看,其实就是遍历ApplicationListener的实现类,找到监听ApplicationStartingEvent这个事件的监听器,执行onApplicationEvent()方法。

    总结

    这一步其实就是广播了ApplicationStartingEvent事件来触发监听这个事件的ApplicationListener

    因此如果自定义了ApplicationListener并且监听了ApplicationStartingEvent(应用程序开始启动)事件,则这个监听器将会被触发。

    2. 环境构建

    这一步主要用于加载系统配置以及用户的自定义配置(application.properties),源码如下,在run()方法中:

    ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

    prepareEnvironment方法内部广播了ApplicationEnvironmentPreparedEvent事件,源码如下图:

    环境构建这一步加载了系统环境配置、用户自定义配置并且广播了ApplicationEnvironmentPreparedEvent事件,触发监听器。

    3. 创建IOC容器

    源码在run()方法中,如下:

    context = createApplicationContext();

    跟进代码,真正执行的是ApplicationContextFactory方法,如下图:

    根据webApplicationType决定创建的类型,很显然,我这里的是servlet,因此创建的是AnnotationConfigServletWebServerApplicationContext

    这一步仅仅是创建了IOC容器,未有其他操作。

    4. IOC容器的前置处理

    这一步真是精华了,在刷新容器之前做准备,其中有一个非常关键的操作:将启动类注入容器,为后续的自动化配置奠定基础。源码如下:

    prepareContext(context, environment, listeners, applicationArguments,printedBanner);

    prepareContext()源码解析如下图,内容还是挺多的:

    从上图可以看出步骤很多,下面将会详细介绍几个重点的内容。

    调用初始化器

    SpringApplication构建过程中设置的初始化器,从spring.factories取值的。执行的流程很简单,遍历执行,源码如下图:

    将自定义的ApplicationContextInitializer放在META-INF/spring.factories中,在此时也是会被调用。

    加载启动类,注入容器

    这一步是将主启动类加载到IOC容器中,作为后续自动配置的入口。

    SpringApplication构建过程中将主启动类放置在primarySources这个集合中,此时的getAllSources()即是从其中取值,如下图:

    这里取出的就是主启动类,当然你的项目中可能不止一个,接下来就是将其加载到IOC容器中了,源码如下:

    load(context, sources.toArray(new Object[0]));

    跟着代码进去,其实主要逻辑都在BeanDefinitionLoader.load()方法,如下图:

    将主启动类加载到beanDefinitionMap,后续该启动类将作为开启自动配置化的入口,后续章节详细介绍。

    两次广播事件

    这一步涉及到了两次事件广播,分别是ApplicationContextInitializedEventApplicationPreparedEvent,对应的源码如下:

    listeners.contextPrepared(context);
    load(context, sources.toArray(new Object[0]));

    5. 刷新容器

    刷新容器完全是Spring的功能了,比如初始化资源,初始化上下文广播器等,这个就不再详细介绍,有兴趣可以看看Spring的源码。

    protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        //调用创建的容器applicationContext中的refresh()方法
        ((AbstractApplicationContext)applicationContext).refresh();
    }
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            /**
             * 刷新上下文环境
             */
            prepareRefresh();

            /**
             * 初始化BeanFactory,解析XML,相当于之前的XmlBeanFactory的操作,
             */
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            /**
             * 为上下文准备BeanFactory,即对BeanFactory的各种功能进行填充,如常用的注解@Autowired @Qualifier
             * 添加ApplicationContextAwareProcessor处理器
             * 在依赖注入忽略实现*Aware的接口,如EnvironmentAware、ApplicationEventPublisherAware等
             * 注册依赖,如一个bean的属性中含有ApplicationEventPublisher(beanFactory),则会将beanFactory的实例注入进去
             */
            prepareBeanFactory(beanFactory);

            try {
                /**
                 * 提供子类覆盖的额外处理,即子类处理自定义的BeanFactoryPostProcess
                 */
                postProcessBeanFactory(beanFactory);

                /**
                 * 激活各种BeanFactory处理器,包括BeanDefinitionRegistryBeanFactoryPostProcessor和普通的BeanFactoryPostProcessor
                 * 执行对应的postProcessBeanDefinitionRegistry方法 和  postProcessBeanFactory方法
                 */
                invokeBeanFactoryPostProcessors(beanFactory);

                /**
                 * 注册拦截Bean创建的Bean处理器,即注册BeanPostProcessor,不是BeanFactoryPostProcessor,注意两者的区别
                 * 注意,这里仅仅是注册,并不会执行对应的方法,将在bean的实例化时执行对应的方法
                 */
                registerBeanPostProcessors(beanFactory);

                /**
                 * 初始化上下文中的资源文件,如国际化文件的处理等
                 */
                initMessageSource();

                /**
                 * 初始化上下文事件广播器,并放入applicatioEventMulticaster,如ApplicationEventPublisher
                 */
                initApplicationEventMulticaster();

                /**
                 * 给子类扩展初始化其他Bean
                 */
                onRefresh();

                /**
                 * 在所有bean中查找listener bean,然后注册到广播器中
                 */
                registerListeners();

                /**
                 * 设置转换器
                 * 注册一个默认的属性值解析器
                 * 冻结所有的bean定义,说明注册的bean定义将不能被修改或进一步的处理
                 * 初始化剩余的非惰性的bean,即初始化非延迟加载的bean
                 */
                finishBeanFactoryInitialization(beanFactory);

                /**
                 * 通过spring的事件发布机制发布ContextRefreshedEvent事件,以保证对应的监听器做进一步的处理
                 * 即对那种在spring启动后需要处理的一些类,这些类实现了ApplicationListener<ContextRefreshedEvent>,
                 * 这里就是要触发这些类的执行(执行onApplicationEvent方法)
                 * 另外,spring的内置Event有ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、RequestHandleEvent
                 * 完成初始化,通知生命周期处理器lifeCycleProcessor刷新过程,同时发出ContextRefreshEvent通知其他人
                 */
                finishRefresh();
            }

            finally {
        
                resetCommonCaches();
            }
        }
    }

    6. IOC容器的后置处理

    一个扩展方法,源码如下:

    afterRefresh(context, applicationArguments);

    默认为空,如果有自定义需求可以重写,比如打印一些启动结束日志等。

    7. 发出结束执行的事件

    同样是EventPublishingRunListener这个监听器,广播ApplicationStartedEvent事件。

    但是这里广播事件和前几次不同,并不是广播给SpringApplication中的监听器(在构建过程中从spring.factories文件获取的监听器)。因此在IOC容器中注入的监听器(使用@Component等方式注入的)也能够生效。前面几个事件只有在spring.factories文件中设置的监听器才会生效。

    跟着代码进入,可以看到started()方法源码如下:

    这里并没有用事件广播器SimpleApplicationEventMulticaster广播事件,而是使用ConfigurableApplicationContext直接在IOC容器中发布事件。

    8. 执行Runners

    Spring Boot 提供了两种Runner让我们定制一些额外的操作,分别是CommandLineRunnerApplicationRunner,关于这两个的区别,后面文章详细介绍。

    调用的源码如下:

    callRunners(context, applicationArguments);

    跟进代码,其实真正调执行的是如下方法:

    逻辑很简单,从IOC容器中获取,遍历调用。

    总结

    Spring Boot 启动流程相对简单些,作者将其细分了以上八个步骤,希望能够帮助读者理解,流程图如下:

    总结

    Spring Boot启动流程就介绍到这里了,需要重点理解run()方法执行的八个步骤以及事件、初始化器、监听器等组件的执行时间点。

  • 相关阅读:
    腾讯云发布“创新成长快线”,首期向创业者赠送10亿分钟实时音视频时长
    Tencent Kona JDK11正式开源,腾讯大数据将持续贡献Java生态发展
    腾讯视频云勇夺云端视频转码大赛多项第一
    分享一些常用的开源博客社区网站
    分享些发表技术类文章的平台
    Linux之ps命令基本使用
    彻底卸载 Oracle11g r2 教程(亲测有效,已重装过)
    Oracle11g R2 安装教程(非常详细 )
    苹果CMS搭建影视网站教程
    JSON 基本使用
  • 原文地址:https://www.cnblogs.com/lgg20/p/14068688.html
Copyright © 2011-2022 走看看