项目的开始
下面是一个Spring Boot项目启动的入口代码
@SpringBootApplication
public class BootApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(BootApplication.class, args);
}
}
我们可以发现,所有的项目的开始都是从这个SpringApplication开始,这就是SpringBoot项目的开始。那么我们看看这个里面主要包含了哪些东西。
SpringApplication
run
这个方法就是我们main方法中启动项目使用的方法,一切的开始
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
方法最终是新建了SpringApplication容器,并且调用run方法执行了main方法的args参数(假如有的话)
新建容器——构造方法
根据参数新建sb项目的容器
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 资源加载器
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// java config类的数组
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// web应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// ApplicationContextInitializer 重启初始化的数组
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 监听器的数组
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 主类
this.mainApplicationClass = deduceMainApplicationClass();
}
上面代码我们需要关注的几个方法
- WebApplicationType.deduceFromClasspath 获得web应用类型
- 初始化数组 setInitializers
- 监听器数组 setInitializers
- 获得主类 deduceMainApplicationClass
新建容器——run方法
根据之前代码我们可以知道新建完容器后,系统会马上执行run方法。下面就是run方法的内容
public ConfigurableApplicationContext run(String... args) {
// StopWatch 一个时间统计工具
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置Headless
configureHeadlessProperty();
// 获得监听器,并开始监听
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 获得容器的Arguments
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 通过监听器获得并处理配置环境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// 配置环境
configureIgnoreBeanInfo(environment);
// 打印Banner 这个东西emmmmm…………
Banner printedBanner = printBanner(environment);
// 创建容器
context = createApplicationContext();
// 获得异常报告
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 进一步初始化容器
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 刷新容器
refreshContext(context);
// 刷新容器的后置处理
afterRefresh(context, applicationArguments);
// 计时器结束
stopWatch.stop();
// 打印时长
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// 通知监听器,容器启动结束
listeners.started(context);
// 执行applicationRunner的允许方法
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;
}
整个代码的逻辑大致可以这么归纳:
- 执行监听操作,开始监听
- 获得主类mian方法中的参数,配合监听器获得缓存配置
- 配置环境
- 创建容器
- 收集异常信息
- 初始化容器
- 进行一次刷新,以及刷新后的后置处理
- 通知监听操作,监听结束
- 执行applicationRunner
新建容器,以及执行时的这些方法
获得web应用类型
主要根据classPath判断不同的应用
/**
* 根据classpath判断类型
* @return
*/
static WebApplicationType deduceFromClasspath() {
// DispatcherHandler
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
// mvc的DispatcherServlet
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
// jersey的ServletContainer
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
// 内嵌的 Reactive Web 应用。例如说,Spring Webflux
return WebApplicationType.REACTIVE;
}
// 遍历 ConfigurableWebApplicationContext,javax.servlet.Servlet
for (String className : SERVLET_INDICATOR_CLASSES) {
// 如果不存在
if (!ClassUtils.isPresent(className, null)) {
// 非内嵌的 Web 应用
return WebApplicationType.NONE;
}
}
// 内嵌的 Servlet Web 应用。例如说,Spring MVC
return WebApplicationType.SERVLET;
}
返回的三种类型,在springboot上的说明是
- NONE APP不应作为Web应用程序运行,也不应启动嵌入式Web服务器
- SERVLET APP应作为基于servlet的Web应用程序运行,并应启动嵌入式servlet Web服务器。
- REACTIVE APP应作为反应式Web应用程序运行,并应启动嵌入式反应式Web服务器。
容器初始化对象
用来初始化ApplicationContextInitializer对象,容器初始化策略
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 = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
// 加载在 `META-INF/spring.factories` 里的类名的数组
// 在 META-INF/spring.factories 文件中,会以 KEY-VALUE 的格式,配置每个类对应的实现类们
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 创建对象们
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
// 进行排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
1.从线程中获得类加载器,
2.然后从META-INF/spring.factories中加载 ApplicationContextInitializer.class的实现类
获得监听器
用来初始化ApplicationListener对象,监听器接口,整个逻辑和初始化ApplicationContextInitializer相同
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 逻辑参考 初始化监听器数组
获得主类
获得启动启动的主类
private Class<?> deduceMainApplicationClass() {
try {
// 获得当前 StackTraceElement 数组
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;
}
从异常堆栈中获得执行了main方法的类。
设置Headless
Headless模式是在缺少显示屏、键盘或者鼠标是的系统配置
// 配置Headless
configureHeadlessProperty();
private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
获得监听器
获得目前执行的监听器,注意这和之前的监听器不同,之前的ApplicationListener不同,之前的为spring的监听器,这一块初始化的是springboot的
// 获得监听器,并开始监听
SpringApplicationRunListeners listeners = getRunListeners(args);
// 获得所有SpringApplicationRunListener的实现类,
// 然后报装成SpringApplicationRunListeners
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
获得容器参数,以及通过监听器获得环境配置
这一块感觉用的不算太多,逻辑也不算复杂
// 获得容器的Arguments
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
public class DefaultApplicationArguments implements ApplicationArguments {
private final Source source;
private final String[] args;
//...其他代码
}
配置环境参数
// 通过监听器获得并处理配置环境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// 配置环境
configureIgnoreBeanInfo(environment);
获得环境参数
主要获得环境的参数配置
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
// 获得或者创建环境
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 进行环境配置
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 通知监听器,环境已经完成配置
listeners.environmentPrepared(environment);
// 环境绑定到容器中
bindToSpringApplication(environment);
// 如果不是自定义的环境,则进行类型转换
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
// 如果有 attach 到 environment 上的 MutablePropertySources ,则添加到 environment 的 PropertySource 中
ConfigurationPropertySources.attach(environment);
return environment;
}
从上面可以看到代码主要分为四个部分:
- 环境配置的创建
- 数据的配置,这其中监听器有很重要的作用
- 事件的发布
- 数据的绑定
创建容器
还记得上面的webApplicationType么,这一步就是根据之前获得的webApplicationType来初始化不同的实现类。
// 创建容器
context = createApplicationContext();
protected ConfigurableApplicationContext createApplicationContext() {
// 获得上面定义的容器类
Class<?> contextClass = this.applicationContextClass;
// 如果为空,则重新进行一次web容器类型判断一次返回对应的容器类,默认使用DEFAULT_CONTEXT_CLASS
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);
}
初始化容器
在完成参数配置后,容器开始进行数据初始化
// 进一步初始化容器
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 设置容器的环境
context.setEnvironment(environment);
// 设置容器的属性
postProcessApplicationContext(context);
// Initializers的初始化
applyInitializers(context);
// 通知监听器容器准备晚餐
listeners.contextPrepared(context);
// 进行日志打印
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 为bean工厂注册springApplicationArguments、springBootBanner
// Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// 获得所有的bean源
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 为容器加载bean
load(context, sources.toArray(new Object[0]));
// 通知监听器容器加载完成
listeners.contextLoaded(context);
}
我们可以看到,容器初始化主要处理的逻辑有:
- 配置容器环境和属性的配置
- 真正的初始化逻辑主要是ApplicationContextInitializer来进行初始化
- 事件的发布
- bean资源的加载
刷新容器
在完成参数设置后,刷新容器的配置。这一块主要还是涉及spring的逻辑。
// 刷新容器
refreshContext(context);
private void refreshContext(ConfigurableApplicationContext context) {
// 进行刷新
refresh(context);
if (this.registerShutdownHook) {
try {
// 注册钩子ShutdownHook
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
// 执行容器的刷新操作
((AbstractApplicationContext) applicationContext).refresh();
}
刷新容器后置处理
// 刷新容器的后置处理
afterRefresh(context, applicationArguments);
通知监听器,容器启动结束
// 通知监听器,容器启动结束
listeners.started(context);
执行applicationRunner
// 执行applicationRunner的允许方法
callRunners(context, applicationArguments);
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
// 获得容器中所有的ApplicationRunner类
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
// 获得容器中所有的CommandLineRunner 类
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
// 排序
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
// 执行其对应的callRunner方法
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
通知监听器容器运行中
// 通知监听器容器运行中
listeners.running(context);
总结
上面就是我们在启动springboot项目的时候,spring的一系列操作了。后续我们根据SpringApplication的构造以及SpringApplication.run两个大块进行学习,看看里面具体逻辑。主要包含内容
- 构造函数内方法(ApplicationContextInitializer和webApplicationType)
- run方法监听器
- run方法环境配置
- run方法初始化