美文网首页
SpringBoot应用启动流程分析

SpringBoot应用启动流程分析

作者: anyoptional | 来源:发表于2022-01-10 17:57 被阅读0次

      SpringApplicationSpringBoot提供的帮助应用程序启动的引导类,它负责

    1. 创建合适的ApplicationContext
    2. 将命令行参数融入Environment抽象
    3. 刷新ApplicationContext,初始化所有的singleton bean
    4. 触发所有的CommandLineRunner回调

    本文中的源码分析基于spring-boot-2.1.5.RELEASE,各位看官还请注意下版本。

    构造函数

      大多数时候,我们使用SpringApplication#run(...)工厂方法来创建SpringApplication实例,其实本质上都是对构造函数的调用。

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        // 资源加载器,一般为空,因为 ApplicationContext 本身实现了 ResourceLoader 接口
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        // 主配置类,一般情况下是标注了 @SpringBootApplication 注解的类
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        // 根据 jar 包的引入情况判定应用类型
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        // 获取 spring.factories 中配置的 ApplicationContextInitializer
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        // 获取 spring.factories 中配置的 ApplicationListener
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 获取 main 函数入口类
        this.mainApplicationClass = deduceMainApplicationClass();
    }
    

    primarySources是创建ApplicationContext时传递给它的主配置类,一般情况下是标注了@SpringBootApplication注解的类;webApplicationType则根据类路径下jar包的引入情况来进行推断,比如引入了spring-boot-starter-web就会被推断为WebApplicationType.SERVLET#setInitializers(...)#setListeners(...)则使用之前讲解过的Spring SPI机制来获取对应的实现;最后mainApplicationClass则对应调用栈中声明了main函数的类。

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
        return getSpringFactoriesInstances(type, new Class<?>[] {});
    }
    
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = getClassLoader();
        // 使用 Set 防止重复
        Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        // 反射创建实例,其中 parameterTypes 是构造函数形参类型,args 是实参
        // 具体代码基本上就是 clazz.getDeclaredConstructor(parameterTypes).newInstance(args)
        // 节约篇幅,就不放出来了
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        // 基于 @Order/Ordered 排序
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }
    

    这里对Spring SPI机制的使用是非常直接的,同时也告诉了我们如何注册自定义的ApplicationContextInitializerApplicationListener,比如在自定义starterMETA-INF/spring.factories中加入:

    org.springframework.context.ApplicationListener=example.MyApplicationListener
    

    就完成了对MyApplicationListener的注册。

    启动过程

    public ConfigurableApplicationContext run(String... args) {
        // 用于记录启动时长
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        // 容纳 SPI SpringBootExceptionReporter 的实现
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        // 服务端程序一般都是没有UI的
        configureHeadlessProperty();
        // 获取 SPI SpringApplicationRunListener 的实现
        SpringApplicationRunListeners listeners = getRunListeners(args);
        // 回调 SpringApplicationRunListener#starting(...),此时 #run(...) 刚开始运行
        listeners.starting();
    
        try {
            // 封装命令行参数
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 1. 创建 Environment 并进行配置
            // 2. 回调 SpringApplicationRunListener#environmentPrepared(...),此时 Environment 已创建
            // 3. 为 ConfigurationProperties binding 做准备
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            // 打印 banner
            Banner printedBanner = printBanner(environment);
            // 根据 webApplicationType 来创建 ApplicationContext
            context = createApplicationContext();
            // 获取 SPI SpringBootExceptionReporter 的实现
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            // 1. 配置 ApplicationContext
            // 2. 运行 ApplicationInitializer(s)
            // 3. 回调 SpringApplicationRunListener#contextPrepared(...),
            //    此时 ApplicationContext 已经创建,但 BeanDefinition 还未加载
            // 4. 加载 Bean 定义信息,但不刷新容器,此时已经有了创建 Bean 的蓝图 BeanDefinition
            // 5. 回调 SpringApplicationRunListener#contextLoaded(...),此时 BeanDefinition 已经
            //    悉数加载完毕,但容器未刷新,对应的 bean 实例还未创建
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            // 刷新容器,初始化所有 non-lazy-init 的 bean
            refreshContext(context);
            // 钩子函数
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            // 回调 SpringApplicationRunListener#started(...),此时容器已刷新,bean 也初始化完毕
            listeners.started(context);
            // 回调容器中所有的 ApplicationRunner 和 CommandLineRunner
            callRunners(context, applicationArguments);
        } catch (Throwable ex) {
            // 1. 回调 SpringApplicationRunListener#failed(...)
            // 2. 使用 SpringBootExceptionReporter 来处理异常
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }
    
        try {
            // 回调 SpringApplicationRunListener#running(...),此时不仅容器已刷新,各种 *Runner 也已回调
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
    
        return context;
    }
    

    #run(...)方法虽然略长,逻辑却是很清晰的:

    1. 创建Environment并进行配置
    2. 创建ApplicationContext并进行配置
    3. 加载配置类并解析出对应的BeanDefinition
    4. 刷新容器,根据BeanDefinition创建Bean
    5. 回调*Runner接口

    SpringApplicationRunListener则提供了对#run(...)过程中关键节点的回调,默认注册有EventPublishingRunListener——它使用ApplicationEventMulticaster发出对应的事件。其中比较重要的有ApplicationEnvironmentPreparedEventApplicationReadyEventSpringCloud中的很多特性,比如Bootstrap Context的创建和Environment的动态刷新就是基于这些事件来触发的。SpringBootExceptionReporter则提供了对#run(...)过程中异常的处理,默认注册的FailureAnalyzers会打印异常信息并提供一个解决方案,类似下面的输出相信大家都不会陌生(笑)。

    ***************************
    APPLICATION FAILED TO START
    ***************************
    
    Description:
    
    Field restTemplate in example.TestController required a single bean, but 2 were found:
        - restTemplate1: defined by method 'restTemplate1' in class path resource [example/Config.class]
        - restTemplate2: defined by method 'restTemplate2' in class path resource [example/Config.class]
    
    Action:
    
    Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
    

    创建Environment并进行配置

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                                                      ApplicationArguments applicationArguments) {
        // 根据 webApplicationType 来创建 Environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        // 1. 添加 ConversionService 用于类型转换
        // 2. 添加 defaultProperties 和 commandLineArgs 两个 PropertySource
        // 3. 合并 additionalProfiles 到 activeProfiles
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        // 回调,发出 ApplicationEnvironmentPreparedEvent 事件
        listeners.environmentPrepared(environment);
        // 将 spring.main 前缀的配置项应用到 SpringApplication,比如 allowBeanDefinitionOverriding
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {
            environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                    deduceEnvironmentClass());
        }
        // 将 PropertySource 转换成 ConfigurationPropertySource
        // 这一步是为了支持 ConfigurationProperties binding
        ConfigurationPropertySources.attach(environment);
        return environment;
    }
    

    创建ApplicationContext并进行配置

    protected ConfigurableApplicationContext createApplicationContext() {
       Class<?> contextClass = this.applicationContextClass;
       if (contextClass == null) {
          try {
             switch (this.webApplicationType) {
             case SERVLET:
                contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                break;
             case REACTIVE:
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
             default:
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
             }
          } catch (ClassNotFoundException ex) {
             throw new IllegalStateException(
                   "Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
                   ex);
          }
       }
       return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
    

    根据webApplicationType创建对应的ApplicationContext,比如引入了spring-boot-starter-web就会创建AnnotationConfigServletWebServerApplicationContext,进而启动内嵌的Tomcat容器,这部分内容之前已经聊过了,大家可以翻翻看。

    加载配置类并解析出对应的BeanDefinition

    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
          SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        // 设置使用的 Environment
       context.setEnvironment(environment);
        // 设置 ResourceLoader、ClassLoader和ConversionService
       postProcessApplicationContext(context);
        // 应用上前面加载好的 ApplicationContextInitializer
       applyInitializers(context);
        // 回调,发出 ApplicationPreparedEvent 事件
       listeners.contextPrepared(context);
       if (this.logStartupInfo) {
          logStartupInfo(context.getParent() == null);
          logStartupProfileInfo(context);
       }
       // 单独注册一下几个比较特殊的 Bean
       ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
       beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
       if (printedBanner != null) {
          beanFactory.registerSingleton("springBootBanner", printedBanner);
       }
        // 将 allowBeanDefinitionOverriding 配置项应用到 ApplicationContext 底层的 BeanFactory
       if (beanFactory instanceof DefaultListableBeanFactory) {
          ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
       }
       // primarySources 和以编程方式加入的其它配置源
       Set<Object> sources = getAllSources();
       Assert.notEmpty(sources, "Sources must not be empty");
        // 从以上配置源中解析出 BeanDefinition
        // 这部分属于 spring-context 包的内容了,无非是使用 AnnotatedBeanDefinitionReader
        // ClassPathBeanDefinitionScanner 等组件来加载并解析
       load(context, sources.toArray(new Object[0]));
        // 回调,发出 ApplicationPreparedEvent 事件
       listeners.contextLoaded(context);
    }
    

    大家很熟悉的自动装配就发生在这一阶段,@EnableAutoConfiguration注解一般是标注在primarySource上的,看一下它的定义:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    
       String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
       /**
        * 待排除的自动配置类
        */
       Class<?>[] exclude() default {};
        
       /**
        * 待排除的自动配置类名
        */
       String[] excludeName() default {};
    }
    

    它通过@Import注解向容器中导入了一个ImportSelector接口的实现——AutoConfigurationImportSelector,并在这里实现了自动装配逻辑。鉴于这部分内容比较多,咱们下次可以展开聊聊。

    刷新容器,根据BeanDefinition创建Bean

    protected void refresh(ApplicationContext applicationContext) {
       Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
       ((AbstractApplicationContext) applicationContext).refresh();
    }
    

    刷新容器直接调用ApplicationContext#refresh()方法即可,这将导致所有non-lazy-initsingleton bean得到初始化。

    回调*Runner接口

    private void callRunners(ApplicationContext context, ApplicationArguments args) {
       List<Object> runners = new ArrayList<>();
        // 获取容器中所有类型为 ApplicationRunner 的 bean
       runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        // 获取容器中所有类型为 CommandLineRunner 的 bean
       runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        // 基于 @Order/Ordered 排序 
       AnnotationAwareOrderComparator.sort(runners);
        // 接着就是逐个调用了
       for (Object runner : new LinkedHashSet<>(runners)) {
          if (runner instanceof ApplicationRunner) {
             callRunner((ApplicationRunner) runner, args);
          }
          if (runner instanceof CommandLineRunner) {
             callRunner((CommandLineRunner) runner, args);
          }
       }
    }
    

    这部分代码是自解释的,不多提了。

    结语

      今天我们一起分析了SpringBoot应用的启动流程,也仅仅是主体的启动流程。Spring本身是完全开放、极易扩展的,大家也看到了伴随着应用启动的各种回调,众多的SpringBoot特性都是在这些扩展点中得到实现的,这也是接下来的文章要分析的内容。

    相关文章

      网友评论

          本文标题:SpringBoot应用启动流程分析

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