springboot启动流程、日志分析
只关注info级别日志
1. 主类启动,通过调用SpringApplication#run(Class<?> primarySource, String... args)
方法启动程序
public static void main(String[] args) {
SpringApplication.run(AppConfig.class, args);
}
2. SpringApplication实例化、并调用run(Class<?>[] primarySources, String[] args)
方法启动
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);
}
2.1. 创建SpringApplication对象
主要设置应用类型、初始化器、监听器
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
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();
}
resourceLoader
:资源加载器,无默认值,此处即为null
,用途参考文章:IoC 之 Spring 统一资源加载策略primarySources
:主要的Java Config 类的数组,此处即为服务启动的主类cn.javadog.demo.springboot.AppConfig
webApplicationType
:Web 应用类型,调用WebApplicationType#deduceFromClasspath()
方法,通过classpath推断,此处即为SERVLET
- 具体类型取决于引入的maven依赖,如果引入的为
spring-boot-starter-webflux
则类型为REACTIVE
,如果引入的为spring-boot-starter-web
则类型为SERVLET
,都为引入则为null
- 这个属性,在下面的
createApplicationContext()
方法,将根据它的值(类型),创建不同类型的 ApplicationContext 对象,即 Spring 容器的类型不同。
- 具体类型取决于引入的maven依赖,如果引入的为
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;
}
initializers
: ApplicationContextInitializer 数组- 通过
getSpringFactoriesInstances(Class<T> type)
方法,进行获得 ApplicationContextInitializer 类型的对象数组,详细的解析,见「2.1.1 getSpringFactoriesInstances」 方法。 - 当前只在 Spring MVC 的环境下,initializers 属性的结果如下图:
- 通过
listeners
:ApplicationListener 数组- 也是通过 #getSpringFactoriesInstances(Class type) 方法,进行获得 ApplicationListener 类型的对象数组
- 当前只在 Spring MVC 的环境下,initializers 属性的结果如下图:
mainApplicationClass
:程序启动main方法所在的类,此处即为服务启动的主类cn.javadog.demo.springboot.AppConfig
- 这个 mainApplicationClass 属性,没有什么逻辑上的用途,主要就是用来打印下日志,说明是通过这个类启动 Spring 应用的
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
2.1.1 从META-INF/spring.factories
中指定类对应的实例对象
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();
// 加载指定类型对应的实现类,去重,在 `META-INF/spring.factories` 里的类名的数组
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 创建对象,具体实现不深究
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 将对象进行排序,主要是针对类上有`@Order`注解的
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
2.2 运行run(String... args)
方法启动spring
程序,注意是spring
程序!
public ConfigurableApplicationContext run(String... args) {
// <1> 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// <2> 配置 headless 属性
configureHeadlessProperty();
// <3> 获得 SpringApplicationRunListener 的数组,并启动监听
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// <4> 创建ApplicationArguments对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// <5> 加载属性配置。执行完成后,所有的 environment 的属性都会加载进来,包括 application.properties 和外部的属性配置。
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// <6> 打印 Spring Banner
Banner printedBanner = printBanner(environment);
// <7> 创建 Spring 容器。
context = createApplicationContext();
// <8> 异常报告器
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// <9> 主要是调用所有初始化类的 initialize 方法
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// <10> 初始化 Spring 容器。
refreshContext(context);
// <11> 执行 Spring 容器的初始化的后置逻辑。默认实现为空。
afterRefresh(context, applicationArguments);
// <12> 停止 StopWatch 统计时长
stopWatch.stop();
// <13> 打印 Spring Boot 启动的时长日志。
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// <13> 通知 SpringApplicationRunListener 的数组,Spring 容器启动完成。
listeners.started(context);
// <14> 调用 ApplicationRunner 或者 CommandLineRunner 的运行方法。
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// <14.1> 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
// <15> 通知 SpringApplicationRunListener 的数组,Spring 容器运行中。
try {
listeners.running(context);
}
catch (Throwable ex) {
// <15.1> 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
2.2.1 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长
逻辑比较简单,就是创建StopWatch对象,其内部有个TaskInfo
内部类,记录一次任务的持续时间和任务名称,结果放在StopWatch的taskList
属性里;本项目持续时间即为Spring容器初始化的时间,任务名称为空字符串
2.2.2 配置headless
属性
这个逻辑,可以无视,和AWT(硬件)相关
2.2.3 获得 SpringApplicationRunListener 的数组,并启动监听
实际类型为SpringApplicationRunListener
的实现类EventPublishingRunListener
,实现 SpringApplicationRunListener、Ordered 接口,将 SpringApplicationRunListener 监听到的事件,转换成对应的 SpringApplicationEvent 事件,发布到监听器。具体作用暂时不深究
2.2.4 创建ApplicationArguments对象
此处的args
目前为空数组!可以通过命令行等方式设置参数
2.2.5 加载属性配置
调用prepareEnvironment(...)
方法,加载属性配置。执行完成后,所有的 environment 的属性都会加载进来,包括 application.properties 和外部的属性配置,具体原理不深究,参见:《【死磕 Spring】—— 环境 & 属性:PropertySource、Environment、Profile》
- 根据 webApplicationType 类型,会创建不同类型的 ConfigurableEnvironment 对象
- 主要配置environment有两大属性:
propertySources
和activeProfiles
propertySources
:根据配置的 defaultProperties、或者 JVM 启动参数,作为附加的 PropertySource 属性源,本项目都没有做配置activeProfiles
:激活的profile,未指定即为空数组,可以同时指定多个
- 通知 SpringApplicationRunListener 的数组,环境变量已经准备完成,相关监听器会产生一些 debug 级别日志的日志,不去深究
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
// Create and configure the environment
// <1> 创建 ConfigurableEnvironment 对象,并进行配置
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
// <2> 通知 SpringApplicationRunListener 的数组,环境变量已经准备完成。
listeners.environmentPrepared(environment);
// <3> 绑定 environment 到 SpringApplication 上,暂时不太知道用途。
bindToSpringApplication(environment);
// <4> 如果非自定义 environment ,则根据条件转换。认情况下,isCustomEnvironment 为 false ,所以会执行这块逻辑。但是,一般情况下,返回的还是 environment 自身,所以可以无视这块逻辑先。
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
// <5> 如果有 attach 到 environment 上的 MutablePropertySources ,则添加到 environment 的 PropertySource 中。
ConfigurationPropertySources.attach(environment);
return environment;
}
2.2.6 打印 Spring Banner
打印的第一个日志,样式如下
- 默认的是
org.springframework.boot.SpringBootBanner
打印的spring的启动banner - 核心逻辑如下,没有自定义的banner就取默认的,可以通过在路径下添加
banner.txt
或者指定spring.banner.location
属性进行更换
private Banner getBanner(Environment environment) {
Banners banners = new Banners();
banners.addIfNotNull(getImageBanner(environment));
banners.addIfNotNull(getTextBanner(environment));
if (banners.hasAtLeastOneBanner()) {
return banners;
}
if (this.fallbackBanner != null) {
return this.fallbackBanner;
}
return DEFAULT_BANNER;
}
2.2.7 调用createApplicationContext()
创建 Spring 容器
具体解析见下方
2.2.8 创建异常报告器
此处exceptionReporters
属性如下,不做深究:
2.2.9 调用 prepareContext(...)
方法,主要是调用所有初始化类的 initialize(...)
方法
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// <1> 设置 context 的 environment 属性
context.setEnvironment(environment);
// <2> 设置 context 的一些属性
postProcessApplicationContext(context);
// <3> 初始化 ApplicationContextInitializer
applyInitializers(context);
// <4> 通知 SpringApplicationRunListener 的数组,Spring 容器准备完成。
listeners.contextPrepared(context);
// <5> 打印日志
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
// <6> 设置 beanFactory 的属性
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// Load the sources
// <7> 加载 BeanDefinition 们
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
// <8> 通知 SpringApplicationRunListener 的数组,Spring 容器加载完成。
listeners.contextLoaded(context);
}
2.2.10 调用 refreshContext(ConfigurableApplicationContext context)
方法,启动(刷新) Spring 容器
具体解析见下方
2.2.11 调用afterRefresh(...)
方法,执行 Spring容器的初始化的后置逻辑。 默认实现为空。代码如下:
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}