经过前面《SpringBoot2.1.x源码阅读环境搭建详解》,本节主要内容----SpringBoot启动流程源码分析。
首先看下环境准备:
项目/工具 | 版本 |
SpringBoot | v2.1.x |
spring | v5.1.x |
maven | v3.5.4 |
SpringBoot框架减少的大量的文件配置,框架集成便捷,给项目开发带来了很多便利。SpringBoot项目一般都会有注解*Application标注的入口类,入口类会有一个main方法,main方法是一个标准的Java应用程序的入口,可以直接启动。
OK,废话不多说,进入正题。
在入口程序,我们可以看到其引入了@SpringBootApplication这个注解,它是SpringBoot的核心注解,用此注解标注的入口类是应用的启动类,通常会在启动类的在main()方法中创建了SpringApplication类的实例,然后调用该类的run()方法来启动SpringBoot项目。
@SpringBootApplication
public class SpringBootAnalysisApplication {
private static final Logger logger = LoggerFactory.getLogger(SpringStudyPractise.class);
public static void main(String[] args) {
logger.debug("================正在启动==============");
SpringApplication app = new SpringApplication(SpringStudyPractise.class);
app.run(args);
logger.debug("================启动成功==============");
}
}
@SpringBootApplication其实是一个组合注解。源码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class>[] scanBasePackageClasses() default {};
}
这个注解主要组合了以下注解:
【1】@SpringBootConfiguration:它是SpringBoot项目的配置注解,也是一个组合注解,源码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
在SpringBoot项目中推荐使用@SpringBootConfiguration注解来替代@Configuration注解。
【2】@EnableAutoConfiguration:启动自动配置,该注解会让SpringBoot根据当前项目所依赖的jar包自动配置项目的相关配置项。
【3】@ComponentScan:扫描配置,SpringBoot默认会扫描@SpringBootApplication所在类的同级包以及它的子包,所以建议将@SpringBootApplication修饰的入口类放置在项目包下(Group Id + Artifact Id),这样做的好处是,可以保证SpringBoot项目自动扫描到项目所有的包。
而main方法中这个SpringApplication实例所提供的run()方法只应用主程序开始的运行,SpringApplication这个类可用于从Java主方法引导和启动Spring应用程序。
OK,继续往下主程序启动流程。
启动主程序main方法,初始化SpringApplication实例对象:
/**
* 创建一个新的{@link SpringApplication}实例。
* 应用程序上下文将从指定的主要源加载bean。可以在调用{@link #run(String…)}之前定制实例。
* @param resourceLoader 资源加载器使用
* @param primarySources bean对象
*/
@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));
//【1】设置servlet环境
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//【2】获取ApplicationContextInitializer,也是在这里开始首次加载spring.factories文件
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//【3】获取监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
首先,来看一下判断Web环境的deduceFromClassoath()
方法:
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;
}
【1】这里主要是通过判断REACTIVE
相关的字节码是否存在,如果不存在,则web环境即为SERVLET
类型。这里设置好web环境类型,在后面会根据类型初始化对应环境。
继续往下看【2】getSpringFactoriesInstances()方法:它以ApplicationContextInitializer接口类为入参,是
spring组件spring-context
组件中的一个接口,主要是spring ioc容器刷新之前的一个回调接口,用于处于自定义逻辑。
private Collection getSpringFactoriesInstances(Class type, Class>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
//在这里,将加载sprin.factories
Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
这里看一下names集合的入参的loadFactoryNames()方法,这里第一次加载META-INF/spring.factories文件,
在加载了spring.factories文件后,会将配置文件中的各个属性设置加入缓存中。同时,这里也加载了ApplicationListener监听器,这10个监听器会贯穿springBoot整个生命周期。
关于SpringApplication的实例化流程,限于篇幅,将于后面内容进行分析。随后,SpringBoot将进入自动化启动流程。
这里,进入SpringApplication的run()方法:
/**
* 运行这个Spring应用,创建并产生一个新的应用上下文
* {@link ApplicationContext}.
* @param args 来自于Java程序main方法中的参数
* @return a running
*/
public ConfigurableApplicationContext run(String... args) {
//【1】初始化时间监控器
StopWatch stopWatch = new StopWatch();
//开始记录启动时间,启动一个未命名的任务。如果在不调用该方法的情况下调用{@link #stop()}或计时方法,则结果是未定义的。
stopWatch.start();
ConfigurableApplicationContext context = null;
//【2】初始化Spring异常报告集合
Collection exceptionReporters = new ArrayList<>();
//【3】java.awt.headless是J2SE的一种模式用于在缺少显示屏、键盘或者鼠标时的系统配置
// 很多监控工具如jconsole 需要将该值设置为true,系统变量默认为true
configureHeadlessProperty();
//【4】获取spring.factories中的监听器变量
// 参数args:为指定的参数数组,默认为当前类SpringApplication
//获取并启动监听器,即初始化监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
//在run方法第一次启动时立即调用。可以用于非常早期的初始化。
listeners.starting();
try {
//【5】装配参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//【6】初始化容器环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//【7】设置需要忽略的Bean信息
configureIgnoreBeanInfo(environment);
//【7.1】打印banner,启动的Banner就是在这一步打印出来的。
Banner printedBanner = printBanner(environment);
//【8】创建容器
context = createApplicationContext();
//【9】实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//【10】准备容器,装配参数:容器属性、容器环境、监听器属性、应用对象
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//【11】刷新容器
refreshContext(context);
//【12】刷新容器后的拓展接口,在容器被刷新之后调用。
afterRefresh(context, applicationArguments);
//【13】停止监听器
stopWatch.stop();
//【14】在启动时记录应用程序日志信息。
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//【15】使用广播和回调机制通知监听器springboot容器启动成功(容器已经被刷新,应用程序已经启动)
listeners.started(context);
【16】容器bean的回调。
callRunners(context, applicationArguments);
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
【17】使用广播和回调机制通知监听器springboot容器已成功running
listeners.running(context);
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
【18】返回容器
return context;
}
Spring中加载一个应用,主要是通过一些复杂的配置实现,这里看来,SpringBoot帮我们将这些配置工作提前实现了。
从上面SpringApplication类中run()方法的源码,我们基本上知道SpringBoot在主程序执行run()方法后,启动如下关键流程:
初始化时间监听器,开始记录项目启动时间:
StopWatch stopWatch = new StopWatch();
stopWatch.start();
这里,初始化构造一个时间监听器对象,进入start()方法,开始记录启动时间,并启动一个未命名的任务:
/**
* 开始记录启动时间,启动一个未命名的任务。
* 如果在不调用该方法的情况下调用{@link #stop()}或计时方法,则结果是未定义的。
* @param taskName 要启动的任务的名称
*/
public void start(String taskName) throws IllegalStateException {
if (this.currentTaskName != null) {
throw new IllegalStateException("Can't start StopWatch: it's already running");
}
this.currentTaskName = taskName;
this.startTimeMillis = System.currentTimeMillis();
}
初始化Spring异常报告集合,这里将构造出一个SpringBootExceptionReporter接口类的集合对象:
Collection exceptionReporters = new ArrayList<>();
OK,进入SpringBootExceptionReporter看一下:
/**
* 该类是一个用于支持自定义报告{@link SpringApplication}启动错误的回调接口类。
* {@link SpringBootExceptionReporter}是通过{@link SpringFactoriesLoader}加载的,
* 并且必须声明一个带有单个{@link ConfigurableApplicationContext}参数的公共构造函数。
*/
@FunctionalInterface
public interface SpringBootExceptionReporter {
/**
* 启动失败,则上报失败信息。
*
* 如果报告失败,返回true;
* 如果发生默认报告,则返回false
*/
boolean reportException(Throwable failure);
}
java.awt.headless是J2SE的一种模式用于在缺少显示屏、键盘或者鼠标时的系统配置。
configureHeadlessProperty();
很多监控工具如jconsole 需要将该值设置为true,系统变量默认为true。看下源码:
private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
Boolean.toString(this.headless)));
}
//【4】获取spring.factories中的监听器变量
// 参数args:为指定的参数数组,默认为当前类SpringApplication
//获取并启动监听器,即初始化监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
//在run方法第一次启动时立即调用。可以用于非常早期的初始化。
listeners.starting();
看上边程序,首先获取监听器getRunListeners(),然后启动监听器starting()。逐步分析:
//第一步:获取并启动监听器,即初始化监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
进入getRunListeners()方法,
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class>[] types = new Class>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
分析:
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
进入getSpringFactoriesInstances(),最终将返回一个工厂实例。
/***
* 获取Spring工厂实例
*/
private Collection getSpringFactoriesInstances(Class type, Class>[] parameterTypes, Object... args) {
//获取类加载器
ClassLoader classLoader = getClassLoader();
//使用给定的类加载器,从{@value #FACTORIES_RESOURCE_LOCATION}("META-INF/spring.factories")装入给定类型的工厂实现的完全限定类名。
Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//根据启动类的类型、参数类型和类加载器创建工厂实例集合
List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
loadFactoryNames()方法,将取到工厂实例name的集合:
进入createSpringFactoriesInstances()方法,它展示了整个SpringBoot框架获取factories的方式:
@SuppressWarnings("unchecked")
private List createSpringFactoriesInstances(Class type, Class>[] parameterTypes,ClassLoader classLoader, Object[] args, Set names) {
List instances = new ArrayList<>(names.size());
for (String name : names) {
try {
//加载class类文件到内存中
Class> instanceClass = ClassUtils.forName(name, classLoader);
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;
}
通过反射获取实例,将触发EventPublishingRunListener的构造方法:
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
private final SpringApplication application;
private final String[] args;
/**
* 将所有事件广播给所有已注册的监听器,让监听器来忽略它们不感兴趣的事件。
* 监听器通常会对传入的事件对象执行相应的{@code instanceof}检查。
*
* 默认情况下,在调用线程中调用所有监听器。
* 这允许流氓监听器阻塞整个应用程序的危险,但只增加了最小的开销。
* 指定一个可选的任务执行器,以便在不同的线程中(例如从线程池中)执行监听器。
*/
private final SimpleApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener> listener : application.getListeners()) {
//加载监听器
this.initialMulticaster.addApplicationListener(listener);
}
}
... ...
}
这里加载所有监听器,进入addApplicationListener()方法看一下:
@Override
public void addApplicationListener(ApplicationListener> listener) {
synchronized (this.retrievalMutex) {
//如果已经注册,则显式删除代理的目标
//为了避免对同一个监听器的重复调用。
Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
if (singletonTarget instanceof ApplicationListener) {
this.defaultRetriever.applicationListeners.remove(singletonTarget);
}
/**
* 在静态类AbstractApplicationEventMulticaster中定义了一个目标监听器实例
* private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);
*
* 默认目标监听器实例调用了一个AbstractApplicationEventMulticaster类的内部类
* ListenerRetriever实例对象
*/
this.defaultRetriever.applicationListeners.add(listener);
this.retrieverCache.clear();
}
}
OK,进入applicationListeners()方法,也就进入这个内部类:
/**
* ListenerRetriever 是一个Helper类,它封装了一组特定的目标监听器侦听器,允许有效地检索预先过
* 滤的监听器。并且此帮助器的实例按事件类型和源类型缓存。
*/
private class ListenerRetriever {
public final Set> applicationListeners = new LinkedHashSet<>();
public final Set applicationListenerBeans = new LinkedHashSet<>();
private final boolean preFiltered;
public ListenerRetriever(boolean preFiltered) {
this.preFiltered = preFiltered;
}
public Collection> getApplicationListeners() {
List> allListeners = new ArrayList<>(
this.applicationListeners.size() + this.applicationListenerBeans.size());
allListeners.addAll(this.applicationListeners);
if (!this.applicationListenerBeans.isEmpty()) {
//Bean工厂实例化
BeanFactory beanFactory = getBeanFactory();
//遍历当前的监听器Bean实例集合
for (String listenerBeanName : this.applicationListenerBeans) {
try {
//从监听器bean集合中获取监听器的实例Bean对象
ApplicationListener> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);
if (this.preFiltered || !allListeners.contains(listener)) {
//装载监听器实例bean
allListeners.add(listener);
}
} catch (NoSuchBeanDefinitionException ex) {
// Singleton listener instance (without backing bean definition) disappeared -
// probably in the middle of the destruction phase
}
}
}
if (!this.preFiltered || !this.applicationListenerBeans.isEmpty()) {
//对监听器排序
AnnotationAwareOrderComparator.sort(allListeners);
}
return allListeners;
}
}
通过this.defaultRetriever.applicationListeners.add(listener),将监听器spring.factories中的监听器传递给SimpleApplicationEventMulticaster中。触发EventPublishingRunListener的构造方法,然后获取到所有的监听器实例。
内部类SimpleApplicationEventMulticaster继承了AbstractApplicationEventMulticaster,然后由AbstractApplicationEventMulticaster实现三个接口。继承关系如下:
下一步启动监听器,继续看SpringApplication.java类:
//在run方法第一次启动时立即调用。可以用于非常早期的初始化。
listeners.starting();
从获取监听器分析,可知这里启动EventPublishingRunListener监听器,即启动时间发布监听器,用来发布启动事件。
EventPublishingRunListener作为早期的监听器,执行后边的started()方法,将发布监听事件。这里,我们进入该类的starting()
@Override
public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
}
启动监听器时,调用starting()方法,这里我们进入starting()方法,调用multicastEvent()方法:
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
//解析事件类型
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
//获取线程池
Executor executor = getTaskExecutor();
for (ApplicationListener> listener : getApplicationListeners(event, type)) {
//如果为空则同步处理
if (executor != null) {
//异步发送监听事件
executor.execute(() -> invokeListener(listener, event));
} else {
//同步发送监听事件
invokeListener(listener, event);
}
}
}
以日志监听器为例:
/**
* 继承自ApplicationListener接口的方法
*
* @param event
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
//Springboot启动时
if (event instanceof ApplicationStartingEvent) {
onApplicationStartingEvent((ApplicationStartingEvent) event);
}
//环境准备完成时
else if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
//容器环境配置完成后
else if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
}
//容器关闭时
else if (event instanceof ContextClosedEvent
&& ((ContextClosedEvent) event).getApplicationContext().getParent() == null) {
onContextClosedEvent();
}
//容器启动失败时
else if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}
装配参数,构建一个默认的应用对象:
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
进入DefaultApplicationArguments()方法:
public DefaultApplicationArguments(String[] args) {
Assert.notNull(args, "Args must not be null");
this.source = new Source(args);
this.args = args;
}
//初始化容器环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
查看prepareEnvironment()方法:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
//获取相应的配置环境
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
//监听环境已准备事件,并发布
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
这里,环境信息配置,我们先进入getOrCreateEnvironment()方法:
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
//根据web环境类型判断,返回对应的环境类型配置
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
看下枚举类WebApplicationType:
public enum WebApplicationType {
/**
*应用程序不应作为web应用程序运行,也不应启动嵌入式web服务器。
*/
NONE,
/**
* 应用程序应该作为基于servlet的web应用程序运行,并且应该启动嵌入式servlet web服务器。
*/
SERVLET,
/**
*应用程序应该作为反应性web应用程序运行,并应该启动嵌入式反应性web服务器。
*/
REACTIVE;
... ...
}