深入剖析 SpringApplication

本文将以Spring Boot启动类SpringApplication为切入点,深入剖析SpringBoot的整个启动过程,本文内容包括

  • 使用方式
  • 原理剖析
  • 总结

使用方式

在Spring Boot官方文档里,有个简单的实例,

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.web.bind.annotation.*;

@RestController
@EnableAutoConfiguration
public class Example {

    @RequestMapping("/")
    String home() {
        return "Hello World!";
    }

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

}

本实例启动了一个Spring Boot实例,注册了一个Restfull接口。对比没有Spring Boot之前,这种方式不仅减少了配置文件,还极大的简化了代码,并且不需要再部署到一个Web容器中,而是使用了内置的Web容器。下面我们将深入剖析这种使用的整体过程。

原理剖析

根据实例,查看SpringApplication.run() 方法的实现

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
                                                 String[] args) {
    return new SpringApplication(primarySources).run(args);
}

源码中,SpringApplication分两个阶段,

  1. 一个是new SpringApplication() 准备阶段。
  2. 一个是SpringApplication#run 运行阶段。

接下来对这两个阶段进行剖析。

准备阶段

准备阶段就是SpringApplication 的实例化阶段,先看源码

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. 设置初始化器
  3. 设置监听器
  4. 推断带有main方法的引导类
推断应用类型

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

根据源码,可以看出,应用类型主要有三种,WebApplicationType.REACTIVEWebApplicationType.NONEWebApplicationType.SERVLET ,针对三种应用类型,创建的ApplicationContext 会有所不同,具体的不同应用类型创建何种ApplicationContext 将会在运行阶段说明。这个应用类型推断,依据应用中所存在的类来判断,判断如下:

  1. 若存在org.springframework.web.reactive.DispatcherHandler 并且不存在 org.springframework.web.servlet.DispatcherServlet, 则推断为REACTIVE 应用类型。
  2. 若同时存在javax.servlet.Servletorg.springframework.web.context.ConfigurableWebApplicationContext, 则推断为 SERVLET 应用类型。
  3. 否则推断为 NONE 类型
设置初始化器和监听器

设置初始化器和监听器,用了同样的原理,都是使用SpringFactoriesLoader 类进行加载的,前面有篇专门的文章介绍了SpringFactoriesLoader ,这里就简单说明下。SpringFactoriesLoader 会读取META-INF/spring.factories 文件,这个文件符合properties 文件格式,SpringFactoriesLoader 读取这里文件的内容,并将所有相同Key的字符串集合,实例化为Class

这里初始化器和监听器的设置,就是将所有META-INF/spring.factories 文件里的键为 org.springframework.context.ApplicationContextInitializer 的字符串集合,设置为初始化器;所有键为org.springframework.context.ApplicationListener 的字符串集合,设置为监听器。

推断引导类

deduceMainApplicationClass 方法用于推断包含main方法的引导类,下面是具体源码:

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 的准备阶段,接下来将继续分析运行阶段内容

运行阶段

运行阶段的入口是org.springframework.boot.SpringApplication#run(java.lang.String...) 这个方法,看下源码过程

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    // 获取SpringApplicationRunListener 集合
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 通知监听器开始应用starting
    listeners.starting();
    try {
        // 获取启动参数,并添加到环境配置中
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
        // 初始化环境配置ConfigurableEnvironment, 并通知监听器
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                                                               applicationArguments);
        configureIgnoreBeanInfo(environment);
        // 获取Banner图标
        Banner printedBanner = printBanner(environment);
        // 根据WebApplicationType 判断创建的ApplicationContext
        context = createApplicationContext();
        // 获取异常报告处理类
        exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
        // 装载Bean,并执行初始化器,及通知监听器
        prepareContext(context, environment, listeners, applicationArguments,
                       printedBanner);
        // 执行ApplicationContext刷新操作
        refreshContext(context);
        // 执行刷新后操作
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                .logStarted(getApplicationLog(), stopWatch);
        }
        // 通知监听器,应用已started
        listeners.started(context);
        // 执行Runners,包括ApplicationRunner和CommandLineRunner
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        // 异常处理
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // 通知监听器,应用处于running状态
        listeners.running(context);
    }
    catch (Throwable ex) {
        // 异常处理
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

运行阶段,主要完成以下流程

  1. 获取监听器
  2. 通知监听器,应用处于starting状态
  3. 初始化环境配置 ConfigurableEnvironment
  4. 创建ApplicationContext
  5. 装载Bean
  6. 执行ApplicationContext refresh操作
  7. 通知监听器,应用处理started状态
  8. 执行Runners,包括ApplicationRunnerCommandLineRunner 两接口的所有实现
  9. 最后通知监听器,应用处于running状态

下面我们重点分析创建ApplicationContext 、装载Bean、执行ApplicationContext refresh操作

创建ApplicationContext

源码中,通过createApplicationContext 方法来创建ApplicationContext ,来看下这个方法的源码如何

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

源码可见,创建ApplicationContext 是根据WebApplicationType 的类型创建的,规则如下:

WebApplicationType类型ApplicationContext实例类型
SERVLETorg.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
REACTIVEorg.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
NONEorg.springframework.context.annotation.AnnotationConfigApplicationContext
装载Bean

装载Bean的过程,通过方法prepareContext 实现,代码如下

private void prepareContext(ConfigurableApplicationContext context, 
                            ConfigurableEnvironment environment, 
                            SpringApplicationRunListeners listeners,
                            ApplicationArguments applicationArguments, 
                            Banner printedBanner) {
    // 设置全局外部化配置
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    // 执行初始化器
    applyInitializers(context);
    // 通知监听器
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }

    // 添加启动参数为单例
    context.getBeanFactory().registerSingleton("springApplicationArguments",
                                               applicationArguments);
    if (printedBanner != null) {
        context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
    }

    // 加载配置
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    // 装载Bean
    load(context, sources.toArray(new Object[0]));
    // 通知监听器
    listeners.contextLoaded(context);
}

这部分代码的主要内容在于第29行,load 方法代码如下:

protected void load(ApplicationContext context, Object[] sources) {
    if (logger.isDebugEnabled()) {
        logger.debug(
            "Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
    }
    BeanDefinitionLoader loader = createBeanDefinitionLoader(
        getBeanDefinitionRegistry(context), sources);
    if (this.beanNameGenerator != null) {
        loader.setBeanNameGenerator(this.beanNameGenerator);
    }
    if (this.resourceLoader != null) {
        loader.setResourceLoader(this.resourceLoader);
    }
    if (this.environment != null) {
        loader.setEnvironment(this.environment);
    }
    loader.load();
}

load 方法创建了BeanDefinitionLoader 去加载所有Bean,在方法最后执行装载过程。

执行ApplicationContext refresh操作

ApplicatoinContext 的refresh操作,通过refreshContext 方法操作,最终执行的代码如下:

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

此方法可以看出,最终执行的还是AbstractApplicationContext 的refresh方法。

总结

  1. SpringApplication 的最简单使用,是使用SpringApplication.run 方法
  2. SpringApplication 的启动,分为两阶段,准备阶段和运行阶段
  3. 准备阶段主要完成应用类型推断、设置初始化器和监听器、引导类推断
  4. 运行阶段主要完成ApplicationContext 的创建、Bean的装载、执行ApplicationContext 的刷新
  5. ApplicationContext 的刷新主要通过AbstractApplicationContext#refresh 完成。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值