Spring Boot的启动流程
梳理Springboot执行流程的时候,先抓大的步骤,不要一上来就每个方法都跟进去看,那样不利于对整体的理解,且一会就看不下去了。要先理解整体的场景是什么,把握主要步骤,全理解之后可以在每个方法挨个点击去细看。
所以先看一下启动的整体流程步骤:
--------------------------------------创建SprigApplication对象----------------------------------
1.推断应用类型,判断是不是web应用
2.获取所有初始化容器(读取META-INFO/spring.factories文件-ApplicationContextInitializer)
3.获取所有监听器(读取META-INFO/spring.factories文件-ApplicationListener)
4.根据调用栈,推断出main方法的类名
------------------------------------------run方法执行流程----------------------------------------
1. 获取并启动监听器 (getRunListeners+listeners.starting())
2. 准备环境,将配置文件读取到容器中 (prepareEnvironment)
3. 准备Banner打印器,打印Spring图标 (printBanner)
4. 初始化应用上下文,创建spring容器 (createApplicationContext)
5. Spring容器前置处理 (prepareContext)
6. 刷新应用上下文 (prepareContext)
7. Spring容器后置处理 (afterRefresh)
8. 使用广播和回调机制告诉监听者springboot容器已经启动成功 (listeners.started,listeners.running)
下面开始详细介绍
首先,Spring Boot项目创建完成会默认生成一个名为 *Application 的入口类,我们是通过该类的main方法启动Spring Boot项目的。在main方法中,通过SpringApplication的静态方法,即run方法进行SpringApplication类的实例化操作,然后再针对实例化对象调用另外一个run方法来完成整个项目的初始化和启动。
会调用另外一个run方法
由此看到,主要干了两件事情:
- 构造SpringApplication实例
- 调用示例的run方法
下面就按照这两大点来展开
1 构造SpringApplication实例
构造SpringApplication实例主要干了以下几件事:
- 给primarySources变量赋值,传参可以是任意类型
- 获取应用类型,判断是不是web应用
- 获取所有初始化容器,并设置到initializers变量中
- 创建并初始化所有监听器,并设置到listeners变量中
- 初始化主类mainApplectionClass,定位main方法
下面对每一步的源码进行分析
经过两层过早方法,最终会进入下面这个核心的构造方法中
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
/**
* 【1 推断应用类型,判断是不是web应用(会根据类型初始化对应的环境,常用的一般都是servlet环境)】
* deduce:推断
*/
this.webApplicationType = WebApplicationType.deduceFromClasspath();
/**
* 【2 获取所有初始化容器】
*/
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
/**
* 【3 获取所有监听器】
*/
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
/**
* 【4 根据调用栈,推断出main方法的类名】
*/
this.mainApplicationClass = deduceMainApplicationClass();
}
首先会将参数sources赋值给primarySources变量
然后回判断应用类型是否是web应用,跟进deduceFromClasspath
方法
/**
* 判断是什么应用类型:三种类型
* NONE:非web应用,即不会启动服务器
* SERVLET:基于servlet的web应用
* REACTIVE:响应式web应用(暂未接触过)
* @return
*/
static WebApplicationType deduceFromClasspath() {
//classpath下是否存在org.springframework.web.reactive.DispatcherHandler
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;
}
}
//以servlet-web应用启动,并且启动内嵌的servlet web服务器
return WebApplicationType.SERVLET;
}
从返回结果来看,一共有三种类型
返回值 | 说明 |
---|---|
NONE | 非web应用,即不会启动服务器 |
SERVLET | 基于servlet的web应用 |
REACTIVE | 响应式web应用 |
判断一共设计四个常量,springboot在启动的时候,会对下面四个常量所对应的class进行判断,看看他们是否存在,从而返回应用类型。
WEBFLUX_INDICATOR_CLASS = “org.springframework.web.reactive.DispatcherHandler”;
WEBMVC_INDICATOR_CLASS = “org.springframework.web.servlet.DispatcherServlet”;
JERSEY_INDICATOR_CLASS = “org.glassfish.jersey.servlet.ServletContainer”;
SERVLET_INDICATOR_CLASSES = { “javax.servlet.Servlet”,
“org.springframework.web.context.ConfigurableWebApplicationContext” };
然后是获取所有初始化容器,跟进getSpringFactoriesInstances
。该方法是公共方法,会根据传入的class类型获取不同的实例。
getSpringFactoriesInstances
其实主要干了三件事:
- 获取所有初始化器的名称集合
- 根据名称集合实例化这些初始化器
- 对Spring工厂实例排序
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
/**
* 获取所有初始化器的名称集合 * SpringFactoriesLoader.loadFactoryNames:在META-INF/spring.factories配置文件里获取初始化器
*/
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//创建spring工厂实例 通过反射 成实例对象
/**
* 根据名称集合实例化这些初始化器
*/
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
//对Spring工厂实例排序(org.springframework.core.annotation.0rder注解指定的顺序)
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
其中SpringFactoriesLoader.loadFactoryNames(type, classLoader)
会返回所有容器的名称集合,其中type参数为获取该类别所属的容器名称。跟进方法:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//此时cache中已经与值了,此处直接返回(问题:此处的cache是什么时候被赋值的)
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
//加载META-INF/ spring.factories中的资源
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
loadSpringFactories
方法中有个核心步骤:classLoader.getResources(FACTORIES_RESOURCE_LOCATION)
和ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)
。顾名思义:用类加载器获取系统资源,传入的路径:FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
直白来说就是是去META-INF/spring.factories
配置文件中,拿到和传入的class类名一致的下面的所有实例类名。
例如:先传入的是ApplicationContextInitializer
,通过loadSpringFactories
读取所有spring.factories文件中的所有类名,在通过getOrDefault(factoryTypeName)
方法取出所需要的类名集合,如下图所示
通过此方法,容器的名称已经返回,再通过createSpringFactoriesInstances
方法进行实例化。返回后通过setInitializers
方法赋值给initializers
变量
下一步是获取所有监听器,也是调用getSpringFactoriesInstances
方法,只是传入的是ApplicationListener
,最后赋值给listeners
变量。原理通上一步一样。
最后初始化主类mainApplectionClass,进入deduceMainApplicationClass
方法
private Class<?> deduceMainApplicationClass() {
try {
// 通过创建运行时异常的方式获取栈
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;
}
到此是创建SpringbootApplication对象的过程,下面进入run方法
2 run方法执行流程
创建完对象,就要执行run方法的逻辑,该方法中主要步骤为以下几步:
- 获取并启动监听器
- 准备环境,将配置文件读取到容器中
- 准备Banner打印器,打印Spring图标
- 初始化应用上下文(创建Spring容器)
- Spring容器前置处理
- 刷新应用上下文
- Spring容器后置处理
- 使用广播和回调机制告诉监听者springboot容器已经启动成功
下面我们跟进run方法的源码:
public ConfigurableApplicationContext run(String... args) {
//记录程序运行时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
/**
* 【1 获取并启动监听器】
*/
SpringApplicationRunListeners listeners = getRunListeners(args);
// 循环遍历启动监听器
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
/**
* 【2 准备环境,将配置文件读取到容器中】
*/
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//处理需要忽略的bean
configureIgnoreBeanInfo(environment);
/**
* 【3 准备Banner打印器,打印Spring图标】
*/
Banner printedBanner = printBanner(environment);
/**
* 【4 初始化应用上下文】
*/
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
/**
* 【5 Spring容器前置处理】
*/
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
/**
* 【6 刷新容器】
*/
refreshContext(context);
/**
* 【7 Spring容器后置处理】
*/
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
/**
* 【8 使用广播和回调机制告诉监听者springboot容器已经启动化成功】
*/
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
下面我们一个一个步骤进行分析
首先是获取并启动监听器,getRunListeners
这一步跟创建SpringbootApplication对象时获取监听对象操作基本一致,都是读取/META-INF/spring.factories
拿到ApplicationListener下的所有类
然后是进行环境准备,是为了查找并设置配置文件信息到 environment
中。打印Spring图标,然后对应用上下文进行初始化,进入createApplicationContext()
方法
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
来赋值不同的类型,并通过BeanUtils.instantiateClass
实例化
IOC 容器就是上面创建的 context 对象,它的类型是:AnnotationConfigServletWebServerApplicationContext,这个类名翻译过来是”基于注解配置的内嵌了 servlet 的 web服务应用程序上下文“
接下来是执行prepareContext
方法,前置处理其实也给各个变量赋值,进入方法:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//设置容器环境
context.setEnvironment(environment);
//执行容器后置处理
postProcessApplicationContext(context);
//执行初始化器
applyInitializers(context);
listeners.contextPrepared(context);//向各个监听器发送容器已经准备好的事件
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//将main函数中的args参数封装成单例Bean,注册进容器
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
//将printedBanner也封装成单例,注册进容器
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources 拿到的是主类
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载我们的启动类,将启动类生成实例对象注入容器
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
前置环境准备完,开始刷新上下文应用,执行refreshContext(context)
方法,该方法将应用程序运行所需要的所有 bean(包括开发者自己写的) 加载到了 context 的 beanFactory 中。
刷新 context 中最主要也是最重要的一件事情是(自动配置):将要加载的类做成 bean 保存(注册)到 context 中。具体流程是这样的: 要自动配置的类得先写好了,加上注解;( 在启动过程中调用
BeanFactoryPostProcesser
的某个实现类,它为需要自动配置的每一个类创造一个对应的beanDefinition
,并将所有的beanDefinition
保存(注册) 到 context 中。(实际上是为每一个类构建键值对 className:BeanDefinion 保存到了 context 中的beanFactory
属性的beanDefinitionMap
属性中)。 接着再遍历 beanDefinitionMap,根据 className 从 context 中先获取对应的 bean,如果能拿到,就表示这个类已经被注册了,如果拿不到,那就通过 BeanDefinion 创建一个新 bean 注册到 context中。(实际上是从 context 中的 beanFactory 属性的 singletonObject 属性中拿 bean,如果拿不到,创建的新 bean 也是保存到 signletonObject 中)。
执行完refreshContext
方法,后面不是重点了,根据注释捋一捋主题步骤即可