请求生命周期
⚠️ 最大的认知差异:内嵌 Web 服务器
PHP 的请求模型:
Nginx/Apache → PHP-FPM(进程池,每个请求一个独立进程)→ PHP 文件Spring Boot 的请求模型:
客户端 → 内嵌 Tomcat(JVM 进程中启动,线程池处理请求)→ Spring MVCSpring Boot 自带 Tomcat,不需要单独装 nginx/Apache。
main()方法一启动,Tomcat 就在同一个 JVM 进程里跑起来了。这意味着:
- 没有 nginx 的
location转发,不需要rewrite规则- 所有请求都走一个进程,多线程并发(不是多进程)
- 静态资源也由 Tomcat 处理(也可以配 nginx 代理,但开发期不需要)
一次请求的完整流程
客户端
│
▼
Tomcat(端口 8080)
│ 接收 HTTP 请求,解析为 HttpServletRequest
│
▼
Filter 链
│ 类比中间件,可修改请求/响应
│ 例:CorsFilter、CharacterEncodingFilter
│
▼
DispatcherServlet(前端控制器,类比 index.php)
│ Spring MVC 的核心,找到哪个控制器来处理
│
▼
HandlerInterceptor.preHandle()
│ 控制器执行前的拦截
│ 返回 false 则终止请求
│
▼
控制器(@RestController)
│ 执行你的业务代码
│ 返回到 Service → Repository → DB
│
▼
HandlerInterceptor.postHandle()
│ 控制器执行后,视图渲染前
│
▼
视图解析 / JSON 序列化
│ @RestController → Jackson 序列化为 JSON
│ @Controller → Thymeleaf 渲染 HTML
│
▼
HandlerInterceptor.afterCompletion()
│ 请求结束,资源清理
│
▼
返回给客户端线程模型对比 ⚠️
| 特性 | PHP-FPM | Spring Boot (Tomcat) |
|---|---|---|
| 并发模型 | 多进程 | 多线程 |
| 每个请求 | 独立 PHP 进程 | Tomcat 线程池中的一条线程 |
| 内存 | 进程隔离,互不共享 | 线程共享堆内存,需注意线程安全 |
| 进程/线程数 | pm.max_children 控制 | server.tomcat.threads.max 控制(默认 200) |
| 类/对象状态 | 每次请求重新加载 | 单例 Bean 全局共享 |
对代码的影响:
php// PHP:没问题,每个请求都是干净的 class UserController { private $count = 0; public function index() { $this->count++; // 永远递增到 1(因为每次请求新实例) } }java// Java:危险!所有请求共享这个变量 @RestController public class UserController { private int count = 0; // ❌ 并发下会错乱 @GetMapping("/test") public int test() { return ++count; // 并发请求下结果不可预测 } }解决方案:不要在 Bean 中放可变实例变量。需要状态的场景用数据库、Redis 或 ThreadLocal。
应用启动 vs 请求处理
java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
// 1. 启动 JVM
// 2. 扫描注解,注册所有 Bean
// 3. 自动配置(数据源、Redis、Security 等)
// 4. 启动内嵌 Tomcat
// 5. 准备就绪,开始接受请求
SpringApplication.run(DemoApplication.class, args);
}
}启动过程需要 3-10 秒(取决于项目大小和依赖数量)。一旦启动完成,后续请求处理速度很快(因为所有 Bean 都已在内存中预热)。
PHP 的开发体验是"改完保存就刷新页面",Java 的开发体验中每次重启后需要等待。Spring Boot DevTools 可以在检测到文件变化后自动重启(约 2-5 秒),勉强接近 PHP 的体验。
关键时间点
| 时机 | 可做的事 | 对应注解/接口 |
|---|---|---|
| 应用启动完成时 | 初始化数据、预热缓存 | @PostConstruct、CommandLineRunner |
| 请求到达前 | 鉴权、日志、限流 | HandlerInterceptor.preHandle() |
| 控制器执行前 | 参数解析、校验 | @RequestParam、@Valid |
| 控制器执行后 | 包装响应、记录耗时 | HandlerInterceptor.postHandle() |
| 请求完全结束后 | 清理资源 | HandlerInterceptor.afterCompletion() |
| 应用关闭时 | 释放连接、关闭线程池 | @PreDestroy |