🎓博主介绍:Java、Python、js全栈开发 “多面手”,精通多种编程语言和技术,痴迷于人工智能领域。秉持着对技术的热爱与执着,持续探索创新,愿在此分享交流和学习,与大家共进步。
📖DeepSeek-行业融合之万象视界(附实战案例详解100+)
📖全栈开发环境搭建运行攻略:多语言一站式指南(环境搭建+运行+调试+发布+保姆级详解)
👉感兴趣的可以先收藏起来,希望帮助更多的人
图解 Spring Boot 启动流程:从 main 方法到 Tomcat 启动的完整生命周期
一、引言
Spring Boot 作为 Spring 生态系统中简化开发的利器,极大地提高了开发效率。它通过自动配置和嵌入式服务器,让开发者能够快速搭建和部署应用程序。深入理解 Spring Boot 的启动流程,不仅有助于我们排查问题,还能让我们更好地定制和优化应用。本文将详细介绍 Spring Boot 从 main 方法开始到 Tomcat 启动的完整生命周期,并通过图解和代码示例进行深入剖析。
二、Spring Boot 启动的入口:main 方法
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);
}
}
在上述代码中,@SpringBootApplication
是一个组合注解,它包含了 @Configuration
、@EnableAutoConfiguration
和 @ComponentScan
等注解,用于启用自动配置、组件扫描等功能。SpringApplication.run
方法是 Spring Boot 启动的核心入口。
2.2 SpringApplication.run 方法的执行过程
SpringApplication.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);
}
这里,首先创建了一个 SpringApplication
对象,然后调用其 run
方法。
三、SpringApplication 对象的初始化
3.1 构造函数的作用
SpringApplication
的构造函数会完成一系列的初始化工作,包括:
- 推断应用类型(如 Web 应用、非 Web 应用等)
- 查找并加载初始化器(
ApplicationContextInitializer
) - 查找并加载监听器(
ApplicationListener
) - 推断主应用类
以下是构造函数的部分代码:
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.2 推断应用类型
WebApplicationType.deduceFromClasspath()
方法会根据类路径中的类来推断应用类型,常见的应用类型有:
SERVLET
:传统的基于 Servlet 的 Web 应用REACTIVE
:响应式 Web 应用NONE
:非 Web 应用
四、SpringApplication.run 方法的详细执行流程
4.1 启动计时
在 run
方法开始时,会记录启动时间:
StopWatch stopWatch = new StopWatch();
stopWatch.start();
4.2 配置应用环境
创建并配置 ConfigurableEnvironment
对象,加载配置文件(如 application.properties
或 application.yml
):
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
4.3 创建并刷新应用上下文
根据推断的应用类型创建相应的 ApplicationContext
,并调用 refresh
方法进行刷新:
context = createApplicationContext();
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
4.4 启动完成
停止计时并发布 ApplicationReadyEvent
事件:
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.contextLoaded(context);
callRunners(context, applicationArguments);
五、应用上下文的刷新过程
5.1 准备刷新
设置上下文的启动日期、激活上下文、初始化属性源等:
@Override
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
if (logger.isDebugEnabled()) {
if (logger.isTraceEnabled()) {
logger.trace("Refreshing " + this);
}
else {
logger.debug("Refreshing " + getDisplayName());
}
}
// Initialize any placeholder property sources in the context environment
initPropertySources();
// Validate that all properties marked as required are resolvable
// see ConfigurablePropertyResolver#setRequiredProperties
getEnvironment().validateRequiredProperties();
// Store pre-refresh ApplicationListeners...
if (this.earlyApplicationListeners == null) {
this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
}
else {
// Reset local application listeners to pre-refresh state.
this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners);
}
// Allow for the collection of early ApplicationEvents,
// to be published once the multicaster is available...
this.earlyApplicationEvents = new LinkedHashSet<>();
}
5.2 创建并刷新 BeanFactory
创建 DefaultListableBeanFactory
并加载 Bean 定义:
@Override
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
return getBeanFactory();
}
@Override
protected void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
5.3 初始化 Bean
在 finishBeanFactoryInitialization
方法中,会初始化所有剩余的单例 Bean:
@Override
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Initialize conversion service for this context.
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
// Register a default embedded value resolver if no bean post-processor
// (such as a PropertySourcesPlaceholderConfigurer bean) registered any before:
// at this point, primarily for resolution in annotation attribute values.
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
}
// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}
// Stop using the temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(null);
// Allow for caching all bean definition metadata, not expecting further changes.
beanFactory.freezeConfiguration();
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
}
六、Tomcat 服务器的启动
6.1 嵌入式 Tomcat 的自动配置
Spring Boot 通过自动配置机制,根据类路径中的依赖自动配置嵌入式 Tomcat 服务器。在 ServletWebServerFactoryAutoConfiguration
类中,会自动配置 TomcatServletWebServerFactory
:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class TomcatServletWebServerFactoryConfiguration {
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers()
.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers()
.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers()
.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
6.2 Tomcat 服务器的启动过程
在应用上下文刷新完成后,ServletWebServerApplicationContext
会调用 TomcatServletWebServerFactory
创建并启动 Tomcat 服务器:
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
七、总结
通过以上分析,我们详细了解了 Spring Boot 从 main 方法开始到 Tomcat 启动的完整生命周期。整个过程涉及到应用类型推断、环境配置、应用上下文刷新、Bean 初始化以及嵌入式服务器启动等多个关键步骤。深入理解这些步骤,有助于我们更好地开发和优化 Spring Boot 应用程序。