博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【深入SpringBoot 1.3.5 第一章】Boot应用的启动流程
阅读量:4290 次
发布时间:2019-05-27

本文共 8748 字,大约阅读时间需要 29 分钟。

一、 快速创建一个Boot应用

使用maven

org.springframework.boot
spring-boot-starter-parent
1.3.5.RELEASE
org.springframework.boot
spring-boot-starter-web

应用程序结构

这里写图片描述

App.java作为启动类

@EnableAutoConfiguration@ComponentScan(basePackages = {
"com.jazz.controller"})@Configurationpublic class App {
public static void main(String[] args) { SpringApplication application = new SpringApplication(App.class); application.run(args); }}

* 第一个controller *

@RestControllerpublic class FirstController {
@RequestMapping("/test_1.service") public String queryTeacher() { String a="Hello World"; return a; }}

二、SpringApplication类概要

从上节可以看出:SpringApplication用来启动整个应用程序。

主要功能

1.根据classspath创建合适的ApplicationContext

2.注册CommandLinePropertySource生成命令行参数
3.刷新application context,载入所有bean
4.运行CommandLineRunner bean

使用方式

public static void main(String[] args) throws Exception {    //实例化--使用当前类作为source(带有@Configuration的配置类)    SpringApplication app = new SpringApplication(MyApplication.class);    // 启动应用    app.run(args) }

当然还有其他方式,不过类的内部都是根据这种方式转变而来。

配置(source)

Boot官方文档建议使用带有@Configuration的java类作为配置,而不是XML或其他配置方式,本系列文章都使用@Configuration。

三、SpringApplication 实例化

通过实例化 SpringApplication application = new SpringApplication(App.class);

主要调用了SpringApplication内部的initialize(..)方法,源码如下:

private void initialize(Object[] sources) {    //1    if (sources != null && sources.length > 0) {        this.sources.addAll(Arrays.asList(sources));    }    //2    this.webEnvironment = deduceWebEnvironment();     //3    setInitializers((Collection) getSpringFactoriesInstances(                ApplicationContextInitializer.class));     //4             setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));     //5    this.mainApplicationClass = deduceMainApplicationClass();}

流程分析

1.设置配置类

2.推断环境类型(标准或web)

3.实例化spring.factory文件中ApplicationContextInitializer对应的类

4.实例化spring.factory文件中ApplicationListener对应的类

5.推断主类


四、SpringApplication 执行

SpringBoot#run(String… args)

public ConfigurableApplicationContext run(String... args) {    //计时器,记录应用启动消耗时间    StopWatch stopWatch = new StopWatch();    ConfigurableApplicationContext context = null;    //用来设置java.awt.headless 属性是true 还是false,是J2SE的一种模式用于在缺少显示屏、键盘        //或者鼠标时的系统配置    configureHeadlessProperty();     //实例化Boot的SpringApplicationRunListeners    SpringApplicationRunListeners listeners = getRunListeners(args);     //执行所有SpringApplicationRunListener的start方法    listeners.started();     try {        ApplicationArguments applicationArguments                        = new DefaultApplicationArguments( args);        //5创建并刷新Spring上下文           context = createAndRefreshContext(listeners, applicationArguments);        //6执行刷新完上下文的流程        afterRefresh(context, applicationArguments);        //7执行所有SpringApplicationRunListener的finish方法。        listeners.finished(context, null);        stopWatch.stop();        if (this.logStartupInfo) {            new StartupInfoLogger(this.mainApplicationClass)                        .logStarted(getApplicationLog(), stopWatch);        }        return context;    }    catch (Throwable ex) {        handleRunFailure(context, listeners, ex);        throw new IllegalStateException(ex);    }}

createAndRefreshContext(..)方法

private ConfigurableApplicationContext createAndRefreshContext(            SpringApplicationRunListeners listeners,            ApplicationArguments applicationArguments) {    ConfigurableApplicationContext context;    // 5.1 创建并配置环境    ConfigurableEnvironment environment = getOrCreateEnvironment();    configureEnvironment(environment, applicationArguments.getSourceArgs());    listeners.environmentPrepared(environment);    if (isWebEnvironment(environment) && !this.webEnvironment) {        environment = convertToStandardEnvironment(environment);    }    if (this.bannerMode != Banner.Mode.OFF) {        printBanner(environment);    }    //5.2 Create, load, refresh 和run   ApplicationContext    context = createApplicationContext();    context.setEnvironment(environment);     postProcessApplicationContext(context);    applyInitializers(context);    listeners.contextPrepared(context);    if (this.logStartupInfo) {        logStartupInfo(context.getParent() == null);        logStartupProfileInfo(context);    }    //5.3 添加boot 特殊bean    context.getBeanFactory().registerSingleton("springApplicationArguments",                applicationArguments);    //5.4 载入 sources    Set sources = getSources();    Assert.notEmpty(sources, "Sources must not be empty");    load(context, sources.toArray(new Object[sources.size()]));    listeners.contextLoaded(context);    //5.5 刷新 context    refresh(context);    if (this.registerShutdownHook) {        try {            context.registerShutdownHook();        }        catch (AccessControlException ex) {            // Not allowed in some environments.        }    }    return context;}

综上两个方法,进行流程分析:

1.程序启动监听器
Boot程序开始启动,执行其开始监听器。这个方法真正会广播ApplicationStartedEvent事件给ApplicationListener。
执行以下监听器

【LoggingApplicationListener】

配置日志

【ClasspathLoggingApplicationListener】

debug模式则开始日志信息

【LiquibaseServiceLocatorApplicationListener】

存在Liquibase类则配置Liquibase

注意:有些监听器监听了多个事件,所以下文中还会出现。

2.创建并配置环境

根据命令行参数及其系统属性、系统环境、Servlet参数等创建环境,并配置profile等。
配置完成的环境对象如下图:
这里写图片描述

3.执行环境准备好监听器

广播ApplicationEnvironmentPreparedEvent事件。 这个事件可以用来修改环境中的属性源。

【ConfigFileApplicationListener】

这个类会加载spring.factory文件中的EnvironmentPostProcessor实例到list中,~*这是一个SpringBoot的拓展点。*
然后添加本身,因为它也是EnvironmentPostProcessor
然后依次执行这这些EnvironmentPostProcessor的postProcessEnvironment方法。如下
—SpringApplicationJsonEnvironmentPostProcessor-
把环境中spring.application.json的json值转化为MapPropertySource,并将此MapPropertySource添加到环境的属性源列表中
如果在程序启动之前添加了一个系统属性json

public static void main(String[] args) {    System.getProperties().put("spring.application.json", "{ \"name\": \"Web\"}");    SpringApplication application = new SpringApplication(App.class);      application.run(args);}

那么,此processor运行完之后,环境的属性源为:

这里写图片描述

—CloudFoundryVcapEnvironmentPostProcessor-

对CloudFoundry提供支持
—ConfigFileApplicationListener-
首先,向环境的属性源列表中添加一个Random属性源,如下
这里写图片描述

然后,从配置文件中提取相应的键值添加到名为applicationConfigurationProperties属性源中,这个属性源可以包含多个子属性源

这里写图片描述

【AnsiOutputApplicationListener】

解析环境中以下属性
spring.output.ansi.enabled 来决定是否颜色输出模式,值为枚举AnsiOutput.Enabled (DETECT, ALWAYS, NEVER)
spring.output.ansi.console-available (?测试未成功)是否控制台输出

【LoggingApplicationListener】

(?未测试)配置日志相关,输出配置相关信息
这里写图片描述

【BackgroundPreinitializer】

初始化一些信息

runSafely(new MessageConverterInitializer());runSafely(new MBeanFactoryInitializer());runSafely(new ValidationInitializer());

【DelegatingApplicationListener】

这个监听器监听所有事件,如果是ApplicationEnvironmentPreparedEvent,那么获取环境中key为context.listener.classes的监听器,后续对这些监听器广播相应的事件~拓展点

【FileEncodingApplicationListener】

对spring.mandatoryFileEncoding进行处理

4.打印Banner图形

感兴趣的同学可以实现Banner定制自己的Banner

或者指定banner.location,或者直接添加banner.txt。
~这里还是比较有趣的、^_^

5.创建ApplicationContext并添加上面的环境

如果没有在实例变量中添加ApplicationContext,那么根据是否是网络环境来决定创建添加以下两种ApplicationContext:

AnnotationConfigEmbeddedWebApplicationContextAnnotationConfigApplicationContext。

然后把环境设置给ApplicationContext

6.后处理 ApplicationContext

根据是否含有某些实例变量来向ApplicationContext添加:

beanNameGeneratorresourceLoaderresourceLoader

7.执行ApplicationContextInitializer

初始化时添加到实例变量的ApplicationContextInitializer,依次执行:
这里写图片描述
【DelegatingApplicationContextInitializer】
这个类取得环境中“context.initializer.classes”对应的多个ApplicationContextInitializer,用“,”分割,并依次执行~这里可以进行自己拓展,来修改ApplicationContext

【ContextIdApplicationContextInitializer】

根据
${vcap.application.instance_index:${spring.application.index:${server.port:${PORT:null}}}}
来设置applicationContext的ID

【ConfigurationWarningsApplicationContextInitializer】

为applicationContext添加一个BeanDefinitionRegistryPostProcessor:ConfigurationWarningsPostProcessor,来输出警告信息
例如@ComponentScan没有写明具体扫描的包名称

【ServerPortInfoApplicationContextInitializer】

添加监听器监听EmbeddedServletContainerInitializedEvent事件,用来设置嵌入的servlet容器端口号(?)
【AutoConfigurationReportLoggingInitializer】
向ApplicationContext添加AutoConfigurationReportListener用来输出自动配置报告

8.listeners.contextPrepared(context);

把之前用到的广播器添加到ApplicationContext中,没有发布任何事件

9输出Profile日志

10注册命令行参数bean

向ApplicationContext注册name为springApplicationArguments的Bean

11 载入配置sources

使用ApplicationContext.load载入资源

12 发布给ApplicationContext监听器ApplicationPreparedEvent

执行contextLoaded
【ConfigFileApplicationListener】
【LoggingApplicationListener】
【DelegatingApplicationListener】

13 refresh(context);

刷新上下文,如果是网络环境,那么进入
AnnotationConfigEmbeddedWebApplicationContext的世界

14 运行完成

主要是运行实现ApplicationRunner和CommandLineRunner接口的类

15 程序完成监听器

【ConfigFileApplicationListener】
【DelegatingApplicationListener】


后面我们将会从这12步分析~深入理解SpringBoot和Spring的各种原理。

我们发现spring的监听器贯穿在始终,所以下一节我们将会从监听器入手

你可能感兴趣的文章
聊聊CAS - 面试官最喜欢问的并发编程专题
查看>>
Spring Boot 中使用一个注解轻松将 List 转换为 Excel 下载
查看>>
高并发环境下,先操作数据库还是先操作缓存?
查看>>
MySQL Explain详解
查看>>
一直搞不清楚什么是读写分离,主从复制的原理,今天总算搞懂了
查看>>
消息队列 mq 必会面试题
查看>>
线程池的工作原理是啥?能手写一个线程池吗?
查看>>
Java程序内存的简单分析
查看>>
Javascript单例模式概念与实例
查看>>
SQL NULL 函数
查看>>
多例设计模式
查看>>
WebView的JavaScript与本地代码三种交互方式
查看>>
WebView的JavaScript与本地代码三种交互方式
查看>>
Android Studio里面配置Tesseract
查看>>
深入浅出JavaScript之this
查看>>
Android include标签的使用注意事项
查看>>
final成员变量和final局部变量
查看>>
Android数据加密之异或加密算法
查看>>
greenDao好的示例网址
查看>>
Android自定义控件--仿安全卫士中的一键加速
查看>>