1. SpringBoot请求处理全链路概述
当你用浏览器访问一个SpringBoot应用的URL时,这个请求会经历一场奇妙的"旅行"。想象一下快递包裹从发货到签收的过程:包裹要经过分拣中心、运输车、配送站等多个环节,每个环节都有特定的处理逻辑。SpringBoot处理请求的流程也类似,从Tomcat容器接收请求开始,到最终返回响应,中间会经过过滤器、拦截器、参数绑定、控制器方法执行等多个关键节点。
我刚开始用SpringBoot时,经常搞不清楚过滤器和拦截器的区别,也不明白为什么有些异常能被@ControllerAdvice捕获而有些不能。后来通过阅读源码和实际踩坑,终于理清了整个流程。下面我就用最直白的语言,带你走一遍这个完整链路。
2. 请求处理全流程详解
2.1 Tomcat容器接收请求
当请求到达服务器时,Tomcat作为Servlet容器最先接手。它会把HTTP请求解析成HttpServletRequest对象,然后创建对应的HttpServletResponse对象。这个过程就像快递公司收到包裹后,先检查外包装是否完好,然后登记发货人和收货人信息。
Tomcat会维护一个线程池来处理并发请求,每个请求都会分配一个独立线程。这里有个性能优化点:合理配置Tomcat的线程池参数(比如maxThreads、acceptCount等)可以显著提高并发处理能力。我在实际项目中就遇到过因为线程池配置不当导致的请求堆积问题。
2.2 Filter过滤器链处理
Tomcat接下来会把请求交给Filter链处理。Filter是JavaEE标准,不依赖Spring,主要用于处理通用的横切关注点,比如:
- 字符编码设置
- 跨域请求处理
- XSS防护
- 权限校验
- 请求日志记录
Filter的执行顺序由@Order注解或FilterRegistrationBean的order属性决定,数值越小优先级越高。常见的坑是忘记调用chain.doFilter()导致请求中断,我就犯过这个错误,调试了半天才发现问题。
// 典型Filter实现示例 public class LogFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { long start = System.currentTimeMillis(); // 前置处理(相当于快递分拣) System.out.println("请求开始:" + ((HttpServletRequest)request).getRequestURI()); chain.doFilter(request, response); // 放行到下一个Filter // 后置处理(相当于快递签收后的回执) System.out.println("请求耗时:" + (System.currentTimeMillis()-start) + "ms"); } }2.3 DispatcherServlet分发请求
经过所有Filter后,请求来到Spring的核心入口DispatcherServlet。它就像公司的前台,负责把不同类型的请求分发给对应的部门(Controller)。DispatcherServlet的工作流程:
- 根据请求URL查找对应的Handler(控制器方法)
- 获取适合的HandlerAdapter(默认是RequestMappingHandlerAdapter)
- 执行拦截器链的preHandle方法
- 真正调用控制器方法
- 处理返回结果(JSON、视图等)
- 执行拦截器链的postHandle方法
- 处理异常(如果有)
- 执行拦截器链的afterCompletion方法
2.4 Interceptor拦截器链
Interceptor是SpringMVC的概念,比Filter更接近业务逻辑。典型应用场景包括:
- 登录状态验证
- 权限检查
- 接口耗时统计
- 防重复提交
拦截器有三个关键方法:
public class AuthInterceptor implements HandlerInterceptor { // 控制器方法执行前调用 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 验证token逻辑 return true; // 返回false会中断请求 } // 控制器方法执行后调用(异常时不执行) @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { // 可以修改返回的ModelAndView } // 请求完成后调用(始终执行) @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 资源清理等收尾工作 } }2.5 参数绑定与方法执行
Spring会通过HandlerMethodArgumentResolver机制将请求参数绑定到方法参数上,包括:
- @RequestParam:查询参数
- @PathVariable:路径变量
- @RequestBody:JSON/XML请求体
- @ModelAttribute:表单数据
- @RequestHeader:请求头
这里常见的坑是参数类型转换失败,比如前端传"abc"到Integer参数。可以通过自定义Converter或实现WebMvcConfigurer#addFormatters来扩展类型转换。
2.6 返回值处理
控制器方法执行完后,返回值会通过HandlerMethodReturnValueHandler处理:
- @ResponseBody:使用HttpMessageConverter转换(如Jackson)
- ModelAndView:视图渲染
- String:视图名
- void:根据请求URL自动找视图
我曾经遇到一个坑:返回的JSON字段名突然变成了驼峰转下划线,后来发现是配置了错误的Jackson PropertyNamingStrategy。
2.7 异常处理
Spring的异常处理流程:
- 先找@ExceptionHandler(当前Controller内)
- 再找@ControllerAdvice中的@ExceptionHandler
- 最后用默认的BasicErrorController
需要注意的是:
- Filter中抛出的异常不会被@ControllerAdvice捕获
- 拦截器afterCompletion抛出的异常只会记录日志
- 想要全局处理Filter异常可以自定义ErrorController
3. 关键组件对比与最佳实践
3.1 Filter vs Interceptor
| 特性 | Filter | Interceptor |
|---|---|---|
| 作用范围 | Servlet容器级别 | SpringMVC级别 |
| 依赖 | Servlet API | Spring框架 |
| 执行顺序 | 最先执行 | 在DispatcherServlet之后 |
| 获取Bean | 不能直接注入 | 可以自动注入 |
| 使用场景 | 通用预处理/后处理 | 业务相关处理 |
实际项目中,我通常这样分工:
- Filter处理:跨域、编码、XSS防护、全局日志
- Interceptor处理:权限校验、接口限流、业务日志
3.2 常见问题解决方案
全局异常处理失效问题:
- Filter异常:在第一个Filter中try-catch,或者实现ErrorController
- 拦截器afterCompletion异常:只能记录日志,无法阻止响应返回
- 多个@ControllerAdvice:用@Order控制顺序
性能优化建议:
- 在Filter层做请求限流
- 使用@ControllerAdvice统一包装响应
- 复杂参数绑定实现自定义Converter
- 静态资源映射不要走SpringMVC流程
调试技巧:
- 在application.properties中添加:
logging.level.org.springframework.web=DEBUG logging.level.org.springframework.boot.autoconfigure=DEBUG- 使用SpringBoot Actuator的httptrace端点
- 自定义Filter和Interceptor时添加详细日志
4. 实战:自定义全链路日志
最后分享一个我在生产环境使用的全链路日志方案:
// 1. 定义TraceID过滤器 public class TraceFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String traceId = UUID.randomUUID().toString(); MDC.put("traceId", traceId); // 用于日志追踪 ((HttpServletResponse)response).setHeader("X-Trace-ID", traceId); chain.doFilter(request, response); MDC.remove("traceId"); } } // 2. 定义日志拦截器 public class LogInterceptor implements HandlerInterceptor { private static final Logger log = LoggerFactory.getLogger(LogInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { log.info("请求开始 {} {}", request.getMethod(), request.getRequestURI()); request.setAttribute("startTime", System.currentTimeMillis()); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { long cost = System.currentTimeMillis() - (long)request.getAttribute("startTime"); log.info("请求完成 状态码:{} 耗时:{}ms", response.getStatus(), cost); } } // 3. 注册组件 @Configuration public class WebConfig implements WebMvcConfigurer { @Bean public FilterRegistrationBean<TraceFilter> traceFilter() { FilterRegistrationBean<TraceFilter> bean = new FilterRegistrationBean<>(); bean.setFilter(new TraceFilter()); bean.setOrder(Ordered.HIGHEST_PRECEDENCE); return bean; } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LogInterceptor()) .addPathPatterns("/**"); } }这个方案可以清晰看到每个请求的完整生命周期,配合ELK等日志系统能快速定位问题。特别是在微服务架构下,通过TraceID可以串联起跨服务的调用链。