跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Spring MVC 请求处理流程与 DispatcherServlet 核心源码解析

深入剖析 Spring MVC 请求处理全流程,从 Tomcat 接收请求到 DispatcherServlet 分发,详细解读 doDispatch 核心方法的九大步骤。涵盖 HandlerMapping 匹配机制、参数绑定原理、拦截器与过滤器区别、视图解析及异常处理策略。通过源码级分析与实战案例,揭示了 HandlerMapping 配置不当导致的 404 问题、大文件上传内存溢出风险以及拦截器顺序陷阱。文章提供了基于 PathPatternParser 的性能优化方案,展示了如何从 3000QPS 提升至 15000QPS 的具体实践,并总结了生产环境下的配置规范与排查指南,帮助开发者彻底掌握 Spring MVC 工作原理与调优技巧。

深海蔚蓝发布于 2026/3/28更新于 2026/10/25101 浏览
Spring MVC 请求处理流程与 DispatcherServlet 核心源码解析

Spring MVC 请求处理流程与 DispatcherServlet 核心源码解析

实战中的典型问题

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

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

这些问题的根源,都在于不理解 Spring MVC 的请求处理流程。今天我就结合这些年踩过的坑、调试源码的经验,把这块内容彻底讲透。

文章配图

从 Tomcat 到 DispatcherServlet:请求的完整路径

请求是怎么到你 Controller 的?

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

文章配图

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

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

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); 
        initRequestToViewNameTranslator(context); 
        initViewResolvers(context);          
        initFlashMapManager(context);        
    }
}
// 6. 异常处理器
// 7. 视图名称转换器
// 8. 视图解析器
// 9. FlashMap 管理器

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

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

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

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

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:Spring MVC 请求处理九步时序图

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

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;
    }
}
获取 Handler

这里 Spring 会遍历所有的 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());
    }
}

HandlerMapping:请求的导航系统

四种 HandlerMapping 的区别

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

类型匹配方式性能适用场景
RequestMappingHandlerMapping@RequestMapping 注解中等RESTful API
BeanNameUrlHandlerMappingBean 名称匹配 URL高简单映射
SimpleUrlHandlerMapping显式配置 URL 映射高静态资源、固定路由
RouterFunctionMapping函数式路由高WebFlux、响应式

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;
    }
}

性能测试数据:

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

接口数量平均匹配时间 (ms)QPSCPU 使用率
1000.12850045%
10000.45220068%
50002.3443085%

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

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

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

路径匹配的性能优化

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 倍

参数绑定:Spring MVC 的魔法

参数绑定是怎么实现的?

这是 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;
}

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

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

自定义参数解析器实战

我做过一个需求:所有接口都要记录操作人。如果每个方法都加个@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) {
        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) {
        orderDTO.setUserId(currentUser.getId());
        return orderService.createOrder(orderDTO);
    }
}

好处:代码更简洁,避免重复代码,统一用户信息获取逻辑。

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

区别到底在哪里?

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

维度Filter(过滤器)Interceptor(拦截器)
所属规范Servlet 规范Spring MVC 规范
使用场景编码转换、安全过滤、日志记录权限校验、日志记录、性能监控
执行时机在 DispatcherServlet 之前在 DispatcherServlet 之后
获取 Spring Bean不能直接注入可以直接注入
异常处理只能在 Filter 中处理可以用@ControllerAdvice 处理

拦截器链的执行顺序

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

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        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:拦截器链执行顺序

性能监控拦截器实战

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

@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) {
                log.warn("慢请求:{} {}, 耗时:{}ms", request.getMethod(), request.getRequestURI(), costTime);
                AlertManager.sendSlowRequestAlert(request, costTime);
            }
        }
        startTimeHolder.remove();
    }
}

生产环境数据:

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

视图解析:不只是返回 JSON

多种视图解析器的选择

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

视图技术解析器性能适用场景
JSPInternalResourceViewResolver中等老项目迁移
ThymeleafThymeleafViewResolver高新项目、模板邮件
FreeMarkerFreeMarkerViewResolver高报表生成
JSONMappingJackson2JsonView高RESTful API
PDFPdfViewResolver低文件导出

视图解析过程源码分析

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 {
        for (ViewResolver viewResolver : this.viewResolvers) {
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
        return null;
    }
}

性能优化点: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());        // 默认视图解析器放最后
    }
}

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

异常处理的三种方式

我推荐的方式优先级:@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 表示让其他解析器处理
    }
}

异常处理器的执行顺序

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

文章配图

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

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

性能优化实战经验

Spring MVC 性能瓶颈分析

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

瓶颈点影响程度优化方案
HandlerMapping 匹配高减少接口数量、优化 URL 设计
参数绑定中使用简单类型、避免复杂对象
拦截器链中减少拦截器数量、异步处理
视图渲染低使用缓存、避免复杂逻辑

实战优化:从 3000QPS 到 15000QPS

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

1. 优化 HandlerMapping
@Configuration
public class OptimizedWebConfig implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        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(() -> 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;
    }
}

性能测试对比

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

指标优化前优化后提升
QPS3,20015,000369%
平均响应时间85ms35ms59%
P99 响应时间420ms150ms64%
CPU 使用率75%65%降低
内存使用2.3GB1.8GB22%

常见问题排查指南

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()
                ));
    }
}

参数绑定失败排查

参数绑定失败常见原因:

参数校验失败

@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 格式错误");
}

类型转换失败

@GetMapping("/user")
public User getUser(@RequestParam Long id) {
    // 如果传入"abc",会绑定失败
}

生产环境最佳实践

我的 Spring MVC 配置宪法

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

第一条:统一异常处理

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

第二条:合理使用拦截器

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

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

配置模板

@Configuration
public class ProductionWebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("https://production.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowCredentials(true)
                .maxAge(3600);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor()).order(1);
        registry.addInterceptor(new AuthInterceptor()).order(2);
        registry.addInterceptor(new PerformanceInterceptor()).order(3);
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig config = new FastJsonConfig();
        config.setSerializerFeatures(SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat);
        converter.setFastJsonConfig(config);
        converters.add(0, converter);
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/")
                .setCacheControl(CacheControl.maxAge(30, TimeUnit.DAYS));
    }

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setPatternParser(new PathPatternParser());
        configurer.setUseTrailingSlashMatch(false);
    }
}

最后的话

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 性能调优 - 底层网络优化

目录

  1. Spring MVC 请求处理流程与 DispatcherServlet 核心源码解析
  2. 实战中的典型问题
  3. 从 Tomcat 到 DispatcherServlet:请求的完整路径
  4. 请求是怎么到你 Controller 的?
  5. DispatcherServlet 的初始化:不是你想的那样简单
  6. 请求处理九大步:DispatcherServlet 的工作流程
  7. doDispatch 方法:Spring MVC 的心脏
  8. Multipart 检查
  9. 获取 Handler
  10. HandlerMapping:请求的导航系统
  11. 四种 HandlerMapping 的区别
  12. RequestMappingHandlerMapping 的匹配算法
  13. 路径匹配的性能优化
  14. 参数绑定:Spring MVC 的魔法
  15. 参数绑定是怎么实现的?
  16. 自定义参数解析器实战
  17. 拦截器 vs 过滤器:别傻傻分不清
  18. 区别到底在哪里?
  19. 拦截器链的执行顺序
  20. 性能监控拦截器实战
  21. 视图解析:不只是返回 JSON
  22. 多种视图解析器的选择
  23. 视图解析过程源码分析
  24. 异常处理:优雅地处理错误
  25. 异常处理的三种方式
  26. 方式一:@ControllerAdvice(最推荐)
  27. 方式二:@ExceptionHandler(控制器内)
  28. 方式三:实现 HandlerExceptionResolver
  29. 异常处理器的执行顺序
  30. 性能优化实战经验
  31. Spring MVC 性能瓶颈分析
  32. 实战优化:从 3000QPS 到 15000QPS
  33. 1. 优化 HandlerMapping
  34. 2. 优化拦截器
  35. 3. 使用异步处理
  36. 性能测试对比
  37. 常见问题排查指南
  38. 404 问题排查
  39. 参数绑定失败排查
  40. 生产环境最佳实践
  41. 我的 Spring MVC 配置宪法
  42. 第一条:统一异常处理
  43. 第二条:合理使用拦截器
  44. 第三条:规范 URL 设计
  45. 第四条:参数校验要彻底
  46. 第五条:监控和日志
  47. 配置模板
  48. 最后的话
  49. 推荐阅读
  50. 官方文档
  51. 源码学习
  52. 实践指南
  53. 性能优化

更多推荐文章

查看全部
  • 爬虫自学难度分析与学习路径指南
  • Debian 系统 libwebkit2gtk-4.1-0 安装后无法加载问题排查
  • 汇川机器人软件 RobotLab 常规操作指南
  • 基于 Java 的外卖点餐系统设计与实现
  • 计算机基础知识总结:操作系统、网络、数据库与 C++
  • DeepSeek 结合通义万相制作 AI 视频实战
  • MIT 电机模式控制详解:参数配置与调试实战
  • AI Copilot 代码推荐出错率高?掌握调试技巧提升准确率
  • DeepSeek-R1 大模型基于 MS-Swift 框架部署与微调指南
  • 大模型学习的五个进阶阶段指南
  • 2025 年 9 月 GESP C++ 三级真题解析
  • 大语言模型(LLM)原理、应用与未来趋势
  • PyQt5 基础与常用控件入门教程
  • Spring Boot 2.x 进程内缓存实战与 Cache 注解详解
  • Android 开发进阶指南:核心技能与避坑建议
  • MySQL 视图与触发器核心用法解析
  • 基于 Semantic Kernel 与 Pandora Box 快速构建 Code Agent
  • Vue CLI 3 项目目录结构详解
  • 基于 AI 的 Java 代码生成与优化实践指南
  • Python 基础语法详解:变量、类型、运算符与交互

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online