🎓博主介绍:Java、Python、js全栈开发 “多面手”,精通多种编程语言和技术,痴迷于人工智能领域。秉持着对技术的热爱与执着,持续探索创新,愿在此分享交流和学习,与大家共进步。
📖DeepSeek-行业融合之万象视界(附实战案例详解100+)
📖全栈开发环境搭建运行攻略:多语言一站式指南(环境搭建+运行+调试+发布+保姆级详解)
👉感兴趣的可以先收藏起来,希望帮助更多的人
图解 Spring Boot 启动流程:源码级深度解析
一、引言
Spring Boot 作为当前 Java 开发中最流行的框架之一,极大地简化了 Spring 应用的开发和部署过程。它的自动配置、嵌入式服务器等特性,让开发者能够快速搭建出生产级别的应用。然而,很多开发者在使用 Spring Boot 时,只是停留在表面的使用上,对于其底层的启动流程了解甚少。本文将从源码的角度,深入剖析 Spring Boot 的启动流程,并通过图解的方式,帮助大家更好地理解。
二、Spring Boot 启动入口
2.1 经典的启动代码
在 Spring Boot 应用中,我们通常会有一个带有 main 方法的类来启动应用,代码如下:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MySpringBootApp {
public static void main(String[] args) {
SpringApplication.run(MySpringBootApp.class, args);
}
}
从这段代码可以看出,SpringApplication.run 方法是 Spring Boot 应用启动的核心入口。接下来,我们将深入分析这个方法的内部实现。
三、SpringApplication 类的初始化
3.1 SpringApplication 构造函数
当调用 SpringApplication.run 方法时,会先创建一个 SpringApplication 实例。SpringApplication 的构造函数主要完成以下几个重要的初始化工作:
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();
}
3.1.1 确定 Web 应用类型
WebApplicationType.deduceFromClasspath() 方法会根据类路径中的类来确定当前应用的 Web 类型,包括 SERVLET、REACTIVE 和 NONE 三种。
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;
}
3.1.2 加载初始化器和监听器
getSpringFactoriesInstances 方法会从 META-INF/spring.factories 文件中加载所有的 ApplicationContextInitializer 和 ApplicationListener 实现类。
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();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
3.2 确定主应用类
deduceMainApplicationClass() 方法会通过分析调用栈来确定包含 main 方法的类。
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;
}
四、SpringApplication.run 方法核心流程
4.1 调用 run 方法
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);
}
4.2 启动流程核心步骤
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
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);
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;
}
4.2.1 启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args); 会加载所有的 SpringApplicationRunListener 实现类,并调用 listeners.starting() 方法,通知所有监听器应用开始启动。
4.2.2 准备环境
prepareEnvironment 方法会创建并配置应用的环境,包括加载配置文件、解析命令行参数等。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
bindToSpringApplication(environment);
if (!CollectionUtils.isEmpty(this.additionalProfiles)) {
environment.addActiveProfiles(StringUtils.toStringArray(this.additionalProfiles));
}
return environment;
}
4.2.3 创建应用上下文
context = createApplicationContext(); 会根据之前确定的 Web 应用类型,创建相应的 ApplicationContext 实例。
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 to create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
4.2.4 准备上下文
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();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// 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);
}
4.2.5 刷新上下文
refreshContext(context); 会调用 ApplicationContext 的 refresh 方法,这是 Spring 框架的核心方法,会完成 Bean 的加载、自动配置等一系列操作。
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
protected void refresh(ConfigurableApplicationContext applicationContext) {
applicationContext.refresh();
}
4.2.6 调用运行器
callRunners(context, applicationArguments); 会调用所有实现了 ApplicationRunner 或 CommandLineRunner 接口的 Bean,这些 Bean 可以在应用启动完成后执行一些初始化操作。
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
try {
(runner).run(args);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
}
}
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
try {
(runner).run(args.getSourceArgs());
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
}
}
五、Spring Boot 启动流程图解
5.1 整体流程图
[此处可以插入一个 Spring Boot 启动流程的整体流程图,包括从 main 方法开始,到创建 SpringApplication 实例,再到各个核心步骤的详细流程]
5.2 关键步骤详解图
5.2.1 初始化阶段
[插入初始化阶段的详细流程图,展示 SpringApplication 构造函数的各个步骤]
5.2.2 启动阶段
[插入启动阶段的详细流程图,展示 run 方法的各个核心步骤]
六、总结
通过本文的源码级深度解析和图解,我们详细了解了 Spring Boot 的启动流程。从启动入口的 SpringApplication.run 方法,到 SpringApplication 类的初始化,再到核心的启动流程,每个步骤都有其重要的作用。掌握 Spring Boot 的启动流程,有助于我们更好地理解 Spring Boot 的工作原理,在开发过程中能够更灵活地使用 Spring Boot 的各种特性,同时也能更方便地进行问题排查和性能优化。

3279

被折叠的 条评论
为什么被折叠?



