SpringApplication
是SpringBoot
提供的帮助应用程序启动的引导类,它负责
- 创建合适的
ApplicationContext
- 将命令行参数融入
Environment
抽象 - 刷新
ApplicationContext
,初始化所有的singleton bean
- 触发所有的
CommandLineRunner
回调
本文中的源码分析基于spring-boot-2.1.5.RELEASE
,各位看官还请注意下版本。
构造函数
大多数时候,我们使用SpringApplication#run(...)
工厂方法来创建SpringApplication
实例,其实本质上都是对构造函数的调用。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 资源加载器,一般为空,因为 ApplicationContext 本身实现了 ResourceLoader 接口
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 主配置类,一般情况下是标注了 @SpringBootApplication 注解的类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 根据 jar 包的引入情况判定应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 获取 spring.factories 中配置的 ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 获取 spring.factories 中配置的 ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 获取 main 函数入口类
this.mainApplicationClass = deduceMainApplicationClass();
}
primarySources
是创建ApplicationContext
时传递给它的主配置类,一般情况下是标注了@SpringBootApplication
注解的类;webApplicationType
则根据类路径下jar
包的引入情况来进行推断,比如引入了spring-boot-starter-web
就会被推断为WebApplicationType.SERVLET
;#setInitializers(...)
和#setListeners(...)
则使用之前讲解过的Spring SPI
机制来获取对应的实现;最后mainApplicationClass
则对应调用栈中声明了main
函数的类。
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) {
ClassLoader classLoader = getClassLoader();
// 使用 Set 防止重复
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 反射创建实例,其中 parameterTypes 是构造函数形参类型,args 是实参
// 具体代码基本上就是 clazz.getDeclaredConstructor(parameterTypes).newInstance(args)
// 节约篇幅,就不放出来了
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 基于 @Order/Ordered 排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
这里对Spring SPI
机制的使用是非常直接的,同时也告诉了我们如何注册自定义的ApplicationContextInitializer
和ApplicationListener
,比如在自定义starter
的META-INF/spring.factories
中加入:
org.springframework.context.ApplicationListener=example.MyApplicationListener
就完成了对MyApplicationListener
的注册。
启动过程
public ConfigurableApplicationContext run(String... args) {
// 用于记录启动时长
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
// 容纳 SPI SpringBootExceptionReporter 的实现
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 服务端程序一般都是没有UI的
configureHeadlessProperty();
// 获取 SPI SpringApplicationRunListener 的实现
SpringApplicationRunListeners listeners = getRunListeners(args);
// 回调 SpringApplicationRunListener#starting(...),此时 #run(...) 刚开始运行
listeners.starting();
try {
// 封装命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 1. 创建 Environment 并进行配置
// 2. 回调 SpringApplicationRunListener#environmentPrepared(...),此时 Environment 已创建
// 3. 为 ConfigurationProperties binding 做准备
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印 banner
Banner printedBanner = printBanner(environment);
// 根据 webApplicationType 来创建 ApplicationContext
context = createApplicationContext();
// 获取 SPI SpringBootExceptionReporter 的实现
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 1. 配置 ApplicationContext
// 2. 运行 ApplicationInitializer(s)
// 3. 回调 SpringApplicationRunListener#contextPrepared(...),
// 此时 ApplicationContext 已经创建,但 BeanDefinition 还未加载
// 4. 加载 Bean 定义信息,但不刷新容器,此时已经有了创建 Bean 的蓝图 BeanDefinition
// 5. 回调 SpringApplicationRunListener#contextLoaded(...),此时 BeanDefinition 已经
// 悉数加载完毕,但容器未刷新,对应的 bean 实例还未创建
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新容器,初始化所有 non-lazy-init 的 bean
refreshContext(context);
// 钩子函数
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 回调 SpringApplicationRunListener#started(...),此时容器已刷新,bean 也初始化完毕
listeners.started(context);
// 回调容器中所有的 ApplicationRunner 和 CommandLineRunner
callRunners(context, applicationArguments);
} catch (Throwable ex) {
// 1. 回调 SpringApplicationRunListener#failed(...)
// 2. 使用 SpringBootExceptionReporter 来处理异常
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 回调 SpringApplicationRunListener#running(...),此时不仅容器已刷新,各种 *Runner 也已回调
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
#run(...)
方法虽然略长,逻辑却是很清晰的:
- 创建
Environment
并进行配置 - 创建
ApplicationContext
并进行配置 - 加载配置类并解析出对应的
BeanDefinition
- 刷新容器,根据
BeanDefinition
创建Bean
- 回调
*Runner
接口
SpringApplicationRunListener
则提供了对#run(...)
过程中关键节点的回调,默认注册有EventPublishingRunListener
——它使用ApplicationEventMulticaster
发出对应的事件。其中比较重要的有ApplicationEnvironmentPreparedEvent
和ApplicationReadyEvent
,SpringCloud
中的很多特性,比如Bootstrap Context
的创建和Environment
的动态刷新就是基于这些事件来触发的。SpringBootExceptionReporter
则提供了对#run(...)
过程中异常的处理,默认注册的FailureAnalyzers
会打印异常信息并提供一个解决方案,类似下面的输出相信大家都不会陌生(笑)。
***************************
APPLICATION FAILED TO START
***************************
Description:
Field restTemplate in example.TestController required a single bean, but 2 were found:
- restTemplate1: defined by method 'restTemplate1' in class path resource [example/Config.class]
- restTemplate2: defined by method 'restTemplate2' in class path resource [example/Config.class]
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
创建Environment
并进行配置
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 根据 webApplicationType 来创建 Environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 1. 添加 ConversionService 用于类型转换
// 2. 添加 defaultProperties 和 commandLineArgs 两个 PropertySource
// 3. 合并 additionalProfiles 到 activeProfiles
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 回调,发出 ApplicationEnvironmentPreparedEvent 事件
listeners.environmentPrepared(environment);
// 将 spring.main 前缀的配置项应用到 SpringApplication,比如 allowBeanDefinitionOverriding
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
// 将 PropertySource 转换成 ConfigurationPropertySource
// 这一步是为了支持 ConfigurationProperties binding
ConfigurationPropertySources.attach(environment);
return environment;
}
创建ApplicationContext
并进行配置
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_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);
}
根据webApplicationType
创建对应的ApplicationContext
,比如引入了spring-boot-starter-web
就会创建AnnotationConfigServletWebServerApplicationContext
,进而启动内嵌的Tomcat
容器,这部分内容之前已经聊过了,大家可以翻翻看。
加载配置类并解析出对应的BeanDefinition
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
// 设置使用的 Environment
context.setEnvironment(environment);
// 设置 ResourceLoader、ClassLoader和ConversionService
postProcessApplicationContext(context);
// 应用上前面加载好的 ApplicationContextInitializer
applyInitializers(context);
// 回调,发出 ApplicationPreparedEvent 事件
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 单独注册一下几个比较特殊的 Bean
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
// 将 allowBeanDefinitionOverriding 配置项应用到 ApplicationContext 底层的 BeanFactory
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// primarySources 和以编程方式加入的其它配置源
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 从以上配置源中解析出 BeanDefinition
// 这部分属于 spring-context 包的内容了,无非是使用 AnnotatedBeanDefinitionReader
// ClassPathBeanDefinitionScanner 等组件来加载并解析
load(context, sources.toArray(new Object[0]));
// 回调,发出 ApplicationPreparedEvent 事件
listeners.contextLoaded(context);
}
大家很熟悉的自动装配就发生在这一阶段,@EnableAutoConfiguration
注解一般是标注在primarySource
上的,看一下它的定义:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* 待排除的自动配置类
*/
Class<?>[] exclude() default {};
/**
* 待排除的自动配置类名
*/
String[] excludeName() default {};
}
它通过@Import
注解向容器中导入了一个ImportSelector
接口的实现——AutoConfigurationImportSelector
,并在这里实现了自动装配逻辑。鉴于这部分内容比较多,咱们下次可以展开聊聊。
刷新容器,根据BeanDefinition
创建Bean
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
刷新容器直接调用ApplicationContext#refresh()
方法即可,这将导致所有non-lazy-init
的singleton bean
得到初始化。
回调*Runner
接口
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
// 获取容器中所有类型为 ApplicationRunner 的 bean
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
// 获取容器中所有类型为 CommandLineRunner 的 bean
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
// 基于 @Order/Ordered 排序
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);
}
}
}
这部分代码是自解释的,不多提了。
结语
今天我们一起分析了SpringBoot
应用的启动流程,也仅仅是主体的启动流程。Spring
本身是完全开放、极易扩展的,大家也看到了伴随着应用启动的各种回调,众多的SpringBoot
特性都是在这些扩展点中得到实现的,这也是接下来的文章要分析的内容。
网友评论