大家好呀!今天我们要一起探索一个神奇的话题——Spring Boot的启动流程。我知道很多小伙伴一听到"启动流程"四个字就开始头疼,别担心!我会用最通俗易懂的方式,带你从main()
方法开始,一步步揭开Spring Boot的神秘面纱,直到内嵌Tomcat欢快地跑起来~ ♂️
准备好了吗?让我们开始这段奇妙的旅程吧!
每个Spring Boot项目都有一个main()
方法,它就像是我们家的前门钥匙,没有它,我们连门都进不去!
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
看到这个@SpringBootApplication
注解了吗?它其实是个"套娃"注解,里面包含了三个重要注解:
@SpringBootConfiguration
:标识这是个配置类@EnableAutoConfiguration
:开启自动配置魔法 ✨@ComponentScan
:告诉Spring去哪里找组件当我们调用SpringApplication.run()
时,背后发生了很多事情:
// 伪代码简化版
public static ConfigurableApplicationContext run(Class primarySource, String... args) {
// 1. 创建SpringApplication实例
SpringApplication application = new SpringApplication(primarySource);
// 2. 运行!
return application.run(args);
}
Spring Boot会先判断我们是什么类型的应用:
// 判断逻辑大致是这样的
private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", null) {
return WebApplicationType.REACTIVE;
}
if (ClassUtils.isPresent("javax.servlet.Servlet", null)) {
return WebApplicationType.SERVLET;
}
return WebApplicationType.NONE;
}
初始化器就像是Spring Boot的"小助手"们,它们会在应用启动的不同阶段帮忙做一些准备工作。Spring Boot会从META-INF/spring.factories
文件中加载这些初始化器。
# 示例 spring.factories 内容
org.springframework.context.ApplicationContextInitializer=\
com.example.MyInitializer,\
com.example.AnotherInitializer
监听器就像是一群"小耳朵",它们会监听Spring Boot启动过程中的各种事件,比如:
ApplicationStartingEvent
:应用刚开始启动ApplicationEnvironmentPreparedEvent
:环境准备好了ApplicationPreparedEvent
:应用上下文准备好了ApplicationStartedEvent
:应用启动完成ApplicationReadyEvent
:应用准备就绪现在,我们来到了最精彩的部分——run()
方法的执行过程!让我们一步步来看:
Spring Boot一启动就会打开秒表,记录启动耗时:
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Spring Boot会准备运行环境,这包括:
ApplicationEnvironmentPreparedEvent
事件ConfigurableEnvironment environment = prepareEnvironment(...);
根据应用类型创建不同的应用上下文:
AnnotationConfigServletWebServerApplicationContext
AnnotationConfigReactiveWebServerApplicationContext
AnnotationConfigApplicationContext
context = createApplicationContext();
这一步会做很多重要工作:
ApplicationContextInitializedEvent
事件prepareContext(context, environment, listeners, applicationArguments, printedBanner);
这是整个启动过程中最复杂的一步!refresh()
方法做了很多事情:
refreshContext(context);
让我们深入看看refresh()
方法都做了什么:
DefaultListableBeanFactory
BeanFactoryPostProcessor
的实现类@EnableAutoConfiguration
的核心)BeanPostProcessor
ContextRefreshedEvent
事件启动完成后:
ApplicationStartedEvent
和ApplicationReadyEvent
事件afterRefresh(context, applicationArguments);
stopWatch.stop();
现在,让我们重点看看内嵌Tomcat是如何启动的!这是很多小伙伴最感兴趣的部分~
在refresh()
方法的"初始化其他特殊Bean"阶段,Spring Boot会创建Web服务器。对于Tomcat来说,关键类是这个:
org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
创建Tomcat实例:
Tomcat tomcat = new Tomcat();
配置基础信息:
创建Web应用上下文:
Context context = tomcat.addContext("", docBase);
配置Servlet容器:
启动Tomcat:
tomcat.start();
特点 | 内嵌Tomcat | 传统Tomcat |
---|---|---|
启动方式 | 通过Java代码启动 | 通过startup.sh/bat脚本启动 |
部署方式 | 打包成可执行JAR | 打包成WAR部署到webapps目录 |
配置方式 | 通过application.properties配置 | 通过server.xml配置 |
类加载器 | 使用应用的类加载器 | 使用Tomcat的类加载器 |
版本控制 | 由Spring Boot管理版本 | 需要单独安装和管理 |
Spring Boot最强大的特性之一就是自动配置,让我们看看它是如何工作的!
这个注解会导入AutoConfigurationImportSelector
,它会从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中加载自动配置类。
自动配置类通常使用各种条件注解来决定是否生效:
@ConditionalOnClass
:当类路径下有指定类时生效@ConditionalOnMissingBean
:当容器中没有指定Bean时生效@ConditionalOnProperty
:当配置属性满足条件时生效例如,Tomcat的自动配置类:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public class EmbeddedTomcatAutoConfiguration {
// 配置代码
}
为了帮助大家更好地理解,我画了一个简化的启动流程图:
main()
│
├─ 创建SpringApplication实例
│ ├─ 确定应用类型
│ ├─ 加载初始化器
│ └─ 加载监听器
│
└─ 执行run()方法
├─ 启动计时器
├─ 准备环境
├─ 创建应用上下文
├─ 准备上下文
├─ 刷新上下文 (核心!)
│ ├─ 准备刷新
│ ├─ 获取BeanFactory
│ ├─ 准备BeanFactory
│ ├─ 执行BeanFactory后置处理器
│ ├─ 注册Bean后置处理器
│ ├─ 初始化消息源
│ ├─ 初始化事件广播器
│ ├─ 初始化特殊Bean (如Tomcat)
│ ├─ 完成BeanFactory初始化
│ └─ 完成刷新
├─ 收尾工作
└─ 返回应用上下文
可能原因:
解决方案:
@Lazy
延迟初始化在application.properties中:
server.port=9090
server.tomcat.max-threads=200
server.tomcat.connection-timeout=5000
或者通过代码:
@Bean
public WebServerFactoryCustomizer tomcatCustomizer() {
return factory -> factory.addConnectorCustomizers(connector -> {
connector.setPort(9090);
connector.setProperty("maxThreads", "200");
});
}
启动时添加debug参数:
java -jar myapp.jar --debug
或者在application.properties中:
debug=true
默认情况下,Spring Boot会扫描主类所在包及其子包。如果项目很大,可以明确指定扫描路径:
@ComponentScan("com.myapp.package1", "com.myapp.package2")
Spring Boot 2.2+支持全局延迟初始化:
spring.main.lazy-initialization=true
如果知道某些自动配置不需要,可以排除它们:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
在开发环境中,添加DevTools可以加快重启速度:
org.springframework.boot
spring-boot-devtools
runtime
哇!我们终于走完了Spring Boot的整个启动流程!让我们回顾一下重点:
SpringApplication.run()
Spring Boot的启动过程就像是一个精密的瑞士手表⌚,各个部件协同工作,最终让我们的应用顺利运行。理解这个过程不仅能帮助我们更好地使用Spring Boot,还能在遇到问题时快速定位原因。
希望这篇文章能帮助你理解Spring Boot的启动机制!如果觉得有用,别忘了点赞收藏哦~
下次见!
由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)
如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系
HTTP、HTTPS、Cookie 和 Session 之间的关系
什么是 Cookie?简单介绍与使用方法
什么是 Session?如何应用?
使用 Spring 框架构建 MVC 应用程序:初学者教程
有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
如何理解应用 Java 多线程与并发编程?
把握Java泛型的艺术:协变、逆变与不可变性一网打尽
Java Spring 中常用的 @PostConstruct 注解使用总结
如何理解线程安全这个概念?
理解 Java 桥接方法
Spring 整合嵌入式 Tomcat 容器
Tomcat 如何加载 SpringMVC 组件
“在什么情况下类需要实现 Serializable,什么情况下又不需要(一)?”
“避免序列化灾难:掌握实现 Serializable 的真相!(二)”
如何自定义一个自己的 Spring Boot Starter 组件(从入门到实践)
解密 Redis:如何通过 IO 多路复用征服高并发挑战!
线程 vs 虚拟线程:深入理解及区别
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
“打破重复代码的魔咒:使用 Function 接口在 Java 8 中实现优雅重构!”
Java 中消除 If-else 技巧总结
线程池的核心参数配置(仅供参考)
【人工智能】聊聊Transformer,深度学习的一股清流(13)
Java 枚举的几个常用技巧,你可以试着用用
由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)
如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系
HTTP、HTTPS、Cookie 和 Session 之间的关系
使用 Spring 框架构建 MVC 应用程序:初学者教程
有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
Java Spring 中常用的 @PostConstruct 注解使用总结
线程 vs 虚拟线程:深入理解及区别
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
探索 Lombok 的 @Builder 和 @SuperBuilder:避坑指南(一)
为什么用了 @Builder 反而报错?深入理解 Lombok 的“暗坑”与解决方案(二)