精尽Spring Boot源码分析 – SpringApplication 启动类的启动过程

精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程

该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读

Spring Boot 版本:2.2.x

最好对 Spring 源码有一定的了解,可以先查看我的 《死磕 Spring 之 IoC 篇 – 文章导读》 系列文章

如果该篇内容对您有帮助,麻烦点击一下“推荐”,也可以关注博主,感激不尽~

该系列其他文章请查看:《精尽 Spring Boot 源码分析 – 文章导读》

概述

我们 Spring Boot 项目的启动类通常有下面三种方式

// 方式一
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
// 方式二
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class).run(args);
    }
}
// 方式三
@SpringBootApplication
public class Application extends SpringBootServletInitializer {

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

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(Application.class);
    }
}

方式一方式二本质上都是通过调用 SpringApplication#run(..) 方法来启动应用,不同的是方式二通过构建器模式,先构建一个 SpringApplication 实例对象,然后调用其 run(..) 方法启动应用,这种方式可以对 SpringApplication 进行配置,更加的灵活。

我们再来看到方式三,它和方式一差不多,不同的是它继承了 SpringBootServletInitializer 这个类,作用就是当你的 Spring Boot 项目打成 war 包时能够放入外部的 Tomcat 容器中运行,如果是 war 包,那上面的 main(...) 方法自然是不需要的,当然,configure(..) 方法也可以不要。

在上篇 《精尽 Spring Boot 源码分析 – Jar 包的启动实现》 文章中讲到,通过 java -jar 启动 Spring Boot 应用时,最终会调用我们启动类的 main(..) 方法,那么本文主要就是对 SpringApplication 这个类进行分析。至于上面 @SpringBootApplication 注解和方式三的相关内容在后续的文章会讲到。

SpringApplicationBuilder

org.springframework.boot.builder.SpringApplicationBuilder,SpringApplication 的构建器,如下:

public class SpringApplicationBuilder {

	private final SpringApplication application;

	private ConfigurableApplicationContext context;

	private final AtomicBoolean running = new AtomicBoolean(false);

	private final Set<Class<?>> sources = new LinkedHashSet<>();

	public SpringApplicationBuilder(Class<?>... sources) {
		this.application = createSpringApplication(sources);
	}
    
    protected SpringApplication createSpringApplication(Class<?>... sources) {
		return new SpringApplication(sources);
	}
    
    public ConfigurableApplicationContext run(String... args) {
		if (this.running.get()) {
			// If already created we just return the existing context
			return this.context;
		}
		configureAsChildIfNecessary(args);
		if (this.running.compareAndSet(false, true)) {
			synchronized (this.running) {
				// If not already running copy the sources over and then run.
				this.context = build().run(args);
			}
		}
		return this.context;
	}
    
    public SpringApplication build() {
		return build(new String[0]);
	}
    
    public SpringApplication build(String... args) {
		configureAsChildIfNecessary(args);
		this.application.addPrimarySources(this.sources);
		return this.application;
	}
}

上面仅列出了 SpringApplicationBuilder 的部分代码,它支持对 SpringApplication 进行配置,底层还是通过 SpringApplication 这个类来启动应用的,不过多的讲述,感兴趣的可以去看看。

SpringApplication

org.springframework.boot.SpringApplication,Spring 应用启动器。正如其代码上所添加的注释,它来提供启动 Spring 应用的功能。

Class that can be used to bootstrap and launch a Spring application from a Java main method.

相关属性

public class SpringApplication {

	/**
	 * Spring 应用上下文(非 Web 场景)
	 * The class name of application context that will be used by default for non-web environments.
	 */
	public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
			+ "annotation.AnnotationConfigApplicationContext";

	/**
	 * Spring 应用上下文(Web 场景)
	 * The class name of application context that will be used by default for web environments.
	 */
	public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
			+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

	/**
	 * Spring 应用上下文(Reactive 场景)
	 * The class name of application context that will be used by default for reactive web environments.
	 */
	public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
			+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";

	/**
	 * 主 Bean(通常为我们的启动类,优先注册)
	 */
	private Set<Class<?>> primarySources;
	/**
	 * 来源 Bean(优先注册)
	 */
	private Set<String> sources = new LinkedHashSet<>();
	/**
	 * 启动类
	 */
	private Class<?> mainApplicationClass;
	/**
	 * Banner 打印模式
	 */
	private Banner.Mode bannerMode = Banner.Mode.CONSOLE;
	/**
	 * 是否打印应用启动耗时日志
	 */
	private boolean logStartupInfo = true;
	/**
	 * 是否接收命令行中的参数
	 */
	private boolean addCommandLineProperties = true;
	/**
	 * 是否设置 ConversionService 类型转换器
	 */
	private boolean addConversionService = true;
	/**
	 * Banner 对象(用于输出横幅)
	 */
	private Banner banner;
	/**
	 * 资源加载对象
	 */
	private ResourceLoader resourceLoader;
	/**
	 * Bean 名称生成器
	 */
	private BeanNameGenerator beanNameGenerator;
	/**
	 * Spring 应用的环境对象
	 */
	private ConfigurableEnvironment environment;
	/**
	 * Spring 应用上下文的 Class 对象
	 */
	private Class<? extends ConfigurableApplicationContext> applicationContextClass;
	/**
	 * Web 应用的类型(Servlet、Reactive)
	 */
	private WebApplicationType webApplicationType;
	/**
	 * 是否注册钩子函数,用于 JVM 关闭时关闭 Spring 应用上下文
	 */
	private boolean registerShutdownHook = true;
	/**
	 * 保存 ApplicationContextInitializer 对象(主要是对 Spring 应用上下文做一些初始化工作)
	 */
	private List<ApplicationContextInitializer<?>> initializers;
	/**
	 * 保存 ApplicationListener 监听器(支持在整个 Spring Boot 的多个时间点进行扩展)
	 */
	private List<ApplicationListener<?>> listeners;
	/**
	 * 默认的配置项
	 */
	private Map<String, Object> defaultProperties;
	/**
	 * 额外的 profile
	 */
	private Set<String> additionalProfiles = new HashSet<>();
	/**
	 * 是否允许覆盖 BeanDefinition
	 */
	private boolean allowBeanDefinitionOverriding;
	/**
	 * 是否为自定义的 Environment 对象
	 */
	private boolean isCustomEnvironment = false;
	/**
	 * 是否支持延迟初始化(需要通过 {@link LazyInitializationExcludeFilter} 过滤)
	 */
	private boolean lazyInitialization = false;
}

上面基本上列出了 SpringApplication 的所有属性,每个属性都比较关键,大家先有一个印象,后面也可以回过头来看

构造器

public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // <1> 设置资源加载器
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // <2> 设置主要的 Class 类对象,通常是我们的启动类
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // <3> 通过 `classpath` 判断是否存在相应的 Class 类对象,来决定当前 Web 应用的类型(REACTIVE、SERVLET、NONE),默认为 **SERVLET**
    // 不同的类型后续创建的 Environment 类型不同
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    /**
     * <4> 初始化所有 `ApplicationContextInitializer` 类型的对象,并保存至 `initializers` 集合中
     * 通过类加载器从 `META-INF/spring.factories` 文件中获取 ApplicationContextInitializer 类型的类名称,并进行实例化
     */
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    /**
     * <5> 初始化所有 `ApplicationListener` 类型的对象,并保存至 `listeners` 集合中
     * 通过类加载器从 `META-INF/spring.factories` 文件中获取 ApplicationListener 类型的类名称,并进行实例化
     * 例如有一个 {@link ConfigFileApplicationListener}
     */
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // <6> 获取当前被调用的 `main` 方法所属的 Class 类对象,并设置(主要用于打印日志)
    this.mainApplicationClass = deduceMainApplicationClass();
}

在我们自己的启动类中不管是通过哪种方式都是会先创建一个 SpringApplication 实例对象的,可以先看下它的 run(Class<?>, String...) 方法:

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    // 同样是先创建一个 SpringApplication 对象
    return new SpringApplication(primarySources).run(args);
}

实例化的过程中做了不少事情,如下:

  1. 设置资源加载器,默认为 null,可以通过 SpringApplicationBuilder 设置

  2. 设置 primarySources 为主要的 Class 类对象,通常是我们的启动类

  3. 通过 classpath 判断是否存在相应的 Class 类对象,来决定当前 Web 应用的类型(REACTIVE、SERVLET、NONE),默认为 SERVLET,不同的类型后续创建的 Environment 类型不同

    public enum WebApplicationType {
    
    	NONE, SERVLET, REACTIVE;
    
    	private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
    			"org.springframework.web.context.ConfigurableWebApplicationContext" };
    
    	private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
    
    	private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
    
    	private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
    
    	private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
    
    	private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
    
    	static WebApplicationType deduceFromClasspath() {
    		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
    				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
    			return WebApplicationType.REACTIVE;
    		}
    		for (String className : SERVLET_INDICATOR_CLASSES) {
    			if (!ClassUtils.isPresent(className, null)) {
    				return WebApplicationType.NONE;
    			}
    		}
    		return WebApplicationType.SERVLET;
    	}
    }
    

    很简单,就是依次判断当前 JVM 中是否存在相关的 Class 类对象,来决定使用哪种 Web 类型,默认是 SERVLET 类型

  4. 初始化所有 ApplicationContextInitializer 类型的对象,并保存至 initializers 集合中

  5. 初始化所有 ApplicationListener 类型的对象,并保存至 listeners 集合中,例如 ConfigFileApplicationListenerLoggingApplicationListener

  6. 获取当前被调用的 main 方法所属的 Class 类对象,并设置(主要用于打印日志)

    private Class<?> deduceMainApplicationClass() {
        try {
            // 获取当前的调用栈
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            // 对调用栈进行遍历,找到 `main` 方法所在的 Class 类对象并返回
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }
    

上面的第 45 步都是通过类加载器从 META-INF/spring.factories 文件中分别获取 ApplicationContextInitializerApplicationListener 类型的类名称,然后进行实例化,这个两种类型的对象都是对 Spring 的一种拓展,像很多框架整合 Spring Boot 都可以通过自定义的 ApplicationContextInitializer 对 ApplicationContext 进行一些初始化,通过 ApplicationListener 在 Spring 应用启动的不同阶段来织入一些功能

getSpringFactoriesInstances 方法

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    // <1> 获取类加载器
    ClassLoader classLoader = getClassLoader();
    // Use names and ensure unique to protect against duplicates
    // <2> 通过类加载器从 `META-INF/spring.factories` 文件中获取类型为 `type` 的类名称
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // <3> 为上一步获取到的所有类名称创建对应的实例对象
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    // <4> 通过 `@Order` 注解进行排序
    AnnotationAwareOrderComparator.sort(instances);
    // <5> 返回排序后的 `type` 类型的实例对象
    return instances;
}

过程比较简单,如下:

  1. 获取类加载器

  2. 通过类加载器从所有 META-INF/spring.factories 文件中获取类型为 type 的类名称,这里的 SpringFactoriesLoader 是 Spring 中的一个类

  3. 为上一步获取到的所有类名称创建对应的实例对象

    private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
            ClassLoader classLoader, Object[] args, Set<String> names) {
        List<T> instances = new ArrayList<>(names.size());
        // 遍历所有的类名称,依次创建实例对象,一并返回
        for (String name : names) {
            try {
                // 获取对应的 Class 类对象
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                // 对 Class 类对象进行校验,判断类型是否匹配
                Assert.isAssignable(type, instanceClass);
                // 获取指定入参类型(ConfigurableApplicationContext)的构造器
                Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
                // 通过构造器创建一个实例对象
                T instance = (T) BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            }
            catch (Throwable ex) {
                throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
            }
        }
        return instances;
    }
    
  4. 通过 @Order 注解进行排序

  5. 返回排序后的 type 类型的实例对象

SpringApplication#run 方法

上面已经讲述了 SpringApplication 的实例化过程,那么接下来就是调用它的 run(String... args) 方法来启动 Spring 应用,该过程如下:

public ConfigurableApplicationContext run(String... args) {
    // <1> 创建 StopWatch 对象并启动,主要用于统计当前方法执行过程的耗时
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // <2> 设置 `java.awt.headless` 属性,和 AWT 相关,暂时忽略
    configureHeadlessProperty();
    // <3> 初始化所有 `SpringApplicationRunListener` 类型的对象,并全部封装到 `SpringApplicationRunListeners` 对象中
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // <4> 启动所有的 `SpringApplicationRunListener` 监听器
    // 例如 `EventPublishingRunListener` 会广播 ApplicationEvent 应用正在启动的事件,
    // 它里面封装了所有的 `ApplicationListener` 对象,那么此时就可以通过它们做一些初始化工作,进行拓展
    listeners.starting();
    try {
        // <5> 创建一个应用参数对象,将 `main(String[] args)` 方法的 `args` 参数封装起来,便于后续使用
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // <6> 准备好当前应用 Environment 环境,这里会加载出所有的配置信息,包括 `application.yaml` 和外部的属性配置
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

        configureIgnoreBeanInfo(environment);
        // <7> 打印 banner 内容
        Banner printedBanner = printBanner(environment);
        // <8> 对 `context` (Spring 上下文)进行实例化
        // 例如 Servlet(默认)会创建一个 AnnotationConfigServletWebServerApplicationContext 实例对象
        context = createApplicationContext();
        // <9> 获取异常报告器,通过类加载器从 `META-INF/spring.factories` 文件中获取 SpringBootExceptionReporter 类型的类名称,并进行实例化
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        // <10> 对 Spring 应用上下文做一些初始化工作,例如执行 ApplicationContextInitializer#initialize(..) 方法
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        /**
         * <11> 刷新 Spring 应用上下文,在这里会完成所有 Spring Bean 的初始化,同时会初始化好 Servlet 容器,例如 Tomcat
         * 这一步涉及到 Spring IoC 的所有内容,参考[死磕Spring之IoC篇 - Spring 应用上下文 ApplicationContext](https://www.cnblogs.com/lifullmoon/p/14453083.html)
         * 在 {@link ServletWebServerApplicationContext#onRefresh()} 方法中会创建一个 Servlet 容器(默认为 Tomcat),也就是当前 Spring Boot 应用所运行的 Web 环境
         */
        refreshContext(context);
        // <12> 完成刷新 Spring 应用上下文的后置操作,空实现,扩展点
        afterRefresh(context, applicationArguments);
        // <13> 停止 StopWatch,统计整个 Spring Boot 应用的启动耗时,并打印
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // <14> 对所有的 SpringApplicationRunListener 监听器进行广播,发布 ApplicationStartedEvent 应用已启动事件
        listeners.started(context);
        // <15> 回调 IoC 容器中所有 ApplicationRunner 和 CommandLineRunner 类型的启动器
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        // 处理异常,同时会将异常发送至上面第 `9` 步获取到的异常报告器
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // <16> 对所有的 SpringApplicationRunListener 监听器进行广播,发布 ApplicationReadyEvent 应用已就绪事件
        listeners.running(context);
    }
    catch (Throwable ex) {
        // 处理异常,同时会将异常发送至上面第 `9` 步获取到的异常报告器
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

整个启动过程做了很多事情,主要过程如下:

  1. 创建 StopWatch 对象并启动,主要用于统计当前方法执行过程的耗时

  2. 设置 java.awt.headless 属性,和 AWT 相关,暂时忽略

  3. 调用 getRunListeners(..) 方法,初始化所有 SpringApplicationRunListener 类型的对象,并全部封装到 SpringApplicationRunListeners 对象中

  4. 启动所有的 SpringApplicationRunListener 监听器,例如 EventPublishingRunListener 会广播 ApplicationEvent 应用正在启动的事件,它里面封装了所有的 ApplicationListener 对象,那么此时就可以通过它们做一些初始化工作,进行拓展

  5. 创建一个 ApplicationArguments 应用参数对象,将 main(String[] args) 方法的 args 参数封装起来,便于后续使用

  6. 调用 prepareEnvironment(..) 方法,准备好当前应用 Environment 环境,这里会加载出所有的配置信息,包括 application.yaml 和外部的属性配置

  7. 调用 printBanner(..) 方法,打印 banner 内容

  8. 调用 createApplicationContext() 方法, 对 context(Spring 上下文)进行实例化,例如 Servlet(默认)会创建一个 AnnotationConfigServletWebServerApplicationContext 实例对象

  9. 获取异常报告器,通过类加载器从 META-INF/spring.factories 文件中获取 SpringBootExceptionReporter 类型的类名称,并进行实例化

  10. 调用 prepareContext(..) 方法,对 Spring 应用上下文做一些初始化工作,例如执行 ApplicationContextInitializer#initialize(..) 方法

  11. 调用 refreshContext(..) 方法,刷新 Spring 应用上下文,在这里会完成所有 Spring Bean 的初始化,同时会初始化好 Servlet 容器,例如 Tomcat

    这一步涉及到 Spring IoC 的所有内容,参考 《死磕Spring之IoC篇 – Spring 应用上下文 ApplicationContext》

    ServletWebServerApplicationContext#onRefresh() 方法中会创建一个 Servlet 容器(默认为 Tomcat),也就是当前 Spring Boot 应用所运行的 Web 环境

  12. 调用 afterRefresh(..) 方法,完成刷新 Spring 应用上下文的后置操作,空实现,扩展点

  13. 停止 StopWatch,统计整个 Spring Boot 应用的启动耗时,并打印

  14. 对所有的 SpringApplicationRunListener 监听器进行广播,发布 ApplicationStartedEvent 应用已启动事件,通常只有一个 EventPublishingRunListener 对象

  15. 回调 IoC 容器中所有 ApplicationRunner 和 CommandLineRunner 类型的启动器,默认情况下没有,先暂时忽略

  16. 对所有的 SpringApplicationRunListener 监听器进行广播,发布 ApplicationReadyEvent 应用已就绪事件,通常只有一个 EventPublishingRunListener 对象

启动 Spring 应用的整个主流程清晰明了,先准备好当前应用的 Environment 环境,然后创建 Spring ApplicationContext 应用上下文。

该方法的整个过程更多的细节在于上面每一步调用的方法,抽丝剥茧,对于非常复杂的地方会另起文章进行分析

3. getRunListeners 方法

getRunListeners(String[] args) 方法,初始化所有 SpringApplicationRunListener 类型的对象,并全部封装到 SpringApplicationRunListeners 对象中,如下:

private SpringApplicationRunListeners getRunListeners(String[] args) {
    // 指定实例化对象所使用的构造器的入参类型
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    // 通过类加载器从 `META-INF/spring.factories` 文件中获取 SpringApplicationRunListener 类型的类名称,并进行实例化
    return new SpringApplicationRunListeners(logger,
            getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

这里同样调用上面讲过的 getSpringFactoriesInstances(..) 方法,通过类加载器从 META-INF/spring.factories 文件中获取 SpringApplicationRunListener 类型的类名称,并进行实例化

最后会将它们全部封装到 SpringApplicationRunListeners 对象中,就是把一个 List 封装到一个对象中,不过默认情况只有一个 EventPublishingRunListener 对象,其内部又封装了 SpringApplication 中的所有 ApplicationListener 应用监听器们,例如 ConfigFileApplicationListenerLoggingApplicationListener

6. prepareEnvironment 方法

prepareEnvironment(SpringApplicationRunListeners, ApplicationArguments) 方法,准备好当前应用 Environment 环境,加载出所有的配置信息,包括 application.yaml 和外部的属性配置,如下:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // Create and configure the environment
    // <1> 根据 Web 应用的类型创建一个 StandardEnvironment 对象 `environment`
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // <2> 为 `environment` 配置默认属性(如果有)并设置需要激活的 `profile` 们
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // <3> 将当前 `environment` 的 MutablePropertySources 封装成 SpringConfigurationPropertySources 添加到 MutablePropertySources 首部
    ConfigurationPropertySources.attach(environment);
    /**
     * <4> 对所有的 SpringApplicationRunListener 广播 ApplicationEvent 应用环境已准备好的事件,这一步比较复杂
     * 例如 Spring Cloud 的 BootstrapApplicationListener 监听到该事件会创建一个 ApplicationContext 作为当前 Spring 应用上下文的父容器,同时会读取 `bootstrap.yml` 文件的信息
     * {@link ConfigFileApplicationListener} 监听到该事件然后去解析 `application.yml` 等应用配置文件的配置信息
     */
    listeners.environmentPrepared(environment);
    // <5> 将 `environment` 绑定到当前 SpringApplication 上
    bindToSpringApplication(environment);
    // <6> 如果不是自定义的 Environment 则需要根据 Web 应用类型转换成对应 Environment 类型
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                deduceEnvironmentClass());
    }
    // <7> 再次进行上面第 `3` 步的处理过程,防止上面几步对上面的 PropertySources 有修改
    ConfigurationPropertySources.attach(environment);
    // <8> 返回准备好的 `environment`
    return environment;
}

该过程如下:

  1. 根据 Web 应用的类型创建一个 StandardEnvironment 对象 environment

    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        switch (this.webApplicationType) {
        case SERVLET:
            return new StandardServletEnvironment();
        case REACTIVE:
            return new StandardReactiveWebEnvironment();
        default:
            return new StandardEnvironment();
        }
    }
    
  2. environment 配置默认属性(如果有)并设置需要激活的 profile

    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        if (this.addConversionService) {
            // <1> 获取 ConversionService 类型转换器并设置到 Environment 对象中
            ConversionService conversionService = ApplicationConversionService.getSharedInstance();
            environment.setConversionService((ConfigurableConversionService) conversionService);
        }
        // <2> 配置属性源里面的属性
        // 例如有默认属性则将他们添加至最后,命令行中的参数则添加至最前面
        configurePropertySources(environment, args);
        // <3> 设置需要激活的 `profile` 们
        // 例如通过 `-Dspring.profiles.active=dev` 配置需要激活的环境
        configureProfiles(environment, args);
    }
    
    protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
        MutablePropertySources sources = environment.getPropertySources();
        // 如果默认配置属性不为空则添加至最后
        if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
            sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
        }
        // 如果 `main` 方法有入参
        if (this.addCommandLineProperties && args.length > 0) {
            String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
            if (sources.contains(name)) {
                PropertySource<?> source = sources.get(name);
                CompositePropertySource composite = new CompositePropertySource(name);
                composite.addPropertySource(
                        new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
                composite.addPropertySource(source);
                sources.replace(name, composite);
            }
            else {
                // 将命令行中的入参添加至最前面
                // 例如 "java -jar xxx.jar --spring.profiles.active=dev",那么这里就可以获取到
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }
    }
    

    可以看到会将 main(String[] args) 方法入参中的 -- 开头的参数设置到 Environment 中

  3. 将当前 environment 的 MutablePropertySources 封装成 SpringConfigurationPropertySources 添加到 MutablePropertySources 首部

  4. 对所有的 SpringApplicationRunListener 广播 ApplicationEvent 应用环境已准备好的事件,这一步比较复杂,例如 Spring Cloud 的 BootstrapApplicationListener 监听到该事件会创建一个 ApplicationContext 作为当前 Spring 应用上下文的父容器,同时会读取 bootstrap.yml 文件的信息

    这里会有一个 ConfigFileApplicationListener 监听到该事件然后去解析 application.yml 等应用配置文件的配置信息

  5. environment 绑定到当前 SpringApplication 上

  6. 如果不是自定义的 Environment 则需要根据 Web 应用类型转换成对应 Environment 类型

  7. 再次进行上面第 3 步的处理过程,防止上面几步对上面的 PropertySources 有修改

  8. 返回准备好的 environment

该方法准备好了当前应用 Environment 环境,主要在于上面第 4 步,是 ApplicationListener 监听器的一个扩展点,在这里会加载出所有的配置信息,包括 application.yml 和外部配置,解析配置的过程比较复杂,在后面的文章中单独分析

8. createApplicationContext 方法

createApplicationContext() 方法,对 context(Spring 上下文)进行实例化,如下:

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
            case SERVLET:
                // org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
                contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                break;
            case REACTIVE:
                // org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
            default:
                // org.springframework.context.annotation.AnnotationConfigApplicationContext
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
        }
    }
    // 实例化这个 Class 对象
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

不同的应用类型创建不同的 Spring 应用上下文对象:

  • SERVLET(默认是这个):AnnotationConfigServletWebServerApplicationContext
  • REACTIVEAnnotationConfigReactiveWebServerApplicationContext
  • DEFAULTAnnotationConfigApplicationContext

10. prepareContext 方法

prepareContext(ConfigurableApplicationContext, ConfigurableEnvironment, SpringApplicationRunListeners, ApplicationArguments, Banner) 方法,对 Spring 应用上下文做一些初始化工作,如下:

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
        SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    // <1> 为 Spring 应用上下文设置 Environment 环境
    context.setEnvironment(environment);
    // <2> 将一些工具 Bean 设置到 Spring 应用上下文中,供使用
    postProcessApplicationContext(context);
    // <3> 通知 ApplicationContextInitializer 对 Spring 应用上下文进行初始化工作
    // 参考 SpringApplication 构造方法
    applyInitializers(context);
    // <4> 对所有 SpringApplicationRunListener 进行广播,发布 ApplicationContextInitializedEvent 初始化事件
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    // <5> 向应用上下文注册 `main(String[])` 方法的参数 Bean 和 Banner 对象
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    if (this.lazyInitialization) {
        context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    }
    // Load the sources
    // <6> 获取 `primarySources`(例如你的启动类)和 `sources`(例如 Spring Cloud 中的 @BootstrapConfiguration)源对象
    Set<Object> sources = getAllSources();
    // 应用上下文的源对象不能为空
    Assert.notEmpty(sources, "Sources must not be empty");
    // <7> 将上面的源对象加载成 BeanDefinition 并注册
    load(context, sources.toArray(new Object[0]));
    // <8> 对所有的 SpringApplicationRunListener 广播 ApplicationPreparedEvent 应用已准备事件
    // 会把 ApplicationListener 添加至 Spring 应用上下文中
    listeners.contextLoaded(context);
}

该过程如下:

  1. 为 Spring 应用上下文设置 Environment 环境

  2. 将一些工具 Bean 设置到 Spring 应用上下文中,供使用

    protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
        if (this.beanNameGenerator != null) {
            // 注册 Bean 名称生成器
            context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
                    this.beanNameGenerator);
        }
        if (this.resourceLoader != null) {
            if (context instanceof GenericApplicationContext) {
                // 设置资源加载器
                ((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
            }
            if (context instanceof DefaultResourceLoader) {
                ((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
            }
        }
        if (this.addConversionService) {
            // 设置类型转换器
            context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
        }
    }
    
  3. 通知 ApplicationContextInitializer 对 Spring 应用上下文进行初始化工作

    protected void applyInitializers(ConfigurableApplicationContext context) {
        for (ApplicationContextInitializer initializer : getInitializers()) {
            Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
                    ApplicationContextInitializer.class);
            Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
            // 执行 Spring 应用上下文初始器
            // 例如 ContextIdApplicationContextInitializer 会向 Spring 应用上下文注册一个 ContextId 对象
            initializer.initialize(context);
        }
    }
    
  4. 对所有 SpringApplicationRunListener 进行广播,发布 ApplicationContextInitializedEvent 初始化事件

  5. 向 Spring 应用上下文注册 main(String[]) 方法的参数 Bean 和 Banner 对象

  6. 获取 primarySources(例如你的启动类)和 sources(例如 Spring Cloud 中的 @BootstrapConfiguration)源对象,没有的话会抛出异常

  7. 将上面的源对象加载成 BeanDefinition 并注册

  8. 对所有的 SpringApplicationRunListener 广播 ApplicationPreparedEvent 应用已准备事件,会把 ApplicationListener 添加至 Spring 应用上下文中

通过上面的第 6 步你就知道为什么我们的启动类里面一定得有一个入参为启动类的 Class 对象了

11. refreshContext 方法

refreshContext(ConfigurableApplicationContext) 方法,刷新 Spring 应用上下文,在这里会完成所有 Spring Bean 的初始化,同时会初始化好 Servlet 容器,例如 Tomcat,该方法涉及到 Spring IoC 的所有内容,参考 《死磕Spring之IoC篇 – Spring 应用上下文 ApplicationContext》

private void refreshContext(ConfigurableApplicationContext context) {
    if (this.registerShutdownHook) {
        try {
            // 为当前 Spring 应用上下文注册一个钩子函数
            // 在 JVM 关闭时先关闭 Spring 应用上下文
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
    // 调用 AbstractApplicationContext#refresh() 方法,刷新 Spring 上下文
    refresh(context);
}

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

该方法主要是调用 AbstractApplicationContext#refresh() 方法,刷新 Spring 应用上下文,整个过程牵涉到 Spring 的所有内容,之前的一系列文章已经分析过,关于更多的细节这里不展开谈论,当然,这个过程会有对 @SpingBootApplication 注解的解析

根据 8. createApplicationContext 方法 方法中讲到,我们默认情况下是 SERVLET 应用类型,也就是创建一个 AnnotationConfigServletWebServerApplicationContext 对象,在其父类 ServletWebServerApplicationContext 中重写了 onRefresh() 方法,会创建一个 Servlet 容器(默认为 Tomcat),也就是当前 Spring Boot 应用所运行的 Web 环境,这部分内容放在后面的文章单独分析。

SpringApplicationRunListeners

org.springframework.boot.SpringApplicationRunListeners,对 SpringApplicationRunListener 数组的封装

class SpringApplicationRunListeners {

	private final Log log;

	/**
	 * 封装的所有 SpringApplicationRunListener
	 * Spring Boot 在 META-INF/spring.factories 只配置 {@link EventPublishingRunListener} 监听器
	 */
	private final List<SpringApplicationRunListener> listeners;

	SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
		this.log = log;
		this.listeners = new ArrayList<>(listeners);
	}

	void starting() {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.starting();
		}
	}

	void environmentPrepared(ConfigurableEnvironment environment) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.environmentPrepared(environment);
		}
	}

	void contextPrepared(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.contextPrepared(context);
		}
	}

	void contextLoaded(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.contextLoaded(context);
		}
	}

	void started(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.started(context);
		}
	}

	void running(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.running(context);
		}
	}

	void failed(ConfigurableApplicationContext context, Throwable exception) {
		for (SpringApplicationRunListener listener : this.listeners) {
			callFailedListener(listener, context, exception);
		}
	}

	private void callFailedListener(SpringApplicationRunListener listener, ConfigurableApplicationContext context,
			Throwable exception) {
		try {
			listener.failed(context, exception);
		}
		catch (Throwable ex) {
			if (exception == null) {
				ReflectionUtils.rethrowRuntimeException(ex);
			}
			if (this.log.isDebugEnabled()) {
				this.log.error("Error handling failed", ex);
			}
			else {
				String message = ex.getMessage();
				message = (message != null) ? message : "no error message";
				this.log.warn("Error handling failed (" + message + ")");
			}
		}
	}
}

比较简单,就是封装了多个 SpringApplicationRunListener 对象,对于不同类型的事件,调用其不同的方法

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=
org.springframework.boot.context.event.EventPublishingRunListener

可以在 META-INF/spring.factories 文件中看到,只有一个 EventPublishingRunListener 对象

EventPublishingRunListener

org.springframework.boot.context.event.EventPublishingRunListener,实现了 SpringApplicationRunListener 接口,事件广播器,发布不同类型的事件

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

	/**
	 * Spring Boot 应用的启动类
	 */
	private final SpringApplication application;

	/**
	 * 启动类 `main(String[])` 方法的入参
	 */
	private final String[] args;

	/**
	 * 事件广播器,包含了所有的 `META-INF/spring.factories` 文件中配置的 {@link ApplicationListener} 监听器
	 */
	private final SimpleApplicationEventMulticaster initialMulticaster;

	public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		// 在实例化 SpringApplication 的过程中会从 `META-INF/spring.factories` 文件中获取 ApplicationListener 类型的类名称,并进行实例化
		// 这里会将他们添加至广播器中
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}

	@Override
	public int getOrder() {
		return 0;
	}

	@Override
	public void starting() {
		// 广播 Spring Boot 应用正在启动事件
		this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
	}

	@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		// 广播 Spring Boot 应用的 Environment 环境已准备事件
		this.initialMulticaster
				.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
	}

	@Override
	public void contextPrepared(ConfigurableApplicationContext context) {
		// 广播 Spring Boot 应用的 Spring 上下文已初始化事件
		this.initialMulticaster
				.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
	}

	@Override
	public void contextLoaded(ConfigurableApplicationContext context) {
		// 将所有的 ApplicationListener 添加至 Spring 应用上下文
		for (ApplicationListener<?> listener : this.application.getListeners()) {
			if (listener instanceof ApplicationContextAware) {
				((ApplicationContextAware) listener).setApplicationContext(context);
			}
			context.addApplicationListener(listener);
		}
		// 广播 Spring Boot 应用的 Spring 上下文已准备事件
		this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
	}

	@Override
	public void started(ConfigurableApplicationContext context) {
		// 广播 Spring Boot 应用的 Spring 上下文已启动事件
		context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
	}

	@Override
	public void running(ConfigurableApplicationContext context) {
		// 广播 Spring Boot 应用的 Spring 上下文准备就绪事件
		context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
	}

	@Override
	public void failed(ConfigurableApplicationContext context, Throwable exception) {
		ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
		if (context != null && context.isActive()) {
			// Listeners have been registered to the application context so we should
			// use it at this point if we can
			context.publishEvent(event);
		}
		else {
			// An inactive context may not have a multicaster so we use our multicaster to
			// call all of the context"s listeners instead
			if (context instanceof AbstractApplicationContext) {
				for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
						.getApplicationListeners()) {
					this.initialMulticaster.addApplicationListener(listener);
				}
			}
			this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
			this.initialMulticaster.multicastEvent(event);
		}
	}
}

比较简单,关键在于内部的 SimpleApplicationEventMulticaster 事件广播器,里面包含了所有的 META-INF/spring.factories 文件中配置的 ApplicationListener 监听器,不同的方法发布不同的事件,进行广播

# Application Listeners
org.springframework.context.ApplicationListener=
org.springframework.boot.ClearCachesApplicationListener,
org.springframework.boot.builder.ParentContextCloserApplicationListener,
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,
org.springframework.boot.context.FileEncodingApplicationListener,
org.springframework.boot.context.config.AnsiOutputApplicationListener,
org.springframework.boot.context.config.ConfigFileApplicationListener,
org.springframework.boot.context.config.DelegatingApplicationListener,
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,
org.springframework.boot.context.logging.LoggingApplicationListener,
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

可以看到 Spring Boot 配置了许多个 ApplicationListener,后续文章会对 ConfigFileApplicationListener 和 LoggingApplicationListener 进行简单的分析

总结

Spring Boot 应用打成 jar 包后的启动都是通过 SpringApplication#run(String... args) 这个方法来启动整个 Spring 应用的,流程大致如下:

  1. META-INF/spring.factories 文件中加载出相关 Class 对象,并进行实例化,例如 ApplicationContextInitializerSpringApplicationRunListenerApplicationListener 对象
  2. 准备好当前 Spring 应用的 Environment 环境,这里会解析 application.yml 以及外部配置
  3. 创建一个 ApplicationContext 应用上下文对象,默认 SERVLET 类型下创建 AnnotationConfigServletWebServerApplicationContext 对象
  4. 调用 AbstractApplication#refresh() 方法,刷新 Spring 应用上下文,也就是之前一系列 Spring 相关的文章所讲述的内容

整个过程有许多个扩展点是通过监听器机制实现的,在不同阶段广播不同类型的事件,此时 ApplicationListener 就可进行相关的操作

在上面第 4 步中,SERVLET 应用类型下的 Spring 应用上下文会创建一个 Servlet 容器(默认为 Tomcat)

更多的细节在后续文章依次进行分析

hmoban主题是根据ripro二开的主题,极致后台体验,无插件,集成会员系统
自学咖网 » 精尽Spring Boot源码分析 – SpringApplication 启动类的启动过程