一、Spring MVC 概述与核心定位

Spring MVC(Spring Web MVC)是 Spring Framework 下的子模块,实现了基于 Model-View-Controller(模型-视图-控制器)设计模式的轻量级 Web 框架。它以 前端控制器模式(Front Controller Pattern) 为核心,通过 DispatcherServlet 统一接收并分发 HTTP 请求,实现请求处理流程的解耦与可扩展性。

⚡ 核心优势:松耦合、可插拔、注解驱动、与 Spring 生态无缝集成、支持 RESTful 风格、异常统一处理、拦截器机制完善。

在 Java Web 开发领域,Spring MVC 因其简洁、灵活、健壮的特性,长期占据主流地位。尤其在 Java 后端开发面试中,Spring MVC 面试题常问内容高度聚焦于其核心组件、请求生命周期、注解使用及实际问题解决能力。

从企业级应用角度看,Spring MVC 不仅承担了 MVC 分层职责,更通过与 Spring IoC、AOP、事务管理等模块的深度整合,为构建高内聚、低耦合、易测试、可维护的 Web 应用提供了坚实基础。

二、Spring MVC 核心架构与组件解析

Spring MVC 的架构设计遵循“高内聚、低耦合”原则,各组件职责清晰、协作有序。下图展示了其核心组件间的调用关系:

请求入口
1. DispatcherServlet(前端控制器)

作为中央调度器,接收所有 HTTP 请求,是整个流程的起点。它不处理业务逻辑,仅负责协调各组件协同工作。

映射匹配
2. HandlerMapping(处理器映射器)

根据请求 URL 查找对应的 Handler(即 Controller 方法)。Spring 提供多种实现:如基于注解的 RequestMappingHandlerMapping、基于 XML 配置的 BeanNameUrlHandlerMapping 等。

适配调用
3. HandlerAdapter(处理器适配器)

负责执行具体的 Handler。由于 Handler 可能是不同形式(如实现 Controller 接口、注解方法等),Adapter 充当“适配器”,屏蔽调用差异,统一调用方式(如 handle() 方法)。

业务处理
4. Controller(控制器)

实际处理请求的组件,由开发者编写。接收请求参数、调用 Service 层、返回 ModelAndView 或直接返回视图名/数据。

视图渲染
5. ViewResolver(视图解析器)

将逻辑视图名(如 "user/list")解析为物理视图路径(如 "/WEB-INF/views/user/list.jsp"),并完成视图渲染。

响应返回
6. View(视图)

最终将模型数据渲染为 HTML、JSON、XML 等格式返回给客户端。

⚙️ 关键说明:Spring MVC 默认使用 RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter 支持注解驱动开发,无需额外配置。

三、请求处理完整流程详解(面试高频)

当浏览器发起一个 HTTP 请求(如 GET /user/123),Spring MVC 的处理流程如下:

  1. 请求抵达:请求被前端过滤器(如 CharacterEncodingFilter)处理后,由 DispatcherServlet 拦截。
  2. 查找 Handler:DispatcherServlet 调用 HandlerMapping,根据 URL 匹配到对应 HandlerExecutionChain,其中包含目标 Controller 方法及所有拦截器。
  3. 适配执行:DispatcherServlet 通过 HandlerAdapter 调用目标方法。在此过程中完成:
    • 参数解析(如 @PathVariable、@RequestParam、@RequestBody)
    • 数据校验(配合 @Valid)
    • 类型转换(如字符串转整型)
    • 异常预处理
  4. 返回结果:Controller 方法执行后返回 ModelAndView 对象(含模型数据和视图名),或直接返回字符串(视图名)、String、Map、@ResponseBody 对象等。
  5. 视图解析:若返回逻辑视图名,ViewResolver 解析为具体视图(如 JSP、Thymeleaf 模板)。
  6. 渲染响应:视图引擎将模型数据渲染成 HTML/JSON 等,通过 HttpServletResponse 返回给客户端。
示例:请求流程模拟
// 请求:GET /api/users/1024
// 1. DispatcherServlet 拦截
// 2. HandlerMapping 找到:@GetMapping("/api/users/{id}")
// 3. HandlerAdapter 调用方法:
public ResponseEntity<User> getUser(@PathVariable("id") Long userId) {
    User user = userService.findById(userId); // 调用 Service
    return ResponseEntity.ok(user); // 返回 JSON(含 @ResponseBody)
}
// 4. HttpMessageConverter 将 User 对象转为 JSON 字符串
// 5. 写入响应体,完成响应

延伸思考:若请求路径匹配多个 Handler(如多个 @RequestMapping 注解匹配同一 URL),Spring MVC 如何选择?—— 依据 注解匹配度优先级:路径变量匹配 > 请求参数匹配 > 请求方法匹配 > 请求头匹配。匹配度越高,优先级越高。

四、常用注解深度解析(面试必考)

Spring MVC 提供了丰富的注解,简化开发。以下为高频使用注解及其原理:

注解 用途 典型场景
@Controller 标识控制器类,自动注册为 Bean 返回视图页面的 Web 控制器
@RestController 等价于 @Controller + @ResponseBody RESTful 接口开发,直接返回 JSON/XML
@RequestMapping 通用映射注解,支持 GET/POST 等 方法级路径映射、参数/方法限定
@GetMapping/@PostMapping/@PutMapping/@DeleteMapping @RequestMapping 的快捷方式 REST 风格接口开发
@PathVariable 绑定 URL 中的占位符 /user/{id} → id 参数
@RequestParam 绑定请求参数(Query String 或 Form) ?name=Tom&age=20
@RequestBody 绑定请求体(JSON/XML),触发 HttpMessageConverter POST JSON 请求体解析
@ResponseBody 方法返回值直接作为响应体(JSON/XML) REST 接口返回数据
@ModelAttribute 绑定请求参数到对象;或预置模型数据 表单提交对象封装、预加载数据
@SessionAttributes 将模型数据存入 Session 跨请求数据共享(谨慎使用)
示例:复杂参数绑定
@PostMapping("/api/users")
public ResponseEntity<User> createUser(
    @Valid @RequestBody UserCreateRequest request,  // JSON 请求体
    @RequestHeader("X-Client-Version") String clientVersion, // 请求头
    @CookieValue(value = "JSESSIONID", required = false) String sessionId // Cookie
) {
    // request 对象含字段:username, email, age(自动校验)
    // clientVersion = "2.3.1"
    // sessionId = "ABC123..."
    return ResponseEntity.ok(userService.save(request));
}

面试加分点:
① @RequestBody 与 @RequestParam 的区别:
@RequestBody 读取请求体(JSON/XML),需配合 Content-Type: application/json;
@RequestParam 读取 URL 查询参数或 Form 表单字段(key=value)。两者不可混用——请求体只能读取一次!

② @ModelAttribute 的双重角色:
方法级:在 Controller 方法执行前注入模型数据(常用于编辑前预加载数据);
参数级:将多个请求参数封装为对象(如表单提交绑定到 User 对象)。

五、核心组件详解与扩展实践

5.1 HandlerMapping 与 HandlerAdapter 拓展

虽然 Spring 默认已配置 RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter,但开发者可自定义组件:

  • 自定义 HandlerMapping:实现 HandlerMapping 接口,支持基于数据库的动态路由配置(如 URL 与 Controller 方法的映射关系存储于 DB)。
  • 自定义 HandlerAdapter:实现 HandlerAdapter 接口,支持非标准方法签名(如支持函数式接口 Function<ServerRequest, ServerResponse>)。
⚙️ 扩展建议:实际项目中极少自定义,但需理解其工作原理。Spring Boot 通过 WebMvcRegistrations 接口可定制全局配置。

5.2 ViewResolver 高级用法

默认使用 InternalResourceViewResolver(支持 JSP),但可替换为更现代的模板引擎:

视图技术 ViewResolver 实现 适用场景
Thymeleaf ThymeleafViewResolver 现代 Web 应用,支持 HTML5 原生模板
Freemarker FreeMarkerViewResolver 传统项目迁移,模板语法简洁
JSON(REST) 自动注册(无需配置) 配合 @ResponseBody,通过 Jackson/Gson 序列化
Excel/PDF AbstractExcelView / AbstractPdfView 动态报表导出
Thymeleaf 配置示例
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Bean
    public ViewResolver thymeleafViewResolver() {
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setTemplateEngine(templateEngine());
        resolver.setCharacterEncoding("UTF-8");
        return resolver;
    }
    @Bean
    public TemplateEngine templateEngine() {
        SpringTemplateEngine engine = new SpringTemplateEngine();
        engine.setTemplateResolver(templateResolver());
        return engine;
    }
    @Bean
    public ITemplateResolver templateResolver() {
        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setPrefix("classpath:/templates/");
        resolver.setSuffix(".html");
        resolver.setTemplateMode("HTML");
        return resolver;
    }
}

5.3 MultipartResolver 文件上传支持

处理文件上传需配置 MultipartResolver(Spring Boot 默认启用 StandardServletMultipartResolver):

Controller 方法
@PostMapping("/upload")
public ResponseEntity<String> upload(@RequestParam("file") MultipartFile file) {
    String fileName = file.getOriginalFilename();
    file.transferTo(new File("/tmp/" + fileName));
    return ResponseEntity.ok("上传成功: " + fileName);
}

关键配置项:
multipart.maxFileSize(单文件上限)、multipart.maxRequestSize(总请求上限)——Spring Boot 默认 1MB/10MB。

六、异常处理机制(企业级应用必备)

Spring MVC 提供三层异常处理机制,层层递进:

  1. 方法级:@ExceptionHandler 注解方法,仅处理当前 Controller 的异常。
  2. 全局级:@ControllerAdvice + @ExceptionHandler,全局统一处理所有 Controller 异常。
  3. 底层级:Servlet Filter(如 SimpleMappingExceptionResolver)。
全局异常处理器示例
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(BindException.class)
    public ResponseEntity<Map<String, Object>> handleValidationException(BindException ex) {
        Map<String, Object> result = new HashMap<>();
        result.put("code", 400);
        result.put("message", "参数校验失败");
        result.put("errors", ex.getFieldErrors().stream()
            .map(e -> e.getField() + ": " + e.getDefaultMessage())
            .collect(Collectors.toList()));
        return ResponseEntity.badRequest().body(result);
    }
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Map<String, Object>> handleException(Exception ex) {
        log.error("系统异常", ex);
        return ResponseEntity.status(500).body(Map.of(
            "code", 500,
            "message", "服务器内部错误"
        ));
    }
}

面试高频点:
① @ControllerAdvice 的作用范围:
可通过 annotations()、basePackageClasses()、basePackages() 限定作用域,避免全局影响性能。

② 异常处理与 @ResponseBody 的兼容性:
使用 @RestControllerAdvice(等价于 @ControllerAdvice + @ResponseBody)可直接返回 JSON,无需额外配置。

七、拦截器(Interceptor)原理与实战

拦截器是 AOP 思想在 Web 层的典型应用,用于在请求处理前后执行通用逻辑(如权限校验、日志记录、性能统计)。

preHandle
Controller 方法执行前

返回 true 继续执行;false 中断流程(需手动响应)。

postHandle
Controller 方法执行后,视图渲染前

可修改模型数据或视图。

afterCompletion
整个请求完成后

用于资源清理、异常日志记录。

权限拦截器示例
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String token = request.getHeader("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            response.setStatus(401);
            response.setContentType("application/json");
            response.getWriter().write("{\"code\":401,\"message\":\"未授权\"}");
            return false;
        }
        // 验证 token(如 JWT 解析)
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        // 可添加公共模型数据
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        // 记录请求耗时、异常日志
        log.info("请求完成: {} {},耗时: {}ms", request.getMethod(), request.getRequestURI(),
            System.currentTimeMillis() - (Long)request.getAttribute("startTime"));
    }
}

配置拦截器:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor())
            .addPathPatterns("/api/**")  // 拦截路径
            .excludePathPatterns("/api/public/**"); // 排除路径
    }
}
⚙️ 注意:拦截器与 Filter 区别:
Filter:Servlet 规范,依赖容器,优先级更高;
Interceptor:Spring 自身机制,可访问 Spring Bean,更灵活。

八、数据绑定与类型转换机制

Spring MVC 自动将请求参数绑定到方法参数或对象,依赖以下机制:

8.1 核心组件

  • WebDataBinder:绑定请求参数到 Java 对象,支持类型转换、数据校验。
  • ConversionService:提供类型转换服务(默认使用 DefaultFormattingConversionService)。
  • Validator:执行 JSR-303/JSR-380 校验(如 @NotNull、@Size)。

8.2 自定义类型转换器

处理特殊格式转换(如字符串转枚举、日期格式化):

// 1. 定义转换器
public class StringToLocalDateConverter implements Converter<String, LocalDate> {
    @Override
    public LocalDate convert(String source) {
        return LocalDate.parse(source, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
    }
}
// 2. 注册转换器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToLocalDateConverter());
    }
}

8.3 自定义数据绑定

通过 @InitBinder 自定义绑定规则:

@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(Date.class, new CustomDateEditor(
        new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), true));
}

面试要点:
① 对象嵌套绑定:
请求参数 user.name=Tom&user.address.city=Beijing 可自动绑定到 User 对象的嵌套属性(需提供 setter 方法)。

② 集合参数绑定:
请求参数 hobbies=game&hobbies=music → List<String> hobbies;
请求参数 users[0].name=Tom&users[1].name=Jerry → List<User> users。

九、文件上传与下载实现要点

9.1 文件上传

核心依赖 MultipartFile 接口,支持多文件上传:

@PostMapping("/upload/multi")
public ResponseEntity<List<String>> uploadMulti(@RequestParam("files") List<MultipartFile> files) {
    List<String> fileNames = new ArrayList<>();
    for (MultipartFile file : files) {
        String fileName = UUID.randomUUID() + "-" + file.getOriginalFilename();
        file.transferTo(new File("/tmp/" + fileName));
        fileNames.add(fileName);
    }
    return ResponseEntity.ok(fileNames);
}

9.2 文件下载

通过设置响应头触发浏览器下载:

@GetMapping("/download/{fileName}")
public void download(@PathVariable String fileName, HttpServletResponse response) throws IOException {
    File file = new File("/tmp/" + fileName);
    response.setContentType("application/octet-stream");
    response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
    response.setContentLength((int) file.length());
    Files.copy(file.toPath(), response.getOutputStream());
}
⚙️ 安全提示:
1. 文件名需校验路径遍历(如 ../../);
2. 文件类型需白名单过滤;
3. 上传目录应不可执行(如 Nginx 配置 location /uploads { deny all; })。

十、性能优化与最佳实践

Spring MVC 高性能关键点:

  • 开启缓存:对静态资源(CSS/JS)配置缓存头(Cache-Control)。
  • 异步请求:使用 @Async + Callable、DeferredResult 或 WebAsyncTask 释放 Tomcat 线程。
  • 禁用会话:对无状态接口,禁用 HttpSession(减少内存占用)。
  • 压缩响应:配置 Gzip 压缩(Spring Boot 默认启用)。
  • 减少注解扫描:精确指定 @ComponentScan 范围,避免全包扫描。
异步请求示例
@GetMapping("/async")
public Callable<String> async() {
    return () -> {
        Thread.sleep(2000); // 模拟耗时操作
        return "处理完成";
    };
}

线程模型说明:
异步请求中,Tomcat 线程在进入 Controller 前释放,耗时操作由业务线程池执行,完成后由 Tomcat 线程完成响应——显著提升吞吐量。

十一、Spring MVC 面试题常问|高频问答集锦

1. Spring MVC 与 Struts2 的区别?

① Spring MVC 基于方法设计,Struts2 基于类;
② Spring MVC 更轻量(单例 Controller),Struts2 每次请求创建新对象;
③ Spring MVC 注解驱动更简洁,Struts2 XML 配置繁琐;
④ Spring MVC 与 Spring 生态无缝集成。

2. DispatcherServlet 初始化时机?

默认在第一次请求时初始化(懒加载)。可通过 <load-on-startup>1</load-on-startup> 配置为应用启动时初始化。

3. 如何解决 POST 请求中文乱码?

① 配置 CharacterEncodingFilter;
② 确保请求头 Content-Type: application/x-www-form-urlencoded; charset=UTF-8;
③ Spring Boot 默认使用 UTF-8,无需额外配置。

4. @RequestBody 与 @ModelAttribute 的区别?

① @RequestBody 读取请求体(JSON),触发 HttpMessageConverter;
② @ModelAttribute 从请求参数/表单绑定对象;
③ @ModelAttribute 方法可预置模型数据,@RequestBody 不能。

5. 如何自定义 404/500 错误页面?

Spring Boot:在 resources/templates/ 下放置 error/404.html、error/500.html;
Spring MVC:配置 SimpleMappingExceptionResolver 或使用全局异常处理器。

6. @Valid 与 @Validated 的区别?

① @Valid:JSR-303 标准注解,支持嵌套校验;
② @Validated:Spring 扩展,支持分组校验(如 @Validated(GroupA.class))。

十二、网友还关心:Spring MVC 周边知识拓展

延伸学习建议:
掌握 Spring MVC 后,建议深入:
① Spring Boot 自动配置原理:理解 Spring MVC 自动装配的条件注解(如 @ConditionalOnClass);
② WebFlux 响应式编程:对比非阻塞 I/O 模型;
③ Spring Security 安全框架:整合认证授权机制;
④ 网关层 Nginx + Spring Cloud Gateway:构建微服务架构。