springboot-SpringApplication
Posted siye1989
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot-SpringApplication相关的知识,希望对你有一定的参考价值。
1. 概述
import org.springframework.boot.SpringApplication;
|
<1>
处,使用@SpringBootApplication
注解,标明是 Spring Boot 应用。通过它,可以开启自动配置的功能。<2>
处,调用SpringApplication#run(Class<?>... primarySources)
方法,启动 Spring Boot 应用。
上述的代码,是我们使用 Spring Boot 时,最最最常用的代码。而本文,我们先来分析 Spring Boot 应用的启动过程。
关于
@SpringApplication
注解,我们会后面单独开文章,详细解析。
2. SpringApplication
org.springframework.boot.SpringApplication
,Spring 应用启动器。正如其代码上所添加的注释,它来提供启动 Spring 应用的功能。
Class that can be used to bootstrap and launch a Spring application from a Java main method.
|
大多数情况下,我们都是使用它提供的静态方法:
// SpringApplication.java
|
- 前两个静态方法,最终调用的是第 3 个静态方法。而第 3 个静态方法,实现的逻辑就是:
- 首先,创建一个 SpringApplication 对象。详细的解析,见 「2.1 构造方法」 。
- 然后,调用
SpringApplication#run(Class<?> primarySource, String... args)
方法,运行 Spring 应用。详细解析,见 「2.2 run」 。
2.1 构造方法
// SpringApplication.java
|
- SpringApplication 的变量比较多,我们先只看构造方法提到的几个。
resourceLoader
属性,资源加载器。可以暂时不理解,感兴趣的胖友,可以看看 《【死磕 Spring】—— IoC 之 Spring 统一资源加载策略》 文章。primarySources
属性,主要的 Java Config 类的数组。在文初提供的示例,就是 MVCApplication 类。webApplicationType
属性,调用WebApplicationType#deduceFromClasspath()
方法,通过 classpath ,判断 Web 应用类型。- 具体的原理是,是否存在指定的类,艿艿已经在 WebApplicationType 上的方法添加了注释,直接瞅一眼就明白了。
- 这个属性,在下面的
#createApplicationContext()
方法,将根据它的值(类型),创建不同类型的 ApplicationContext 对象,即 Spring 容器的类型不同。
initializers
属性,ApplicationContextInitializer 数组。- 通过
#getSpringFactoriesInstances(Class<T> type)
方法,进行获得 ApplicationContextInitializer 类型的对象数组,详细的解析,见 「2.1.1 getSpringFactoriesInstances」 方法。 - 假设只在 Spring MVC 的环境下,
initializers
属性的结果如下图:
- 通过
listeners
属性,ApplicationListener 数组。- 也是通过
#getSpringFactoriesInstances(Class<T> type)
方法,进行获得 ApplicationListener 类型的对象数组。 - 假设只在 Spring MVC 的环境下,
listeners
属性的结果如下图:
- 也是通过
-
mainApplicationClass
属性,调用#deduceMainApplicationClass()
方法,获得是调用了哪个#main(String[] args)
方法,代码如下:// SpringApplication.java
private Class<?> deduceMainApplicationClass()
try
// 获得当前 StackTraceElement 数组
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
// 判断哪个执行了 main 方法
for (StackTraceElement stackTraceElement : stackTrace)
if ("main".equals(stackTraceElement.getMethodName()))
return Class.forName(stackTraceElement.getClassName());
catch (ClassNotFoundException ex)
// Swallow and continue
return null;- 在文初的例子中,就是 MVCApplication 类。
- 这个
mainApplicationClass
属性,没有什么逻辑上的用途,主要就是用来打印下日志,说明是通过这个类启动 Spring 应用的。
2.1.1 getSpringFactoriesInstances
#getSpringFactoriesInstances(Class<T> type)
方法,获得指定类类对应的对象们。代码如下:
// SpringApplication.java
|
<1>
处,调用SpringFactoriesLoader#loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader)
方法,加载指定类型对应的,在META-INF/spring.factories
里的类名的数组。- 在
META-INF/spring.factories
文件中,会以 KEY-VALUE 的格式,配置每个类对应的实现类们。 - 关于 SpringFactoriesLoader 的该方法,我们就不去细看了。?? 很多时候,我们看源码的时候,不需要陷入到每个方法的细节中。非关键的方法,猜测到具体的用途后,跳过也是没问题的。
- 在
-
<2>
处,调用#createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names)
方法,创建对象们。代码如下:// SpringApplication.java
/**
* 创建对象的数组
*
* @param type 父类
* @param parameterTypes 构造方法的参数类型
* @param classLoader 类加载器
* @param args 参数
* @param names 类名的数组
* @param <T> 泛型
* @return 对象的数组
*/
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names)
List<T> instances = new ArrayList<>(names.size()); // 数组大小,细节~
// 遍历 names 数组
for (String name : names)
try
// 获得 name 对应的类
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
// 判断类是否实现自 type 类
Assert.isAssignable(type, instanceClass);
// 获得构造方法
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;- 比较简单,就不多做解释了。
<3>
处,调用AnnotationAwareOrderComparator#sort(List<?> list)
方法,排序对象们。例如说,类上有@Order
注解。
2.2 run
#run(String... args)
方法,运行 Spring 应用。代码如下:
艿艿:这是一个饱满的方法,所以逻辑比较多哈。
// SpringApplication.java
|
<1>
处,创建 StopWatch 对象,并调用StopWatch#run()
方法来启动。StopWatch 主要用于简单统计 run 启动过程的时长。<2>
处,配置 headless 属性。这个逻辑,可以无视,和 AWT 相关。-
<3>
处,调用#getRunListeners(String[] args)
方法,获得 SpringApplicationRunListener 数组,并启动监听。代码如下:// SpringApplication.java
private SpringApplicationRunListeners getRunListeners(String[] args)
Class<?>[] types = new Class<?>[] SpringApplication.class, String[].class ;
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));- 此处的
listeners
变量,如下图所示: - 注意噢,此时是 SpringApplicationRunListener ,而不是我们看到
listeners
的 ApplicationListener 类型。详细的,我们在 「3. SpringApplicationRunListeners」 中,在详细解析。
- 此处的
-
<4>
处,调用#prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments)
方法,加载属性配置。执行完成后,所有的 environment 的属性都会加载进来,包括application.properties
和外部的属性配置。详细的,胖友先一起跳到 「2.2.1 prepareEnvironment」 中。 -
<5>
处,调用#printBanner(ConfigurableEnvironment environment)
方法,打印 Spring Banner 。效果如下:. ____ _ __ _ _
/\\ / ___‘_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | ‘_ | ‘_| | ‘_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
‘ |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot ::- 具体的代码实现,就先不分析了。感兴趣的胖友,自己去瞅瞅。
-
<6>
处,调用#createApplicationContext()
方法,创建 Spring 容器。详细解析,见 「2.2.2 createApplicationContext」 。 <7>
处,通过#getSpringFactoriesInstances(Class<T> type)
方法,进行获得 SpringBootExceptionReporter 类型的对象数组。SpringBootExceptionReporter ,记录启动过程中的异常信息。- 此处,
exceptionReporters
属性的结果如下图: - 关于 SpringBootExceptionReporter ,感兴趣的胖友,自己研究先。
- 此处,
<8>
处,调用#prepareContext(...)
方法,主要是调用所有初始化类的#initialize(...)
方法。详细解析,见 「2.2.3 prepareContext」 。<9>
处,调用#refreshContext(ConfigurableApplicationContext context)` 方法,启动(刷新) Spring 容器。详细解析,见 「2.2.4 refreshContext」 。
-
<10>
处,调用#afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args)
方法,执行 Spring 容器的初始化的后置逻辑。默认实现为空。代码如下:// SpringApplication.java
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) -
<11>
处,停止 StopWatch 统计时长。 -
<12>
处,打印 Spring Boot 启动的时长日志。效果如下:2019-01-28 20:42:03.338 INFO 53001 --- [ main] c.iocoder.springboot.mvc.MVCApplication : Started MVCApplication in 20.893 seconds (JVM running for 23.536)
-
<13>
处,调用SpringApplicationRunListeners#started(ConfigurableApplicationContext context)
方法,通知 SpringApplicationRunListener 的数组,Spring 容器启动完成。 <14>
处,调用#callRunners(ApplicationContext context, ApplicationArguments args)
方法,调用 ApplicationRunner 或者 CommandLineRunner 的运行方法。详细解析,见 「2.2.5 callRunners」 。<14.1>
处,如果发生异常,则调用#handleRunFailure(...)
方法,交给 SpringBootExceptionReporter 进行处理,并抛出 IllegalStateException 异常。
<15>
处,调用SpringApplicationRunListeners#running(ConfigurableApplicationContext context)
方法,通知 SpringApplicationRunListener 的数组,Spring 容器运行中。<15.1>
处,如果发生异常,则调用#handleRunFailure(...)
方法,交给 SpringBootExceptionReporter 进行处理,并抛出 IllegalStateException 异常。
2.2.1 prepareEnvironment
艿艿:这个方法,大体看下即可。
#prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments)
方法,加载属性配置。代码如下:
// SpringApplication.java
|
-
<1>
处,调用#getOrCreateEnvironment()
方法,创建 ConfigurableEnvironment 对象。代码如下:// SpringApplication.java
private ConfigurableEnvironment getOrCreateEnvironment()
// 已经存在,则进行返回
if (this.environment != null)
return this.environment;
// 不存在,则根据 webApplicationType 类型,进行创建。
switch (this.webApplicationType)
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
- 根据
webApplicationType
类型,会创建不同类型的 ConfigurableEnvironment 对象。 - 例如说,Servlet 需要考虑
<servletContextInitParams />
和<servletConfigInitParams />
等配置参数。
- 根据
-
<1>
处,调用#configureEnvironment(ConfigurableEnvironment environment, String[] args)
方法,配置environment
变量。代码如下:// SpringApplication.java
/**
* 是否添加共享的 ConversionService
*/
private boolean addConversionService = true;
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args)
// <1.1> 设置 environment 的 conversionService 属性
if (this.addConversionService)
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
// <1.2> 增加 environment 的 PropertySource 属性源
configurePropertySources(environment, args);
// <1.3> 配置 environment 的 activeProfiles 属性
configureProfiles(environment, args);
<1.1>
处,设置environment
的conversionService
属性。可以暂时无视。感兴趣的胖友,可以看看 《【死磕 Spring】—— 环境 & 属性:PropertySource、Environment、Profile》-
<1.2>
处,增加environment
的 PropertySource 属性源。代码如下:// SpringApplication.java
/**
* 是否添加 JVM 启动参数
*/
private boolean addCommandLineProperties = true;
/**
* 默认的属性集合
*/
private Map<String, Object> defaultProperties;
protected void configurePropertySources(ConfigurableEnvironment environment,
String[] args)
MutablePropertySources sources = environment.getPropertySources();
// 配置的 defaultProperties
if (this.defaultProperties != null && !this.defaultProperties.isEmpty())
sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
// 来自启动参数的
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 // 不存在,就进行添加
sources.addFirst(new SimpleCommandLinePropertySource(args));
- 代码上可以看出,可以根据配置的
defaultProperties
、或者 JVM 启动参数,作为附加的 PropertySource 属性源。
- 代码上可以看出,可以根据配置的
-
<1.3>
处,配置environment
的activeProfiles
属性。代码如下:// SpringApplication.java
/**
* 附加的 profiles 的数组
*/
private Set<String> additionalProfiles = new HashSet<>();
protected void configureProfiles(ConfigurableEnvironment environment, String[] args)
environment.getActiveProfiles(); // ensure they are initialized 保证已经被初始化
// But these ones should go first (last wins in a property key clash)
Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
// 设置 activeProfiles
environment.setActiveProfiles(StringUtils.toStringArray(profiles));- 不了解 Profile 的胖友,可以看看 《Spring Boot 激活 profile 的几种方式》 文章。
-
<2>
处,调用SpringApplicationRunListeners#environmentPrepared(ConfigurableEnvironment environment)
方法,通知 SpringApplicationRunListener 的数组,环境变量已经准备完成。 <3>
处,调用#bindToSpringApplication(ConfigurableEnvironment environment)
方法,绑定environment
到 SpringApplication 上。暂时不太知道用途。<4>
处,如果非自定义environment
,则根据条件转换。默认情况下,isCustomEnvironment
为false
,所以会执行这块逻辑。但是,一般情况下,返回的还是environment
自身,所以可以无视这块逻辑先。<5>
处,调用ConfigurationPropertySources#attach(Environment environment)
静态方法,如果有 attach 到environment
上的 MutablePropertySources ,则添加到environment
的 PropertySource 中。这块逻辑,也可以先无视。
2.2.2 createApplicationContext
#createApplicationContext()
方法,创建 Spring 容器。代码如下:
// SpringApplication.java
|
- 根据
webApplicationType
类型,获得对应的 ApplicationContext 对象。
2.2.3 prepareContext
#prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner)
方法,准备 ApplicationContext 对象,主要是初始化它的一些属性。代码如下:
// SpringApplication.java
|
- 这个方法,还是蛮长的,主要是给
context
的属性做赋值,以及 ApplicationContextInitializer 的初始化。 <1>
处,设置context
的environment
属性。-
<2>
处,调用#postProcessApplicationContext(ConfigurableApplicationContext context)
方法,设置context
的一些属性。代码如下:// SpringApplication.java
protected void postProcessApplicationContext(ConfigurableApplicationContext context)
if (this.beanNameGenerator != null)
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>
处,调用#applyInitializers(ConfigurableApplicationContext context)
方法,初始化 ApplicationContextInitializer 。代码如下:// SpringApplication.java
protected void applyInitializers(ConfigurableApplicationContext context)
// 遍历 ApplicationContextInitializer 数组
for (ApplicationContextInitializer initializer : getInitializers())
// 校验 ApplicationContextInitializer 的泛型非空
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
// 初始化 ApplicationContextInitializer
initializer.initialize(context);
- 遍历 ApplicationContextInitializer 数组,逐个调用
ApplicationContextInitializer#initialize(context)
方法,进行初始化。
- 遍历 ApplicationContextInitializer 数组,逐个调用
<4>
处,调用SpringApplicationRunListeners#contextPrepared(ConfigurableApplicationContext context)
方法,通知 SpringApplicationRunListener 的数组,Spring 容器准备完成。-
<5>
处,打印日志。效果如下:2019-01-28 17:53:31.600 INFO 21846 --- [ main] c.iocoder.springboot.mvc.MVCApplication : Starting MVCApplication on MacBook-Pro-5.local with PID 21846 (/Users/yunai/Java/spring-boot/spring-boot-tests/spring-boot-yunai-tests/spring-boot-yunai-mvc-tests/target/classes started by yunai in /Users/yunai/Java/spring-boot)
2019-01-28 17:53:40.028 INFO 21846 --- [ main] c.iocoder.springboot.mvc.MVCApplication : The following profiles are active: prod- 具体的方法逻辑,胖友自己瞅瞅哈。
-
<6>
处,设置beanFactory
的属性。 -
<7>
处,调用#load(ApplicationContext context, Object[] sources)
方法,加载 BeanDefinition 们。代码如下:// SpringApplication.java
protected void load(ApplicationContext context, Object[] sources)
if (logger.isDebugEnabled())
logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
// <1> 创建 BeanDefinitionLoader 对象
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
// <2> 设置 loader 的属性
if (this.beanNameGenerator != null)
loader.setBeanNameGenerator(this.beanNameGenerator);
if (this.resourceLoader != null)
loader.setResourceLoader(this.resourceLoader);
if (this.environment != null)
loader.setEnvironment(this.environment);
// <3> 执行 BeanDefinition 加载
loader.load();-
<1>
处,调用#getBeanDefinitionRegistry(ApplicationContext context)
方法,创建 BeanDefinitionRegistry 对象。代码如下:// SpringApplication.java
private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context)
if (context instanceof BeanDefinitionRegistry)
return (BeanDefinitionRegistry) context;
if (context instanceof AbstractApplicationContext)
return (BeanDefinitionRegistry) ((AbstractApplicationContext) context)
.getBeanFactory();
throw new IllegalStateException("Could not locate BeanDefinitionRegistry");- 关于 BeanDefinitionRegistry 类,暂时不需要深入了解。感兴趣的胖友,可以看看 《【死磕 Spring】—— IoC 之 BeanDefinition 注册表:BeanDefinitionRegistry》 文章。
-
<1>
处,调用#createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources)
方法,创建org.springframework.boot.BeanDefinitionLoader
对象。关于它,后续的文章,详细解析。 <2>
处,设置loader
的属性。<3>
处,调用BeanDefinitionLoader#load()
方法,执行 BeanDefinition 加载。关于这一块,胖友感兴趣,先简单看看 《【死磕 Spring】—— IoC 之加载 BeanDefinition》 文章。
-
-
<8>
处,调用SpringApplicationRunListeners#contextLoaded(ConfigurableApplicationContext context)
方法,通知 SpringApplicationRunListener 的数组,Spring 容器加载完成。
2.2.4 refreshContext
#refreshContext(ConfigurableApplicationContext context)
方法,启动(刷新) Spring 容器。代码如下:
// SpringApplication.java
|
-
<1>
处,调用#refresh(ApplicationContext applicationContext)
方法,开启(刷新)Spring 容器。代码如下:// SpringApplication.java
protected void refresh(ApplicationContext applicationContext)
// 断言,判断 applicationContext 是 AbstractApplicationContext 的子类
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
// 启动(刷新) AbstractApplicationContext
((AbstractApplicationContext) applicationContext).refresh();- 调用
AbstractApplicationContext#refresh()
方法,启动(刷新)Spring 容器。AbstractApplicationContext#refresh()
方法,胖友可以看看 《【死磕 Spring】—— ApplicationContext 相关接口架构分析》 文章。- 这里,可以触发 Spring Boot 的自动配置的功能。关于这一块,我们会在下一篇文章,详细解析。
- 调用
<2>
处,调用ConfigurableApplicationContext#registerShutdownHook()
方法,注册 ShutdownHook 钩子。这个钩子,主要用于 Spring 应用的关闭时,销毁相应的 Bean 们。
2.2.5 callRunners
#callRunners(ApplicationContext context, ApplicationArguments args)
方法,调用 ApplicationRunner 或者 CommandLineRunner 的运行方法。代码如下:
// SpringApplication.java
|
<1>
处,获得所有 Runner 们,并进行排序。-
<2>
处,遍历 Runner 数组,执行逻辑。代码如下:// SpringApplication.java
private void callRunner(ApplicationRunner runner, ApplicationArguments args)
try
(runner).run(args);
catch (Exception ex)
throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
private void callRunner(CommandLineRunner runner, ApplicationArguments args)
try
(runner).run(args.getSourceArgs());
catch (Exception ex)
throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
关于 Runner 功能的使用,可以看看 《ApplicationRunner 接口》 和 《CommandLineRunner 接口》 文档。
3. SpringApplicationRunListeners
org.springframework.boot.SpringApplicationRunListeners
,SpringApplicationRunListener 数组的封装。代码如下:
// SpringApplicationRunListeners.java
|
3.1 SpringApplicationRunListener
org.springframework.boot.SpringApplicationRunListener
,SpringApplication 运行的监听器接口。代码如下:
// SpringApplicationRunListener.java
|
目前,SpringApplicationRunListener 的实现类,只有 EventPublishingRunListener 类。
3.2 EventPublishingRunListener
org.springframework.boot.context.event.EventPublishingRunListener
,实现 SpringApplicationRunListener、Ordered 接口,将 SpringApplicationRunListener 监听到的事件,转换成对应的 SpringApplicationEvent 事件,发布到监听器们。
代码如下:
// EventPublishingRunListener.java |