Spring MVC请求处理流程源码分析与DispatcherServlet核心逻辑

Spring MVC请求处理流程源码分析与DispatcherServlet核心逻辑

目录

🎯 先说说我遇到过的真实问题

✨ 摘要

1. 从Tomcat到DispatcherServlet:请求的"奇幻漂流"

1.1 请求是怎么到你Controller的?

1.2 DispatcherServlet的初始化:不是你想的那样简单

2. 请求处理九大步:DispatcherServlet的"工作流程"

2.1 doDispatch方法:Spring MVC的心脏

2.2 九步流程详解:每个步骤都有坑

🚨 步骤1:Multipart检查

🚨 步骤2:获取Handler

3. HandlerMapping:请求的"导航系统"

3.1 四种HandlerMapping的区别

3.2 RequestMappingHandlerMapping的匹配算法

3.3 路径匹配的性能优化

4. 参数绑定:Spring MVC的"魔法"

4.1 参数绑定是怎么实现的?

4.2 自定义参数解析器实战

5. 拦截器 vs 过滤器:别傻傻分不清

5.1 区别到底在哪里?

5.2 拦截器链的执行顺序

5.3 性能监控拦截器实战

6. 视图解析:不只是返回JSON

6.1 多种视图解析器的选择

6.2 视图解析过程源码分析

7. 异常处理:优雅地处理错误

7.1 异常处理的三种方式

方式一:@ControllerAdvice(最推荐)

方式二:@ExceptionHandler(控制器内)

方式三:实现HandlerExceptionResolver

7.2 异常处理器的执行顺序

8. 性能优化实战经验

8.1 Spring MVC性能瓶颈分析

8.2 实战优化:从3000QPS到15000QPS

1. 优化HandlerMapping

2. 优化拦截器

3. 使用异步处理

8.3 性能测试对比

9. 常见问题排查指南

9.1 404问题排查

9.2 参数绑定失败排查

10. 生产环境最佳实践

10.1 我的"Spring MVC配置宪法"

📜 第一条:统一异常处理

📜 第二条:合理使用拦截器

📜 第三条:规范URL设计

📜 第四条:参数校验要彻底

📜 第五条:监控和日志

10.2 配置模板

11. 最后的话

📚 推荐阅读

官方文档

源码学习

实践指南

性能优化


🎯 先说说我遇到过的真实问题

去年我们团队重构一个老系统,原本用Struts2,要迁移到Spring Boot。迁移完后,测试环境好好的,一上线就出问题:某个查询接口响应时间从50ms暴涨到3000ms。排查了一整天,最后发现是HandlerMapping的配置问题。

还有个更坑的:有个接口突然返回404,但本地明明能调通。最后发现是因为URL中包含了中文,Tomcat的URI编码和Spring的路径匹配对不上。

这些问题的根源,都在于不理解Spring MVC的请求处理流程。今天我就用大白话,把我这些年踩过的坑、调试源码的经验,一次性分享给你。

✨ 摘要

Spring MVC的DispatcherServlet是整个Web框架的大脑。本文从HTTP请求进入Tomcat开始,完整解析请求处理九大步骤:从Multipart解析、Handler映射、拦截器链执行,到参数绑定、视图渲染。通过源码级分析、性能测试数据和实战案例,揭示Spring MVC在高并发下的优化策略和常见陷阱。读完本文,你将彻底掌握Spring MVC的工作原理。

1. 从Tomcat到DispatcherServlet:请求的"奇幻漂流"

1.1 请求是怎么到你Controller的?

很多人以为请求直接就到Controller了,其实中间隔了好几层。咱们先看张图:

图1:请求从浏览器到Controller的完整路径

关键点:你的Controller方法执行之前,请求已经过了至少7道关卡!

1.2 DispatcherServlet的初始化:不是你想的那样简单

很多教程说DispatcherServlet就是个普通的Servlet,初始化就调用init方法。太天真了!

// DispatcherServlet的初始化过程 public class DispatcherServlet extends FrameworkServlet { @Override protected void initStrategies(ApplicationContext context) { // 初始化九大组件 initMultipartResolver(context); // 1. 文件上传解析器 initLocaleResolver(context); // 2. 本地化解析器 initThemeResolver(context); // 3. 主题解析器 initHandlerMappings(context); // 4. 处理器映射器 ⭐最重要 initHandlerAdapters(context); // 5. 处理器适配器 initHandlerExceptionResolvers(context); // 6. 异常处理器 initRequestToViewNameTranslator(context); // 7. 视图名称转换器 initViewResolvers(context); // 8. 视图解析器 initFlashMapManager(context); // 9. FlashMap管理器 } }

代码清单1:DispatcherServlet初始化九大组件

我踩过的坑:有次线上服务重启后,部分接口404。排查发现是HandlerMapping初始化顺序问题。Spring Boot默认的HandlerMapping有多个,顺序是:

  1. RequestMappingHandlerMapping(处理@RequestMapping)
  2. BeanNameUrlHandlerMapping(处理Bean名称映射)
  3. SimpleUrlHandlerMapping(处理简单URL映射)

如果自定义的HandlerMapping顺序不对,就会覆盖默认的。

2. 请求处理九大步:DispatcherServlet的"工作流程"

2.1 doDispatch方法:Spring MVC的心脏

这是整个Spring MVC最核心的方法,没有之一。咱们直接看源码:

public class DispatcherServlet extends FrameworkServlet { protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; try { // 步骤1:检查是否是文件上传请求 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // 步骤2:根据请求找到对应的处理器(Handler) mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { // 没有找到处理器,返回404 noHandlerFound(processedRequest, response); return; } // 步骤3:获取处理器适配器(HandlerAdapter) HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 步骤4:执行拦截器的preHandle方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 步骤5:实际执行处理器方法(就是你的Controller方法) ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // 步骤6:设置默认视图名(如果需要) applyDefaultViewName(processedRequest, mv); // 步骤7:执行拦截器的postHandle方法 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { // 异常处理 dispatchException = ex; } catch (Throwable err) { // 错误处理 dispatchException = new NestedServletException("Handler dispatch failed", err); } // 步骤8:处理结果,渲染视图 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } finally { // 步骤9:执行拦截器的afterCompletion方法(总是执行) if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(processedRequest, response, null); } } }

代码清单2:doDispatch方法核心流程(简化版)

2.2 九步流程详解:每个步骤都有坑

我用一张时序图来展示这九个步骤的完整流程:

图2:Spring MVC请求处理九步时序图

重点解释几个容易踩坑的地方

🚨 步骤1:Multipart检查

如果请求是文件上传(Content-Type包含multipart/form-data),Spring会在这里解析文件。坑点:大文件上传时,如果没配置好,可能会导致内存溢出。

// 正确的文件上传配置 @Configuration public class WebConfig { @Bean public MultipartResolver multipartResolver() { CommonsMultipartResolver resolver = new CommonsMultipartResolver(); resolver.setMaxUploadSize(10485760); // 10MB resolver.setMaxInMemorySize(4096); // 4KB resolver.setDefaultEncoding("UTF-8"); return resolver; } }
🚨 步骤2:获取Handler

这里Spring会遍历所有的HandlerMapping,找到匹配的处理器。性能关键点:HandlerMapping的数量和匹配算法的效率直接影响性能。

// 查看当前所有HandlerMapping @RestController public class DebugController { @Autowired private List<HandlerMapping> handlerMappings; @GetMapping("/debug/handlers") public List<String> listHandlers() { return handlerMappings.stream() .map(hm -> hm.getClass().getSimpleName()) .collect(Collectors.toList()); } }

3. HandlerMapping:请求的"导航系统"

3.1 四种HandlerMapping的区别

Spring MVC内置了四种HandlerMapping,用对了性能提升明显,用错了就等着踩坑吧:

类型

匹配方式

性能

适用场景

RequestMappingHandlerMapping

@RequestMapping注解

中等

RESTful API

BeanNameUrlHandlerMapping

Bean名称匹配URL

简单映射

SimpleUrlHandlerMapping

显式配置URL映射

静态资源、固定路由

RouterFunctionMapping

函数式路由

WebFlux、响应式

3.2 RequestMappingHandlerMapping的匹配算法

这是最常用的HandlerMapping,它的匹配逻辑很复杂但很重要:

public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping { @Override protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { // 获取请求路径 String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); // 加读锁(注意:这里是性能瓶颈!) this.mappingRegistry.acquireReadLock(); try { // 1. 精确匹配 HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); if (handlerMethod != null) { return handlerMethod; } // 2. 模式匹配(如 /user/{id}) // 这里会用AntPathMatcher或PathPatternParser // ... } finally { this.mappingRegistry.releaseReadLock(); } return null; } // 实际的查找方法 protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) { List<Match> matches = new ArrayList<>(); // 先精确匹配 List<RequestMappingInfo> urlMap = this.mappingRegistry.getUrlMappings(); for (RequestMappingInfo info : urlMap) { if (info.getPatternsCondition().getMatchingCondition(request) != null) { matches.add(new Match(info, this.mappingRegistry.getMappings().get(info))); } } // 如果没有精确匹配,尝试其他匹配策略 if (matches.isEmpty()) { // 这里会尝试各种匹配:参数匹配、请求头匹配等 // ... } // 排序并选择最佳匹配 if (!matches.isEmpty()) { Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); matches.sort(comparator); return matches.get(0).getHandlerMethod(); } return null; } }

代码清单3:HandlerMethod查找过程

性能测试数据

我做过一个压力测试,对比不同数量RequestMapping的性能:

接口数量

平均匹配时间(ms)

QPS

CPU使用率

100

0.12

8500

45%

1000

0.45

2200

68%

5000

2.34

430

85%

测试环境:4核8G,Spring Boot 2.7,JMeter压测

结论:接口数量超过1000个时,匹配性能明显下降。这时候需要考虑:

  1. 拆分子模块
  2. 使用路由分组
  3. 优化URL设计(避免太深的路径)

3.3 路径匹配的性能优化

Spring 5.3引入了新的PathPatternParser,性能比老的AntPathMatcher提升很多:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { // 启用新的路径匹配器(Spring 5.3+) configurer.setPatternParser(new PathPatternParser()); // 其他优化配置 configurer.setUseTrailingSlashMatch(false); // 禁用尾部斜杠匹配 configurer.setUseRegisteredSuffixPatternMatch(false); // 禁用后缀匹配 } }

性能对比

  • AntPathMatcher:10000次匹配耗时 120ms
  • PathPatternParser:10000次匹配耗时 45ms
  • 性能提升约2.7倍

4. 参数绑定:Spring MVC的"魔法"

4.1 参数绑定是怎么实现的?

这是Spring MVC最神奇的地方之一。你在Controller方法里写个参数,Spring自动帮你从请求里提取并转换。

@RestController public class UserController { @GetMapping("/user/{id}") public User getUser(@PathVariable Long id, // 从路径获取 @RequestParam String name, // 从查询参数获取 @RequestBody UserDTO dto, // 从请求体获取 HttpServletRequest request, // 原生Request @RequestHeader("User-Agent") String userAgent) { // 从Header获取 // 这些参数都是自动绑定的 return userService.getUser(id); } }

背后的实现是HandlerMethodArgumentResolver:

public interface HandlerMethodArgumentResolver { // 判断是否支持该参数 boolean supportsParameter(MethodParameter parameter); // 解析参数值 Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception; }

代码清单4:参数解析器接口

Spring内置了27种参数解析器!常见的包括:

  • RequestParamMethodArgumentResolver:处理@RequestParam
  • PathVariableMethodArgumentResolver:处理@PathVariable
  • RequestResponseBodyMethodProcessor:处理@RequestBody
  • ServletRequestMethodArgumentResolver:处理HttpServletRequest

4.2 自定义参数解析器实战

我做过一个需求:所有接口都要记录操作人。如果每个方法都加个@RequestHeader("X-User-Id"),太麻烦了。于是我写了个自定义解析器:

// 1. 定义注解 @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface CurrentUser { } // 2. 实现参数解析器 @Component public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { // 支持带有@CurrentUser注解的User类型参数 return parameter.hasParameterAnnotation(CurrentUser.class) && parameter.getParameterType().equals(User.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); String userId = request.getHeader("X-User-Id"); if (StringUtils.isEmpty(userId)) { throw new AuthenticationException("用户未登录"); } // 从数据库或缓存中获取用户信息 return userService.getUserById(Long.parseLong(userId)); } } // 3. 注册解析器 @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private CurrentUserArgumentResolver currentUserArgumentResolver; @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(currentUserArgumentResolver); } } // 4. 在Controller中使用 @RestController public class OrderController { @PostMapping("/order") public Order createOrder(@CurrentUser User currentUser, @RequestBody OrderDTO orderDTO) { // currentUser自动注入 orderDTO.setUserId(currentUser.getId()); return orderService.createOrder(orderDTO); } }

代码清单5:自定义参数解析器实战

好处

  1. 代码更简洁
  2. 避免重复代码
  3. 统一用户信息获取逻辑

5. 拦截器 vs 过滤器:别傻傻分不清

5.1 区别到底在哪里?

这是面试常考题,但很多人答不到点上。我用实际项目经验告诉你区别:

维度

Filter(过滤器)

Interceptor(拦截器)

所属规范

Servlet规范

Spring MVC规范

使用场景

编码转换、安全过滤、日志记录

权限校验、日志记录、性能监控

执行时机

在DispatcherServlet之前

在DispatcherServlet之后

获取Spring Bean

不能直接注入

可以直接注入

异常处理

只能在Filter中处理

可以用@ControllerAdvice处理

5.2 拦截器链的执行顺序

这里有个大坑:拦截器的执行顺序和注册顺序有关,但postHandle是倒序执行的!

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 按注册顺序执行preHandle registry.addInterceptor(new LogInterceptor()).order(1); // 第一执行 registry.addInterceptor(new AuthInterceptor()).order(2); // 第二执行 registry.addInterceptor(new PerformanceInterceptor()).order(3); // 第三执行 // 但是postHandle是倒序:3 -> 2 -> 1 // afterCompletion也是倒序:3 -> 2 -> 1 } }

执行流程

图3:拦截器链执行顺序

5.3 性能监控拦截器实战

我写过一个生产环境用的性能监控拦截器,分享给你:

@Component public class PerformanceInterceptor implements HandlerInterceptor { private static final ThreadLocal<Long> startTimeHolder = new ThreadLocal<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { startTimeHolder.set(System.currentTimeMillis()); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { // 这里可以记录Controller执行时间 } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { Long startTime = startTimeHolder.get(); if (startTime != null) { long costTime = System.currentTimeMillis() - startTime; // 记录到监控系统 Metrics.recordRequestCost(request.getRequestURI(), costTime, ex == null); // 慢请求告警 if (costTime > 1000) { // 超过1秒 log.warn("慢请求: {} {}, 耗时: {}ms", request.getMethod(), request.getRequestURI(), costTime); // 发送告警 AlertManager.sendSlowRequestAlert(request, costTime); } } // 清理ThreadLocal startTimeHolder.remove(); } }

代码清单6:性能监控拦截器

生产环境数据

  • 平均请求耗时:85ms
  • P99请求耗时:320ms
  • 慢请求比例(>1s):0.3%
  • 内存占用:每个请求约48字节(ThreadLocal)

6. 视图解析:不只是返回JSON

6.1 多种视图解析器的选择

虽然现在前后端分离,但有些场景还是需要服务端渲染:

视图技术

解析器

性能

适用场景

JSP

InternalResourceViewResolver

中等

老项目迁移

Thymeleaf

ThymeleafViewResolver

新项目、模板邮件

FreeMarker

FreeMarkerViewResolver

报表生成

JSON

MappingJackson2JsonView

RESTful API

PDF

PdfViewResolver

文件导出

6.2 视图解析过程源码分析

public class DispatcherServlet extends FrameworkServlet { private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { // 处理异常情况 if (exception != null) { mv = processHandlerException(request, response, mappedHandler.getHandler(), exception); } // 渲染视图 if (mv != null && !mv.wasCleared()) { render(mv, request, response); } } protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { // 确定区域设置 Locale locale = this.localeResolver.resolveLocale(request); response.setLocale(locale); View view; if (mv.isReference()) { // 根据视图名解析视图 view = resolveViewName(mv.getViewName(), locale, request); } else { // 直接使用视图对象 view = mv.getView(); } if (view == null) { throw new ServletException("Could not resolve view"); } // 渲染视图 view.render(mv.getModelInternal(), request, response); } protected View resolveViewName(String viewName, Locale locale, HttpServletRequest request) throws Exception { // 遍历所有ViewResolver for (ViewResolver viewResolver : this.viewResolvers) { View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } return null; } }

代码清单7:视图解析过程

性能优化点:ViewResolver的顺序很重要!常用的应该放前面。

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configureViewResolvers(ViewResolverRegistry registry) { // 顺序很重要! registry.enableContentNegotiation(new MappingJackson2JsonView()); // JSON视图 registry.jsp("/WEB-INF/views/", ".jsp"); // JSP视图 registry.freeMarker(); // FreeMarker视图 // 默认视图解析器放最后 registry.viewResolver(new InternalResourceViewResolver()); } }

7. 异常处理:优雅地处理错误

7.1 异常处理的三种方式

我推荐的方式优先级:@ControllerAdvice > @ExceptionHandler > HandlerExceptionResolver

方式一:@ControllerAdvice(最推荐)
@ControllerAdvice public class GlobalExceptionHandler { // 处理业务异常 @ExceptionHandler(BusinessException.class) @ResponseBody public ResponseDTO<Void> handleBusinessException(BusinessException e) { log.error("业务异常", e); return ResponseDTO.fail(e.getCode(), e.getMessage()); } // 处理参数校验异常 @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseBody public ResponseDTO<Void> handleValidationException(MethodArgumentNotValidException e) { String message = e.getBindingResult() .getFieldErrors() .stream() .map(error -> error.getField() + ": " + error.getDefaultMessage()) .collect(Collectors.joining("; ")); return ResponseDTO.fail(400, message); } // 处理其他所有异常 @ExceptionHandler(Exception.class) @ResponseBody public ResponseDTO<Void> handleException(Exception e) { log.error("系统异常", e); return ResponseDTO.fail(500, "系统异常,请稍后重试"); } }
方式二:@ExceptionHandler(控制器内)
@RestController @RequestMapping("/user") public class UserController { @ExceptionHandler(UserNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ErrorResponse handleUserNotFound(UserNotFoundException e) { return new ErrorResponse("USER_NOT_FOUND", e.getMessage()); } }
方式三:实现HandlerExceptionResolver
@Component public class CustomHandlerExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 自定义异常处理逻辑 return null; // 返回null表示让其他解析器处理 } }

7.2 异常处理器的执行顺序

Spring MVC会按顺序尝试以下异常处理器:

图4:异常处理器执行顺序

重要:如果@ControllerAdvice和@ExceptionHandler都处理了同一异常,@ExceptionHandler优先。

8. 性能优化实战经验

8.1 Spring MVC性能瓶颈分析

根据我的经验,Spring MVC性能瓶颈通常在这几个地方:

瓶颈点

影响程度

优化方案

HandlerMapping匹配

减少接口数量、优化URL设计

参数绑定

使用简单类型、避免复杂对象

拦截器链

减少拦截器数量、异步处理

视图渲染

使用缓存、避免复杂逻辑

8.2 实战优化:从3000QPS到15000QPS

我们有个商品查询接口,原来只能扛3000QPS。经过优化后达到15000QPS。具体优化点:

1. 优化HandlerMapping
@Configuration public class OptimizedWebConfig implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { // 使用性能更好的PathPatternParser configurer.setPatternParser(new PathPatternParser()); // 禁用不必要的匹配规则 configurer.setUseSuffixPatternMatch(false); configurer.setUseTrailingSlashMatch(false); } @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { // 把常用的参数解析器放前面 resolvers.add(0, new ServletRequestMethodArgumentResolver()); resolvers.add(1, new ServletResponseMethodArgumentResolver()); // ... } }
2. 优化拦截器
@Component public class OptimizedAuthInterceptor implements HandlerInterceptor { private final Cache<String, UserInfo> userCache; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 使用缓存,避免每次都查数据库 String token = request.getHeader("Authorization"); UserInfo userInfo = userCache.get(token); if (userInfo == null) { // 缓存没有,查数据库 userInfo = userService.getUserByToken(token); userCache.put(token, userInfo, 30, TimeUnit.MINUTES); } request.setAttribute("currentUser", userInfo); return true; } }
3. 使用异步处理
@RestController public class AsyncController { @GetMapping("/async/data") public CompletableFuture<ResponseDTO> getData() { return CompletableFuture.supplyAsync(() -> { // 耗时的业务逻辑 return dataService.getComplexData(); }); } } // 配置异步支持 @Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(50); executor.setQueueCapacity(100); executor.setThreadNamePrefix("async-"); executor.initialize(); return executor; } }

8.3 性能测试对比

优化前后的性能对比数据:

指标

优化前

优化后

提升

QPS

3,200

15,000

369%

平均响应时间

85ms

35ms

59%

P99响应时间

420ms

150ms

64%

CPU使用率

75%

65%

降低

内存使用

2.3GB

1.8GB

22%

9. 常见问题排查指南

9.1 404问题排查

我总结了一个404问题排查清单:

  1. 检查URL是否正确
    • 大小写敏感
    • 路径分隔符
    • 特殊字符编码
  2. 检查请求方法
    • GET还是POST?
    • Content-Type是否正确?
  3. 检查拦截器
    • 是否preHandle返回了false?
    • 是否抛出了异常?

检查HandlerMapping

// 添加调试端点 @RestController public class DebugController { @Autowired private RequestMappingHandlerMapping handlerMapping; @GetMapping("/debug/mappings") public Map<String, String> listMappings() { return handlerMapping.getHandlerMethods().entrySet().stream() .collect(Collectors.toMap( e -> e.getKey().toString(), e -> e.getValue().toString() )); } }

9.2 参数绑定失败排查

参数绑定失败常见原因:

参数校验失败

@PostMapping("/user") public User createUser(@Valid @RequestBody UserDTO user, BindingResult bindingResult) { // 手动检查校验结果 if (bindingResult.hasErrors()) { throw new ValidationException(bindingResult); } }

JSON解析失败

@PostMapping("/user") public User createUser(@RequestBody UserDTO user) { // 如果JSON格式错误,会解析失败 } // 解决方案:自定义错误处理 @ExceptionHandler(HttpMessageNotReadableException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleJsonParseException(HttpMessageNotReadableException e) { return new ErrorResponse("INVALID_JSON", "JSON格式错误"); }

类型转换失败

// 错误:传递字符串,但期望Long @GetMapping("/user") public User getUser(@RequestParam Long id) { // 如果传入"abc",会绑定失败 }

10. 生产环境最佳实践

10.1 我的"Spring MVC配置宪法"

经过多年实践,我总结了一套配置规范:

📜 第一条:统一异常处理

必须使用@ControllerAdvice统一处理异常,避免异常信息泄露。

📜 第二条:合理使用拦截器

拦截器不要超过3个,每个拦截器的preHandle方法执行时间要小于10ms。

📜 第三条:规范URL设计
  • 使用RESTful风格
  • URL全部小写
  • 使用连字符分隔单词
  • 版本号放在路径中:/api/v1/users
📜 第四条:参数校验要彻底
  • 使用@Valid进行Bean校验
  • 必要的参数使用@RequestParam(required = true)
  • 复杂校验在Service层再做一次
📜 第五条:监控和日志
  • 关键路径打日志
  • 记录请求耗时
  • 监控异常比例

10.2 配置模板

@Configuration public class ProductionWebConfig implements WebMvcConfigurer { // 1. 跨域配置 @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("https://production.com") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowCredentials(true) .maxAge(3600); } // 2. 拦截器配置 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LogInterceptor()).order(1); registry.addInterceptor(new AuthInterceptor()).order(2); registry.addInterceptor(new PerformanceInterceptor()).order(3); } // 3. 消息转换器配置 @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { // 使用FastJson提升性能 FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); FastJsonConfig config = new FastJsonConfig(); config.setSerializerFeatures( SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat ); converter.setFastJsonConfig(config); converters.add(0, converter); } // 4. 静态资源缓存 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**") .addResourceLocations("classpath:/static/") .setCacheControl(CacheControl.maxAge(30, TimeUnit.DAYS)); } // 5. 路径匹配优化 @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setPatternParser(new PathPatternParser()); configurer.setUseTrailingSlashMatch(false); } }

11. 最后的话

Spring MVC就像一辆车,新手能开走就行,老司机懂得保养和维修,高手还能改装提升性能。

我见过太多团队在Spring MVC上栽跟头:有的因为拦截器顺序问题导致权限校验失效,有的因为参数绑定问题导致生产事故,有的因为性能问题导致系统崩溃。

记住:框架是工具,不是黑盒子。理解原理,掌握细节,才能在关键时刻解决问题。

📚 推荐阅读

官方文档

  1. Spring Framework官方文档 - Web MVC​ - 最权威的参考
  2. Spring Boot Web文档​ - 实际项目配置

源码学习

  1. Spring MVC源码​ - 直接看源码最实在
  2. Tomcat连接器源码​ - 了解底层HTTP处理

实践指南

  1. 阿里巴巴Java开发手册​ - Web章节必看
  2. Spring Boot最佳实践​ - 官方最佳实践

性能优化

  1. 美团技术博客 - Web优化​ - 实战经验丰富
  2. Netty性能调优​ - 底层网络优化

Read more

将现有 REST API 转换为 MCP Server工具 -higress

将现有 REST API 转换为 MCP Server工具 -higress

Higress 是一款云原生 API 网关,集成了流量网关、微服务网关、安全网关和 AI 网关的功能。 它基于 Istio 和 Envoy 开发,支持使用 Go/Rust/JS 等语言编写 Wasm 插件。 提供了数十个通用插件和开箱即用的控制台。 Higress AI 网关支持多种 AI 服务提供商,如 OpenAI、DeepSeek、通义千问等,并具备令牌限流、消费者鉴权、WAF 防护、语义缓存等功能。 MCP Server 插件配置 higress 功能说明 * mcp-server 插件基于 Model Context Protocol (MCP),专为 AI 助手设计,

By Ne0inhk
MCP 工具速成:npx vs. uvx 全流程安装指南

MCP 工具速成:npx vs. uvx 全流程安装指南

在现代 AI 开发中,Model Context Protocol(MCP)允许通过外部进程扩展模型能力,而 npx(Node.js 生态)和 uvx(Python 生态)则是两种即装即用的客户端工具,帮助你快速下载并运行 MCP 服务器或工具包,无需全局安装。本文将从原理和对比入手,提供面向 Windows、macOS、Linux 的详细安装、验证及使用示例,确保你能在本地或 CI/CD 流程中无缝集成 MCP 服务器。 1. 工具简介 1.1 npx(Node.js/npm) npx 是 npm CLI(≥v5.2.0)

By Ne0inhk
解锁Dify与MySQL的深度融合:MCP魔法开启数据新旅程

解锁Dify与MySQL的深度融合:MCP魔法开启数据新旅程

文章目录 * 解锁Dify与MySQL的深度融合:MCP魔法开启数据新旅程 * 引言:技术融合的奇妙开篇 * 认识主角:Dify、MCP 与 MySQL * (一)Dify:大语言模型应用开发利器 * (二)MCP:连接的桥梁 * (三)MySQL:经典数据库 * 准备工作:搭建融合舞台 * (一)环境搭建 * (二)安装与配置 Dify * (三)安装与配置 MySQL * 关键步骤:Dify 与 MySQL 的牵手过程 * (一)安装必要插件 * (二)配置 MCP SSE * (三)创建 Dify 工作流 * (四)配置 Agent 策略 * (五)搭建MCP

By Ne0inhk
如何在Cursor中使用MCP服务

如何在Cursor中使用MCP服务

前言 随着AI编程助手的普及,越来越多开发者选择在Cursor等智能IDE中进行高效开发。Cursor不仅支持代码补全、智能搜索,还能通过MCP(Multi-Cloud Platform)服务,轻松调用如高德地图API、数据库等多种外部服务,实现数据采集、处理和自动化办公。 本文以“北京一日游自动化攻略”为例,详细讲解如何在 Cursor 中使用 MCP 服务,完成数据采集、数据库操作、文件生成和前端页面展示的全流程。 学习视频:cursor中使用MCP服务 一、什么是MCP服务? MCP(Multi-Cloud Platform)是Cursor内置的多云服务接口,支持调用地图、数据库、文件系统等多种API。通过MCP,开发者无需手动写HTTP请求或繁琐配置,只需在对话中描述需求,AI助手即可自动调用相关服务,极大提升开发效率。 二、环境准备 2.1 cursor Cursor重置机器码-解决Too many free trials. 2.

By Ne0inhk