SpringBoot- 启动流程分析

本贴最后更新于 2043 天前,其中的信息可能已经东海扬尘

从 SpringApplication.run();开始:

/**
 * Static helper that can be used to run a {@link 
 * SpringApplication} from the
 * specified source using default settings.
 * @param source the source to load
 * @param args the application arguments (usually 
 * passed from a Java main method)
 * @return the running {@link ApplicationContext}
 */
public static ConfigurableApplicationContext run(Object source, String... args) {
    return run(new Object[] { source }, args);
}```

进入重构run()方法

/**

  • Static helper that can be used to run a {@link
  • SpringApplication} from the
  • specified sources using default settings and
  • user supplied arguments.
  • @param sources the sources to load
  • @param args the application arguments (usually
  • passed from a Java main method)
  • @return the running {@link ApplicationContext}
    */
    public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return new SpringApplication(sources).run(args);
    }```

从这里可以看到首先创建了一个 SpringApplication 实例,然后在调用的其 run()方法。首先我们先去创建实例这一流程:


/**
 * Create a new {@link SpringApplication} 
 * instance. The application context will load
 * beans from the specified sources (see {@link 
 * SpringApplication class-level}
 * documentation for details. The instance can be 
 * customized before calling
 * {@link #run(String...)}.
 * @param sources the bean sources
 * @see #run(Object, String[])
 * @see #SpringApplication(ResourceLoader, 
 * Object...)
 */
public SpringApplication(Object... sources) {
    initialize(sources);
}```

可以看到其调用了initialize()方法

@SuppressWarnings({ "unchecked", "rawtypes" })

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

从面的代码可以看到初始化过程做了以下几件事情
this.webEnvironment = deduceWebEnvironment();
这一个方法决定创建的是一个 WEB 应用还是一个 SPRING 的标准 Standalone 应用。如果入方法可以看到其是怎么判断的:


private boolean deduceWebEnvironment() {
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return false;
        }
    }
    return true;
}```

可以看到是根据org.springframework.util.ClassUtils的静态方法去判断classpath里面是否有WEB\_ENVIRONMENT\_CLASSES包含的类,如果有都包含则返回true则表示启动一个WEB应用,否则返回false启动一个标准Spring的应用。然后通过代码:

private static final String[] WEB_ENVIRONMENT_CLASSES =

  { "javax.servlet.Servlet",
	"org.springframework.web.context.ConfigurableWebApplicationContext" };```

可以看到是否启动一个 WEB 应用就是取决于 classpath 下是否有 javax.servlet.Servlet 和
org.springframework.web.context.ConfigurableWebApplicationContext。然后进入下一个阶段:


setInitializers((Collection)  getSpringFactoriesInstances(ApplicationContextInitializer.class));

这个方法则是初始化 classpath 下的所有的可用的 ApplicationContextInitializer


setListeners((Collection)  getSpringFactoriesInstances(ApplicationListener.class));

这个方法则是初使化 classpath 下的所有的可用的 ApplicationListener


this.mainApplicationClass = deduceMainApplicationClass();

private Class<?> deduceMainApplicationClass() {
    try {
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {
            if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    }
    catch (ClassNotFoundException ex) {
        // Swallow and continue
    }
    return null;
}```

最后找出main方法的全类名并返回其实例并设置到SpringApplication的this.mainApplicationClass完成初始化。然后调用SpringApplication实例的run方法来启动应用,代码如下:

/**

  • Run the Spring application, creating and
  • refreshing a new
  • {@link ApplicationContext}.
  • @param args the application arguments (usually
  • passed from a Java main method)
  • @return a running {@link ApplicationContext}
    */
    public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.started();
    try {
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(
    args);
    ConfigurableEnvironment environment = prepareEnvironment(listeners,
    applicationArguments);
    Banner printedBanner = printBanner(environment);
    context = createApplicationContext();
    prepareContext(context, environment, listeners, applicationArguments,
    printedBanner);
    refreshContext(context);
    afterRefresh(context, applicationArguments);
    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);
    }
    }```

由于过程比较长,则把详细说明放入下一篇博客进行说明,这里只做简要的过程说明 run()方法的代码中:蓝色部分为加载 SpringApplicationRunListener 对整个容器的初始化过程进行监听,这里先不做解释,然后先观察剩下的几行的代码:


ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); Banner printedBanner = printBanner(environment); context = createApplicationContext(); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments);

首先是获取启动时传入参数 args 并初始化为 ApplicationArguments 对象
SpringApplication.run(Application.class, args);取这里传入值。
然后配置 SpringBoot 应用的环境:


ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

下面的则打印标志这个方法不说明,因为没有什么实质性作用,反应到控制台则是以下的效果如果确实想玩玩修改一下标志,那可以在项目的 classpath 下新建一个 banner.txt 文件,把想打印到控制台的数据放到文件中即可。比如:
src/main/resources/banner.txt 中加入以下内容:

_ _ _ _ __ __ _ _ _____ _ _ ___ | | | | | | | | \ \ / / | | | | | | | | | | / | | | | | | | | | \ \/ / | | | | | | | | | | / /| | | | | | | | | | } { | | | | | | _ | | | | / / | | | | | | | || | / /\ \ | || | | |__ | || | | | / / | | |_____| || \/ // \\ \/ || \/ || // |_|

那么启动的时候就可以看到些标识。
然后下面代码就是比较核心的:


context = createApplicationContext(); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments);

首先是 createApplicationContext()方法:


/**

 * Strategy method used to create the {@link 
 * ApplicationContext}. By default this
 * method will respect any explicitly set 
 * application context or application context
 * class before falling back to a suitable 
 * default.
 * @return the application context (not yet 
 * refreshed)
 * @see #setApplicationContextClass(Class)
 */
protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            contextClass = Class.forName(this.webEnvironment
                    ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}```

可以看出根据这前初始化过程初始化的this.webEnvironment来决定初始化一个什么容器。如果classpath下是否有javax.servlet.Servlet和  
org.springframework.web.context.ConfigurableWebApplicationContext类,  
则使用DEFAULT\_WEB\_CONTEXT\_CLASS初始化容器,如果不存在则用DEFAULT\_CONTEXT_CLASS初始化容器。

public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."

        + "annotation.AnnotationConfigApplicationContext";

/**
 * The class name of application context that 
 * will be used by default for web
 * environments.
 */
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
        + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";```

以上是代码指定了容器的类名,最后通过 Spring 的工具类初始化容器类 bean
BeanUtils.instantiate(contextClass);
完成容器的创建工作。然后执行以下的几个步骤完成整个容器的创建与启动以及 bean 的注入功能。


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

以下这一句代码是实现 spring-boot-starter-*的自动化配置的关键。包括 spring.factories 的加载以及自动化配置类的加载以及注入容器的功能可以参考《SpringBoot-自动配置源码解析


refreshContext(context); afterRefresh(context, applicationArguments);

至此通过 SpringBoot 启动的容器已经构造完成。这里忽略了启动流程中的收集各种 Listener,创建 Environment 及 Environment 的初始化的因为这些地方都是 SpringBoot 提供的各种扩展点,后面的博客会详细的说明各个扩展点的用处以及扩展的方式。

  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    941 引用 • 1458 回帖 • 152 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
Ethan
从前现在过去不在回来,红红落叶望眼尘世外! 苏州

推荐标签 标签

  • WebComponents

    Web Components 是 W3C 定义的标准,它给了前端开发者扩展浏览器标签的能力,可以方便地定制可复用组件,更好的进行模块化开发,解放了前端开发者的生产力。

    1 引用 • 24 关注
  • OnlyOffice
    4 引用 • 19 关注
  • MySQL

    MySQL 是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,目前属于 Oracle 公司。MySQL 是最流行的关系型数据库管理系统之一。

    675 引用 • 535 回帖
  • RESTful

    一种软件架构设计风格而不是标准,提供了一组设计原则和约束条件,主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

    30 引用 • 114 回帖
  • 深度学习

    深度学习(Deep Learning)是机器学习的分支,是一种试图使用包含复杂结构或由多重非线性变换构成的多个处理层对数据进行高层抽象的算法。

    40 引用 • 40 回帖 • 1 关注
  • 旅游

    希望你我能在旅途中找到人生的下一站。

    85 引用 • 895 回帖
  • Sublime

    Sublime Text 是一款可以用来写代码、写文章的文本编辑器。支持代码高亮、自动完成,还支持通过插件进行扩展。

    10 引用 • 5 回帖 • 1 关注
  • DevOps

    DevOps(Development 和 Operations 的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。

    40 引用 • 24 回帖 • 1 关注
  • SpaceVim

    SpaceVim 是一个社区驱动的模块化 vim/neovim 配置集合,以模块的方式组织管理插件以
    及相关配置,为不同的语言开发量身定制了相关的开发模块,该模块提供代码自动补全,
    语法检查、格式化、调试、REPL 等特性。用户仅需载入相关语言的模块即可得到一个开箱
    即用的 Vim-IDE。

    3 引用 • 31 回帖 • 74 关注
  • DNSPod

    DNSPod 建立于 2006 年 3 月份,是一款免费智能 DNS 产品。 DNSPod 可以为同时有电信、网通、教育网服务器的网站提供智能的解析,让电信用户访问电信的服务器,网通的用户访问网通的服务器,教育网的用户访问教育网的服务器,达到互联互通的效果。

    6 引用 • 26 回帖 • 521 关注
  • jQuery

    jQuery 是一套跨浏览器的 JavaScript 库,强化 HTML 与 JavaScript 之间的操作。由 John Resig 在 2006 年 1 月的 BarCamp NYC 上释出第一个版本。全球约有 28% 的网站使用 jQuery,是非常受欢迎的 JavaScript 库。

    63 引用 • 134 回帖 • 741 关注
  • MongoDB

    MongoDB(来自于英文单词“Humongous”,中文含义为“庞大”)是一个基于分布式文件存储的数据库,由 C++ 语言编写。旨在为应用提供可扩展的高性能数据存储解决方案。MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似 JSON 的 BSON 格式,因此可以存储比较复杂的数据类型。

    90 引用 • 59 回帖 • 1 关注
  • TensorFlow

    TensorFlow 是一个采用数据流图(data flow graphs),用于数值计算的开源软件库。节点(Nodes)在图中表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组,即张量(tensor)。

    20 引用 • 19 回帖 • 1 关注
  • Unity

    Unity 是由 Unity Technologies 开发的一个让开发者可以轻松创建诸如 2D、3D 多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

    25 引用 • 7 回帖 • 245 关注
  • MyBatis

    MyBatis 本是 Apache 软件基金会 的一个开源项目 iBatis,2010 年这个项目由 Apache 软件基金会迁移到了 google code,并且改名为 MyBatis ,2013 年 11 月再次迁移到了 GitHub。

    170 引用 • 414 回帖 • 426 关注
  • Chrome

    Chrome 又称 Google 浏览器,是一个由谷歌公司开发的网页浏览器。该浏览器是基于其他开源软件所编写,包括 WebKit,目标是提升稳定性、速度和安全性,并创造出简单且有效率的使用者界面。

    60 引用 • 287 回帖
  • JWT

    JWT(JSON Web Token)是一种用于双方之间传递信息的简洁的、安全的表述性声明规范。JWT 作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以 JSON 的形式安全的传递信息。

    20 引用 • 15 回帖 • 18 关注
  • 微信

    腾讯公司 2011 年 1 月 21 日推出的一款手机通讯软件。用户可以通过摇一摇、搜索号码、扫描二维码等添加好友和关注公众平台,同时可以将自己看到的精彩内容分享到微信朋友圈。

    129 引用 • 793 回帖
  • Ruby

    Ruby 是一种开源的面向对象程序设计的服务器端脚本语言,在 20 世纪 90 年代中期由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)设计并开发。在 Ruby 社区,松本也被称为马茨(Matz)。

    7 引用 • 31 回帖 • 177 关注
  • 运维

    互联网运维工作,以服务为中心,以稳定、安全、高效为三个基本点,确保公司的互联网业务能够 7×24 小时为用户提供高质量的服务。

    148 引用 • 257 回帖 • 1 关注
  • RabbitMQ

    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种语言客户端,如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    49 引用 • 60 回帖 • 394 关注
  • OpenStack

    OpenStack 是一个云操作系统,通过数据中心可控制大型的计算、存储、网络等资源池。所有的管理通过前端界面管理员就可以完成,同样也可以通过 Web 接口让最终用户部署资源。

    10 引用 • 8 关注
  • Sandbox

    如果帖子标签含有 Sandbox ,则该帖子会被视为“测试帖”,主要用于测试社区功能,排查 bug 等,该标签下内容不定期进行清理。

    370 引用 • 1215 回帖 • 582 关注
  • 倾城之链
    23 引用 • 66 回帖 • 103 关注
  • GAE

    Google App Engine(GAE)是 Google 管理的数据中心中用于 WEB 应用程序的开发和托管的平台。2008 年 4 月 发布第一个测试版本。目前支持 Python、Java 和 Go 开发部署。全球已有数十万的开发者在其上开发了众多的应用。

    14 引用 • 42 回帖 • 687 关注
  • Elasticsearch

    Elasticsearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于 RESTful 接口。Elasticsearch 是用 Java 开发的,并作为 Apache 许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

    116 引用 • 99 回帖 • 266 关注
  • 音乐

    你听到信仰的声音了么?

    59 引用 • 509 回帖