关于springboot:SpringBoot启动流程原理解析二

6次阅读

共计 11917 个字符,预计需要花费 30 分钟才能阅读完成。

在上一章咱们剖析了 SpingBoot 启动流程中实例化 SpingApplication 的过程。

return new SpringApplication(primarySources).run(args);
这篇文章咱么说下 run() 办法开始之后都做了那些事件。

持续往下跟着源码进入到 run() 这个是比拟外围的一个办法了

    public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();
                // 计时器开始
        stopWatch.start();
                // 创立启动上下文对象
        DefaultBootstrapContext bootstrapContext = createBootstrapContext();
        ConfigurableApplicationContext context = null;
                // 配置 Handless 模式,是在短少显示屏、键盘或鼠标时的系统配置
                // 默认为 true
        configureHeadlessProperty();
                // 获取并启动监听器
        SpringApplicationRunListeners listeners = getRunListeners(args);
                // 启动监听器
        listeners.starting(bootstrapContext, this.mainApplicationClass);
        try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                        // 筹备环境
            ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
                        // 疏忽配置的 bean
            configureIgnoreBeanInfo(environment);
                        // 打印 banner, 就是启动的时候在控制台的 spring 图案
            Banner printedBanner = printBanner(environment);
                        // 创立容器
            context = createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
                        // 筹备利用上下文(spring 容器前置解决)
            prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
                        // 刷新容器
            refreshContext(context);
                        // 刷新容器后的扩大接口(spring 容器后置解决)
            afterRefresh(context, applicationArguments);
                        // 完结计时器并打印,这就是咱们启动后 console 的显示的工夫
            stopWatch.stop();
            if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
                        // 公布监听利用上下文启动实现(收回启动完结事件)listeners.started(context);
                        // 执行 runner
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
                        // 异样解决,如果 run 过程产生异样
            handleRunFailure(context, ex, listeners);
            throw new IllegalStateException(ex);
        }

        try {
                        // 监听利用上下文运行中
            listeners.running(context);
        }
        catch (Throwable ex) {handleRunFailure(context, ex, null);
            throw new IllegalStateException(ex);
        }
                // 返回最终构建的容器对象
        return context;
    }

接下来就对下面的关键步骤一一解释

1. 获取所有的监听器

这段代码咱们比拟相熟了,上一篇咱么具体介绍过,它的次要作用就是去META-INFO/spring.factories 中加载配置 SpringApplicationRunListener 的监听器如下

显然只有一个事件公布监听器类,拿到了 EventPublishingRunListener 启动事件公布监听器,下一步就是开始启动了listeners.starting(); 咱们往下跟源码看

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        this.initialMulticaster
                .multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
    }

启动的时候实际上是又创立了一个 ApplicationStartingEvent 对象,其实就是监听利用启动事件。
其中 initialMulticaster是一个SimpleApplicationEventMuticaster

    public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
        // 获取线程池,为每个监听事件创立一个线程
        Executor executor = this.getTaskExecutor();
        // 依据 ApplicationStartingEvent 事件类型找到对应的监听器,并迭代 
        Iterator var5 = this.getApplicationListeners(event, type).iterator();

        while(var5.hasNext()) {ApplicationListener<?> listener = (ApplicationListener)var5.next();
            if (executor != null) {
                // 
                executor.execute(() -> {this.invokeListener(listener, event);
                });
            } else {this.invokeListener(listener, event);
            }
        }

    }

2. 筹备环境

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

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
            DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
        // Create and configure the environment
                // 这里咱们退出了 web 依赖所以是一个 servlet 容器
        ConfigurableEnvironment environment = getOrCreateEnvironment();
                // 配置环境
        configureEnvironment(environment, applicationArguments.getSourceArgs());
                // 环境筹备实现
        ConfigurationPropertySources.attach(environment);
        listeners.environmentPrepared(bootstrapContext, environment);
        DefaultPropertiesPropertySource.moveToEnd(environment);
        configureAdditionalProfiles(environment);
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                    deduceEnvironmentClass());
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

因为咱们是增加了 web 的依赖 getOrCreateEnvironment()返回的是一个standardservletEnviroment 规范的 servlet 环境

2.1 配置环境

    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {if (this.addConversionService) {
                        // 嵌入式的转换器
            ConversionService conversionService = ApplicationConversionService.getSharedInstance();
            environment.setConversionService((ConfigurableConversionService) conversionService);
        }
                // 配置属性资源文件
        configurePropertySources(environment, args);
                // 配置文件
        configureProfiles(environment, args);
    }

利用嵌入的转换器ApplicationConversionService

    public static void configure(FormatterRegistry registry) {DefaultConversionService.addDefaultConverters(registry);
        DefaultFormattingConversionService.addDefaultFormatters(registry);
                // 格局转换
        addApplicationFormatters(registry);
                // 类型转换
        addApplicationConverters(registry);
    }

        =============== 格局转换 =================
    public static void addApplicationFormatters(FormatterRegistry registry) {registry.addFormatter(new CharArrayFormatter());
        registry.addFormatter(new InetAddressFormatter());
        registry.addFormatter(new IsoOffsetFormatter());
    }


        ======================== 类型转换 ===================
    public static void addApplicationConverters(ConverterRegistry registry) {addDelimitedStringConverters(registry);
        registry.addConverter(new StringToDurationConverter());
        registry.addConverter(new DurationToStringConverter());
        registry.addConverter(new NumberToDurationConverter());
        registry.addConverter(new DurationToNumberConverter());
        registry.addConverter(new StringToPeriodConverter());
        registry.addConverter(new PeriodToStringConverter());
        registry.addConverter(new NumberToPeriodConverter());
        registry.addConverter(new StringToDataSizeConverter());
        registry.addConverter(new NumberToDataSizeConverter());
        registry.addConverter(new StringToFileConverter());
        registry.addConverter(new InputStreamSourceToByteArrayConverter());
        registry.addConverterFactory(new LenientStringToEnumConverterFactory());
        registry.addConverterFactory(new LenientBooleanToEnumConverterFactory());
        if (registry instanceof ConversionService) {addApplicationConverters(registry, (ConversionService) registry);
        }
    }
    

2.2 环境筹备实现

同下面启动监听事件,这次的环境筹备也是同样的代码

    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
            ConfigurableEnvironment environment) {
        this.initialMulticaster.multicastEvent(
                                // 创立一个应用环境筹备事件对象
                new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
    }

debug 进去之后代码跟 AppLicationstrigevent 事件对象是一样的。不再赘述。
不过这里是 7 个监听器对象

3. 配置疏忽的 bean

configureIgnoreBeanInfo(environment);

4. 打印 banner

这是 SpringBoot 默认的启动时的图标
Banner printedBanner = printBanner(environment);

这个是能够自定义的,也能够是图篇或是文本文件中的图形

5. 创立容器

紧接着上一篇,接下来就是创立容器

    protected ConfigurableApplicationContext createApplicationContext() {return this.applicationContextFactory.create(this.webApplicationType);
    }

6. 筹备利用上下文

    private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
                // 设置环境参数
        context.setEnvironment(environment);
                // 设置后处理利用上下文
        postProcessApplicationContext(context);
                // 把从 spring.factories 中加载的 org.springframework.bt.context.ConfigurationwarningsApplicationContextIitiaLizer,进行初始化操作
        applyInitializers(context);
                //EventPubLishingRunListener 公布利用上下文事件 
        listeners.contextPrepared(context);
                // 打印启动日志
        bootstrapContext.close(context);
        if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }
        // Add boot specific singleton beans
                
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
                         // 注册一个字是 springAppLicationArguments 单例的 bean 
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
        if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory) beanFactory)
                    .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
        if (this.lazyInitialization) {context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }
        // Load the sources 获取所有资源
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
                // 创立 BeanDefinitionLoader 加载器加载注册所有的资源 
        load(context, sources.toArray(new Object[0]));
                // 同之前,公布利用上下文 加载事件 
        listeners.contextLoaded(context);
    }

7. 刷新利用上下文

刷新利用上下文就进入了 spring 的源码了

    public void refresh() throws BeansException, IllegalStateException {synchronized(this.startupShutdownMonitor) {StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
            // Prepare this context for refreshing.
            // 筹备刷新上下文
            this.prepareRefresh();
            // Tetl the subclass to refresh the internal bean facto
            // 告诉子类刷新外部工厂
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            // Prepare the bean factory for use in this context.
            // 筹备 Bean 工厂
            this.prepareBeanFactory(beanFactory);

            try {
                 // Allows post-processing of the bean factory in contex t subc lasses.
                // 容许在上下文子类中对 bean 工厂进行后处理。// Invoke factory processors registered as beans in the context,
                this.postProcessBeanFactory(beanFactory);
                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                this.invokeBeanFactoryPostProcessors(beanFactory);
                // 注册后置处理器。this.registerBeanPostProcessors(beanFactory);
                beanPostProcess.end();
                // 初始化信息源
                this.initMessageSource();
                // 初始化上下文事件公布器
                this.initApplicationEventMulticaster();
                // 初始化其余自定义 bean 
                this.onRefresh();
                // 注册监听器
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                // 实现刷新,清缓存,初始化生命周期,事件公布等
                this.finishRefresh();} catch (BeansException var10) {if (this.logger.isWarnEnabled()) {this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt:" + var10);
                }
                // 销毁 bean 
                this.destroyBeans();
                // Reset 'active'flag.
                this.cancelRefresh(var10);
                throw var10;
            } finally {this.resetCommonCaches();
                contextRefresh.end();}

        }
    }

刷新的代码有点深,也是在这时创立了 Tomcat 对象,这也是SpringBoot 一键启动web 工程的要害

创立了 Tomcat 对象,并设置参数

    @Override
    public WebServer getWebServer(ServletContextInitializer... initializers) {if (this.disableMBeanRegistry) {Registry.disableRegistry();
        }
        Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        connector.setThrowOnFailure(true);
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {tomcat.getService().addConnector(additionalConnector);
        }
        prepareContext(tomcat.getHost(), initializers); 
                // 返回 TomcatWebServer 服务
        return getTomcatWebServer(tomcat);
    }

8. 刷新后处理

afterReftesh(); // 是个一空实现,留着前期扩大

    /**
     * Called after the context has been refreshed.
     * @param context the application context
     * @param args the application arguments
     */
    protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {}

9. 公布监听利用启动事件

    @Override
    public void started(ConfigurableApplicationContext context) {context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
        AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
    }

这里是调用 context.publishEvent()办法,公布利用启动事件 ApplicationStartedEvent.

10. 执行 Runner

获取所有的 ApplicationRuner 和 CommandLineRunner 来初始化一些参数,callRuner(是一个回调函数)

    private void callRunners(ApplicationContext context, ApplicationArguments args) {List<Object> runners = new ArrayList<>();
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        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);
            }
        }
    }

11. 公布上下文筹备实现的事件

listeners.running(context);

    @Override
    public void running(ConfigurableApplicationContext context) {context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
        AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
    }

这段代码看上去似成相识,后面有很多相似的代码,不同的是这里上下文筹备实现之后公布了一个 ApplicationReadyEvent 事件,申明一下利用上下文筹备实现。

小结

这篇次要是介绍了 SpringBoot 启动过程中 run() 的这个过程。从中咱们也能够发现一些十分好的编码习惯,大家能够在日常的工作中从模拟到内化,缓缓变成本人的货色。

如果你最近正在学习 SpringBoot,能够参考我的集体网站:程序员波特,目前曾经更新了 SpringBoot2.x,SpringBoot3.x 的系列学习教程,心愿能够帮忙到你。

正文完
 0