Skip to content

请求生命周期


⚠️ 最大的认知差异:内嵌 Web 服务器

PHP 的请求模型:

Nginx/Apache → PHP-FPM(进程池,每个请求一个独立进程)→ PHP 文件

Spring Boot 的请求模型:

客户端 → 内嵌 Tomcat(JVM 进程中启动,线程池处理请求)→ Spring MVC

Spring Boot 自带 Tomcat,不需要单独装 nginx/Apache。main() 方法一启动,Tomcat 就在同一个 JVM 进程里跑起来了。

这意味着:

  1. 没有 nginx 的 location 转发,不需要 rewrite 规则
  2. 所有请求都走一个进程,多线程并发(不是多进程)
  3. 静态资源也由 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-FPMSpring 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 的体验。


关键时间点

时机可做的事对应注解/接口
应用启动完成时初始化数据、预热缓存@PostConstructCommandLineRunner
请求到达前鉴权、日志、限流HandlerInterceptor.preHandle()
控制器执行前参数解析、校验@RequestParam@Valid
控制器执行后包装响应、记录耗时HandlerInterceptor.postHandle()
请求完全结束后清理资源HandlerInterceptor.afterCompletion()
应用关闭时释放连接、关闭线程池@PreDestroy

面向 PHP 开发者的 Spring Boot 文档