一、Spring MVC 概述与核心定位
Spring MVC(Spring Web MVC)是 Spring Framework 下的子模块,实现了基于 Model-View-Controller(模型-视图-控制器)设计模式的轻量级 Web 框架。它以 前端控制器模式(Front Controller Pattern) 为核心,通过 DispatcherServlet 统一接收并分发 HTTP 请求,实现请求处理流程的解耦与可扩展性。
在 Java Web 开发领域,Spring MVC 因其简洁、灵活、健壮的特性,长期占据主流地位。尤其在 Java 后端开发面试中,Spring MVC 面试题常问内容高度聚焦于其核心组件、请求生命周期、注解使用及实际问题解决能力。
从企业级应用角度看,Spring MVC 不仅承担了 MVC 分层职责,更通过与 Spring IoC、AOP、事务管理等模块的深度整合,为构建高内聚、低耦合、易测试、可维护的 Web 应用提供了坚实基础。
二、Spring MVC 核心架构与组件解析
Spring MVC 的架构设计遵循“高内聚、低耦合”原则,各组件职责清晰、协作有序。下图展示了其核心组件间的调用关系:
作为中央调度器,接收所有 HTTP 请求,是整个流程的起点。它不处理业务逻辑,仅负责协调各组件协同工作。
根据请求 URL 查找对应的 Handler(即 Controller 方法)。Spring 提供多种实现:如基于注解的 RequestMappingHandlerMapping、基于 XML 配置的 BeanNameUrlHandlerMapping 等。
负责执行具体的 Handler。由于 Handler 可能是不同形式(如实现 Controller 接口、注解方法等),Adapter 充当“适配器”,屏蔽调用差异,统一调用方式(如 handle() 方法)。
实际处理请求的组件,由开发者编写。接收请求参数、调用 Service 层、返回 ModelAndView 或直接返回视图名/数据。
将逻辑视图名(如 "user/list")解析为物理视图路径(如 "/WEB-INF/views/user/list.jsp"),并完成视图渲染。
最终将模型数据渲染为 HTML、JSON、XML 等格式返回给客户端。
RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter 支持注解驱动开发,无需额外配置。
三、请求处理完整流程详解(面试高频)
当浏览器发起一个 HTTP 请求(如 GET /user/123),Spring MVC 的处理流程如下:
- 请求抵达:请求被前端过滤器(如
CharacterEncodingFilter)处理后,由DispatcherServlet拦截。 - 查找 Handler:
DispatcherServlet调用HandlerMapping,根据 URL 匹配到对应HandlerExecutionChain,其中包含目标 Controller 方法及所有拦截器。 - 适配执行:
DispatcherServlet通过HandlerAdapter调用目标方法。在此过程中完成:- 参数解析(如
@PathVariable、@RequestParam、@RequestBody) - 数据校验(配合
@Valid) - 类型转换(如字符串转整型)
- 异常预处理
- 参数解析(如
- 返回结果:Controller 方法执行后返回
ModelAndView对象(含模型数据和视图名),或直接返回字符串(视图名)、String、Map、@ResponseBody对象等。 - 视图解析:若返回逻辑视图名,
ViewResolver解析为具体视图(如 JSP、Thymeleaf 模板)。 - 渲染响应:视图引擎将模型数据渲染成 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>)。
WebMvcRegistrations 接口可定制全局配置。
5.2 ViewResolver 高级用法
默认使用 InternalResourceViewResolver(支持 JSP),但可替换为更现代的模板引擎:
| 视图技术 | ViewResolver 实现 | 适用场景 |
|---|---|---|
| Thymeleaf | ThymeleafViewResolver |
现代 Web 应用,支持 HTML5 原生模板 |
| Freemarker | FreeMarkerViewResolver |
传统项目迁移,模板语法简洁 |
| JSON(REST) | 自动注册(无需配置) | 配合 @ResponseBody,通过 Jackson/Gson 序列化 |
| Excel/PDF | AbstractExcelView / AbstractPdfView |
动态报表导出 |
@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):
@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 提供三层异常处理机制,层层递进:
- 方法级:
@ExceptionHandler注解方法,仅处理当前 Controller 的异常。 - 全局级:
@ControllerAdvice+@ExceptionHandler,全局统一处理所有 Controller 异常。 - 底层级: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 层的典型应用,用于在请求处理前后执行通用逻辑(如权限校验、日志记录、性能统计)。
返回 true 继续执行;false 中断流程(需手动响应)。
可修改模型数据或视图。
用于资源清理、异常日志记录。
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: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 MVC 后,建议深入:
① Spring Boot 自动配置原理:理解 Spring MVC 自动装配的条件注解(如 @ConditionalOnClass);
② WebFlux 响应式编程:对比非阻塞 I/O 模型;
③ Spring Security 安全框架:整合认证授权机制;
④ 网关层 Nginx + Spring Cloud Gateway:构建微服务架构。