前两个小节,我们分别了解了SpringBootApplication注解和自动配置的加载过程,这一小节就可以正式地探究SpringBoot的启动原理了。
通常,我们运行SpringBoot项目启动类的main函数,就可以顺利的启动整个SpringBoot项目了。在我们等待SpringBoot项目启动的过程中,除了对着console打印区发呆,有没有想过这段时间SpringBoot在做什么?
让我们从SpringApplication.run方法开始,追踪一下启动过程中SpringBoot具体做了哪些事吧。
首先进入run方法:
public static ConfigurableApplicationContext run(Class>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
发现run方法创建了一个SpringApplication实例然后又调用的这个实例的run方法,在下面的构造方法中主要是给SpringApplication实例赋一些初始值:
// resourceLoader是null,只有第二个参数是我们项目的启动类
public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = Collections.emptySet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
this.applicationStartup = ApplicationStartup.DEFAULT;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
我们接着看SpringApplication实例的run方法,方法比较长,主要关注以下几句:
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
// 配置应用的上下文
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
// 创建了listeners
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 应用配置环境
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
// 将listeners等重要组件和上下文关联
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 自动化配置的关键
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
this.callRunners(context, applicationArguments);
} catch (Throwable var12) {
this.handleRunFailure(context, var12, listeners);
throw new IllegalStateException(var12);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
return context;
} catch (Throwable var11) {
this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var11);
}
}
在梳理run方法的具体细节之前,我们先关注一下启动过程中比较核心的内容:ConfigurableApplicationContext(配置容器上下文),SpringApplicationRunListeners(监听器)和ConfigurableEnvironment(配置环境)
ConfigurableApplicationContext
它是run方法的返回值,它继承了ApplicationContext(所有容器的基类),内部有一些静态常量和方法:
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
// ...
}
它是可配置的 ApplicationContext ,用户可以根据自己的需要配置应用上下文,在SpringBoot启动时,
它的主要作用是配置一些bean name名称,设置容器id,设置环境变量,设置bean factory的后置处理器,设置监听器,设置类加载器等。
SpringApplicationRunListeners
SpringApplicationRunListener 接口的作用主要就是在Spring Boot 启动初始化的过程中可以通过SpringApplicationRunListener接口回调来让用户在启动的各个流程中可以加入自己的逻辑。
package org.springframework.boot;
public interface SpringApplicationRunListener {
// 1.开始启动:在run()方法开始执行时,该方法就立即被调用,可用于在初始化最早期时做一些工作
void starting();
// 2.Environment构建完成:当environment构建完成,ApplicationContext创建之前,该方法被调用
void environmentPrepared(ConfigurableEnvironment environment);
// 3.ApplicationContext构建完成:当ApplicationContext构建完成时,该方法被调用
void contextPrepared(ConfigurableApplicationContext context);
// 4.ApplicationContext完成加载:在ApplicationContext完成加载,但没有被刷新前,该方法被调用
void contextLoaded(ConfigurableApplicationContext context);
// 5.ApplicationContext完成刷新并启动:在ApplicationContext刷新并启动后,CommandLineRunners和ApplicationRunner未被调用前,该方法被调用
void started(ConfigurableApplicationContext context);
// 6.启动完成:在run()方法执行完成前该方法被调用
void running(ConfigurableApplicationContext context);
// 7.启动失败:当应用运行出错时该方法被调用
void failed(ConfigurableApplicationContext context, Throwable exception);
}
SpringApplicationRunListener的接口被启动流程中的事件点的实现原理?
EventPublishingRunListener类实现了SpringApplicationRunListener,它具有广播事件的功能。
它使用了Spring广播器SimpleApplicationEventMulticaster:
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
Iterator var3 = application.getListeners().iterator();
while(var3.hasNext()) {
ApplicationListener> listener = (ApplicationListener)var3.next();
this.initialMulticaster.addApplicationListener(listener);
}
}
ConfigurableEnvironment
Environment是spring中配置文件数据的载体,对外暴露使用的配置文件名称,如:profiles.active,这样我们就可以知道在使用哪个配置文件的信息了。
ConfigurableEnvironment继承于Environment,并且ConfigurableEnvironment还继承了ConfigurablePropertyResolver,而ConfigurablePropertyResolver继承于PropertyResolver。
对于PropertyResolver上一章已经说了,它是对于键值属性PropertySource对外数据的统一处理。实现它来获取PropertySource的数据。
所以ConfigurableEnvironment不仅提供了配置文件解析的数据,以及配置文件名称,还提供了PropertySource数据。其实配置文件的解析出来的数据,也是封装成了PropertySource放在ConfigurableEnvironment中。
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
//设置活动的配置文件
void setActiveProfiles(String... profiles);
// 增加活动的配置文件
void addActiveProfile(String profile);
// 设置默认的配置文件
void setDefaultProfiles(String... profiles);
// 获取PropertySource键值组合的集合
// 该方法返回一个可编辑的PropertySources,
// 如果有在启动阶段自定义环境的PropertySources的需求,就可以通过该方法设置
MutablePropertySources getPropertySources();
// 系统环境变量
Map getSystemEnvironment();
// 系统配置
Map getSystemProperties();
// 合并
void merge(ConfigurableEnvironment parent);
}
ConfigurableEnvironment只是接口,它的具体实现是AbstractEnvironment类。
ConfigurableEnvironment是spring中非常重要的角色,可以通过它获得activeProfiles,来判断我们所使用的环境是dev还是test或者prod等等。还可以根据getProperty拿到配置中的信息并且提供类型转换功能,以及获取系统环境变量等信息:
// run()方法中的加载配置环境的代码:
// ConfigurableEnvironment environment =
// this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// 根据不同环境获取不同的Enviroment
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
// 填充启动类参数到enviroment 对象
this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
// 更新参数
ConfigurationPropertySources.attach((Environment)environment);
// 发布事件
listeners.environmentPrepared(bootstrapContext, (ConfigurableEnvironment)environment);
DefaultPropertiesPropertySource.moveToEnd((ConfigurableEnvironment)environment);
Assert.state(!((ConfigurableEnvironment)environment).containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");
// 绑定到主类
this.bindToSpringApplication((ConfigurableEnvironment)environment);
if (!this.isCustomEnvironment) {
EnvironmentConverter environmentConverter = new EnvironmentConverter(this.getClassLoader());
environment = environmentConverter.convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
}
// 将现有参数有封装成proertySources
ConfigurationPropertySources.attach((Environment)environment);
return (ConfigurableEnvironment)environment;
}
在了解上面的关键组件后,启动流程就比较清晰了,其实整个启动过程可以分为两个步骤:
其中run()方法就是配置ConfigurableApplicationContext的过程,将重要的listener,environment和content关联起来并完成自动化配置,在此过程中重要的事件被发布出去,从而回调listener的对应接口:
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
// 1. 创建了监听器isteners并启动监听
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 2.加载配置环境ConfigurableEnvironment
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
// 3.将listeners,environment等重要组件和要返回的
// ConfigurableApplicationContext上下文关联
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 4.refresh后完成自动化配置
this.refreshContext(context);
// 给实现类留的钩子,这里是一个空方法
this.afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
this.callRunners(context, applicationArguments);
} catch (Throwable var12) {
this.handleRunFailure(context, var12, listeners);
throw new IllegalStateException(var12);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
// 5.返回配置好的ConfigurableApplicationContext
return context;
} catch (Throwable var11) {
this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var11);
}
}
其中两条语句需要关注:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//绑定环境
context.setEnvironment(environment);
//如果application有设置beanNameGenerator、resourceLoader就将其注入到上下文中,并将转换工具也注入到上下文中
postProcessApplicationContext(context);
//调用初始化的切面
applyInitializers(context);
//发布ApplicationContextInitializedEvent事件
listeners.contextPrepared(context);
//日志
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
//如果bean名相同的话是否允许覆盖,默认为false,相同会抛出异常
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
// 这里获取到的是BootstrapImportSelectorConfiguration这个class,而不是自己写的启动来,这个class是在之前注册的BootstrapApplicationListener的监听方法中注入的
Set
this.refreshContext(context)的实现在AbstractApplicationContext类里:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//记录启动时间、状态,web容器初始化其property,复制listener
prepareRefresh();
//这里返回的是context的BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//beanFactory注入一些标准组件,例如ApplicationContextAwareProcessor,ClassLoader等
prepareBeanFactory(beanFactory);
try {
//给实现类留的一个钩子,例如注入BeanPostProcessors,这里是个空方法
postProcessBeanFactory(beanFactory);
// 调用切面方法
invokeBeanFactoryPostProcessors(beanFactory);
// 注册切面bean
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// bean工厂注册一个key为applicationEventMulticaster的广播器
initApplicationEventMulticaster();
// 给实现类留的一钩子,可以执行其他refresh的工作,这里是个空方法
onRefresh();
// 将listener注册到广播器中
registerListeners();
// 实例化未实例化的bean
finishBeanFactoryInitialization(beanFactory);
// 清理缓存,注入DefaultLifecycleProcessor,发布ContextRefreshedEvent
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
下面的图片对启动流程做了一个总结: