Spring Boot启动过程源码分析

由于简书的markdown不支持目录结构,更好的阅读体验可以查看对应的个人blog: https://buaazhangyk.github.io/2018/07/02/spring-boot-1/

0. 前言

开始之前,举一个使用spring boot启动web应用的代码示例:

@SpringBootApplication
public class SpringbootPracticeApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootPracticeApplication.class, args);
    }
}

通过上述代码可以看到,Spring Boot 中应用启动的核心入口在SpringApplication这个类中完成。继续查看在SpringApplicationrun方法内部执行中,主要分为两步:1.初始化创建一个SpringApplication,2.然后执行run(String... args)方法对Application进行启动。

public staticConfigurableApplicationContext run(Class[] primarySources, String[] args) {
    return newSpringApplication(primarySources).run(args);
}

由此,本文对spring boot启动过程的分析也会从这两部分进行展开。1)SpringApplication的初始化部分; 2)SpringApplication的run执行部分。

1. SpringApplication的初始化

1.1 SpringApplication类私有变量

下边的图简单描述了SpringApplication类所包含的一些私有变量。后续会结合类的构造函数来分析其中的重要私有变量及其左右。

image.png

1.2 SpringApplication构造函数

结合Class的构造函数来重点看一下下面几个变量的作用以及如何进行初始化的。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = deduceWebApplicationType();
    setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

1.2.1 ResourceLoader

表示Spring中用来加载资源的资源加载器。

1.2.2 webApplicationType

代表这个SpringApplication的类型,主要包括三个类型:NONE / SERVLET / REACTIVE

NONE: The application should not run as a web application and should not start an embedded web server.

SERVLET: The application should run as a servlet-based web application and should start anembedded servlet
web server.

REACTIVE: The application should run as a reactive web application and should start anembedded reactive web server.

SpringBoot是怎么知道究竟是那种类型的ApplicationType的呢?实现的代码在方法deduceWebApplicationType()中。

    private WebApplicationType deduceWebApplicationType() {
        if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
                && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }

在这段代码里一个核心的方法调用是 ClassUtils.isPresent(String className,@NullableClassLoader classLoader),
这个方法是判断参数中的className是否存在并可以加载成功。由此可见WebApplicationType类型的判断取决于引入的jar包。
其中,
REACTIVE_WEB_ENVIRONMENT_CLASS 对应的类为 org.springframework.web.reactive
.DispatcherHandler
, 对应的package是spring-webflux

MVC_WEB_ENVIRONMENT_CLASS对应的类为org.springframework.web.servlet.DispatcherServlet , 对应的package是 spring-webmvc

WEB_ENVIRONMENT_CLASSES 对应的类为{"javax.servlet.Servlet","org.springframework.web.context
.ConfigurableWebApplicationContext” }
,对应的package是servlet-apispring-web

1.2.3 ApplicationContextInitializer

用来在对ApplicationContext进行refresh操作之前对Application context进行一些初始化操作。

Callback interface for initializing a Spring {@ConfigurableApplicationContext} prior to being
{@ConfigurableApplicationContext#refresh()} refreshed.
Typically used within web applications that require some programmatic initialization of the application context.
For example, registering property sources or activating profiles against the
{@ConfigurableApplicationContext#getEnvironment()}
context's environment. See {@ContextLoader} and {@FrameworkServlet} support for declaring a "contextInitializerClasses" context-param and init-param, respectively.

通过查看代码,我们可以看到ApplicationContextInitializer的获取是通过调用 getSpringFactoriesInstances(Class<T> type)
方法得到的,这个方法实际是去寻找指定Class类型的类并将其实例化返回。那具体是从哪里找呢? 会在后边的小节单独分析一下

1.2.4 ApplicationListener

基于观察者模式的Application的事件监听器。将ApplicationListener注册到ApplicationContext中,当有对应事件发生时,监听器会被调用

Interface to be implemented by application event listeners.
Based on the standard {@java.util.EventListener} interface for the Observer design pattern.
As of Spring 3.0, an ApplicationListener can generically declare the event type that it is interested in. When
registered with a Spring ApplicationContext, events will be filtered accordingly, with the listener getting invoked for matching event objects only.

与获取ApplicationContextInitializer的过程一直, ApplicationListener的获取也是通过调用getSpringFactoriesInstances(Class<T> type)实现。

1.2.5 mainApplicationClass

启动应用程序的main class.通过分析当前的程序堆栈信息获取。

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;
}

1.2.6 关于getSpringFactoriesInstances

2.SpringApplication的run

2.1 整体流程

run方法是SpringApplication的核心方法,在这个方法内部完成了 系统属性的注入,Runner的执行,
创建、准备以及refresh整个ApplicationContext 核心流程。大致可以将整个run方法归纳分解成6个步骤。

[图片上传失败...(image-4fd544-1531105646640)]

2.2 细节分析

下边对这六个步骤进行详细的分析和解读。


image.png

2.2.1 SpringApplicationRunListeners

SpringApplicationRunListener是事件监听器,用来监听SpringApplication
的启动过程,监听到事件发生时进行一些回调操作。通过下边的代码可以看到获取的核心方法是getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)完成的

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
        SpringApplicationRunListener.class, types, this, args));
}

2.2.2 Prepare Environment

配置Application的环境,主要是一些property,这些属性会在创建ApplicationContext以及Referesh的时候起到左右。

private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    if (this.webApplicationType == WebApplicationType.NONE) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertToStandardEnvironmentIfNecessary(environment);
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

核心流程:

  1. 创建一个ConfigurableEnvironment

  2. 配置ConfigurableEnvironment,主要配置PropertySource(包括defaultProperties和addCommandLineProperties)和Profile。

  3. 将environment绑定至SpringApplication

  4. Attach一个ConfigurationPropertySource至environment.

2.2.3 Create ApplicationContext

根据this.webApplicationType的类型来创建不同的ConfigurableApplicationContext。

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
            case SERVLET:
                contextClass = Class.forName(DEFAULT_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);
}

2.2.4 Prepare Context

在 prepareContext的过程中,首先会运行一些初始化的流程,然后会注册一些spring boot启动所需要的bean,加载一些初始的beans。

//1. Apply any relevant post processing the ApplicationContext
postProcessApplicationContext(context);
//2. Apply any {@linkApplicationContextInitializer}s to the context before it isrefreshed.
applyInitializers(context);
//3. Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);
//4. Load beans into the application context.
Set sources = getAllSources();
load(context, sources.toArray(newObject[0]));

2.2.5 Refresh Context

对前边两步中创建和准备好的application context执行refresh操作,这一部分的具体实现在spring-context包中。本文不再具体分析。

2.2.6 Call Runner

主要是调用一下设置的一些ApplicationRunner和CommandLineRunner。

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);
        }
    }
}

3 总结

Spring boot实际上是对原有Spring Application启动方式的一种革命。
在传统的Spring Application中,程序启动的方式是传统的web容器(如tomcat、jetty)为入口,spring application作为webAppContext插入到web容器中。而在spring
boot的方式中,完全是以spring application为主,传统的web容器作为插件嵌入到spring application中。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容