乐趣区

深入理解SpringApplication

SpringApplication 类用于引导和启动一个 Spring 应用程序(即 SpringBoot 开发的应用)。通常用 SpringBoot 开发一个应用程序时,在主类的 main 函数中可以通过如下代码启动一个 Spring 应用:

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

SpringApplication 的静态方法 run(Class<?> primarySource, String... args)) 的第一个参数接受一个 Spring 容器配置类(用 Java 代码对 Spring 容器进行配置)。第二个参数是命令行参数。将命令行参数转发给 SpringApplication 类,就可以在用 java 命令启动应用时,通过命令行参数对 Spring 应用做一些配置。
SpringApplication 类会做如下事情启动应用:

  • 为应用创建一个合适的 ApplicationContext
  • 注册一个 CommandLinePropertySource,通过 CommandLinePropertySource 可以对外暴露命令行参数,并将命令行参数与 spring 应用中用到的 properties 关联起来
  • 启动 ApplicationContext
  • 执行所有的 CommandLineRunner 类型 bean

下面我们通过 SpringApplication 的源码详细描述上述这些过程。

构建 SpringApplication 实例

下面是 SpringApplication 类静态 run 方法的源码。可以看到,当我们调用这个静态 run 方法时,实际上会构造一个 SpringApplication 实例,然后再调用实例的 run 方法完成 spring 应用的启动。

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

下面是 SpringApplication 的构造函数,它主要完成下面初始化工作:

  • 初始化 Spring 容器的配置类primarySources
  • 推断应用程序的类型,进而根据应用程序的类型创建恰当的 ApplicationContext
  • 初始化指定的 ApplicationContextInitializer 列表
  • 初始化指定的 ApplicationListener 列表
  • 推断 main 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 = WebApplicationType.deduceFromClasspath();
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();}

下面对这些初始化过程进行一一说明:

  • spring 容器配置

SpringApplication 能够从各种不同的配置源读取 bean 的定义。Spring Boot 建议采用 Java 注解配置的方式提供一个全局唯一的配置类。但是,你可以同时使用多种不同的配置源。如果是 Java 注解的配置方式,会使用 AnnotatedBeanDefinitionReader 加载配置 (通过全类名)。如果是 XML 的配置方式,则会使用XmlBeanDefinitionReader 加载配置 (通过 XML 文件地址)。
如果除了 primarySources 配置类以外,还需要其它的 ApplicationContext 配置源,则可以调用 SpringApplication#setSources(Set<String> sources) 方法进行设置,该方法的参数既可以接受一个配置类的全类名,也可以是一个 XML 配置文件的地址。

  • 推断应用程序类型

SpringApplication 默认的应用类型只有三种:

public enum WebApplicationType {
    /**
     * 非 web 类应用,无需内嵌 web server
     */
    NONE,
    /**
     * servlet 类型的 web 应用,需要启动内嵌的 web server
     */
    SERVLET,
    /**
     * reactive 类型的 web 应用,需要启动内嵌的 reactive web server
     * 啥是 reactive 类型的 web 应用?目前还不知道 ^_^
     */
    REACTIVE;

判断的逻辑也非常简单,就是检查 classpath 下是否存在对应的类。

  • 如果 classpath 下存在 org.springframework.web.reactive.DispatcherHandler 类,则应用类型是 REACTIVE
  • 如果 classpath 下存在 org.springframework.web.servlet.DispatcherServlet 类,则应用类型是 SERVLET
  • 如果上面两个 DispatcherServlet 类都不存在,则应用类型是 NONE

应用类型直接决定了要创建的 ApplicationContext 类型,下表整理了三种应用类型和所创建的 ApplicationContext 间的对应关系:

应用类型 ApplicationContext 类型
NONE AnnotationConfigApplicationContext
SERVLET AnnotationConfigServletWebServerApplicationContext
REACTIVE AnnotationConfigReactiveWebServerApplicationContext
  • 初始化 ApplicationContextInitializer&ApplicationListener

初始化 ApplicationContextInitializer 和 ApplicationListener 的过程比较相似,都是借助于 SpringFactories 的方式完成初始化的,所以放到一起讲述。

退出移动版