Spring MVC 与 Web

共 19 题
📑 题目列表 19 题
#
★★★

1. Spring MVC 的线程模型(TaskExecutor)

请说明 Spring MVC 的线程模型,尤其是基于 TaskExecutor 的线程池如何处理请求?

  • 传统 Servlet 容器线程池模型(thread-per-request)
  • TaskExecutor 在异步请求、异步任务中的作用
  • 线程池配置与背压

传统 Spring MVC 基于 Servlet 容器(如 Tomcat)的线程池,采用 thread-per-request 模型:每个请求占用一个容器线程,从请求进入直到响应返回全程持有该线程。TaskExecutor 是 Spring 的线程池抽象,在 MVC 中用于异步请求处理(如 @Async、DeferredResult 的异步任务、异步监听器)和业务异步任务,其实现如 ThreadPoolTaskExecutor 封装了 JDK 线程池。通过配置核心线程数、最大线程数、队列容量与拒绝策略,可控制并发与资源占用。在异步请求场景,容器线程被释放回池,业务处理在 TaskExecutor 线程中完成。

线程模型决定了并发与资源使用方式。传统 MVC 的阻塞模型下线程数与并发强相关,配置不当会耗尽线程池;异步 + TaskExecutor 把"请求线程"与"业务线程"解耦,是提升吞吐的手段之一。

#
★★★

2. Spring MVC 与虚拟线程的协作(JDK 21+)

请说明 Spring MVC 在 JDK 21+ 虚拟线程下的协作方式,以及开启虚拟线程后线程模型的变化?

  • spring.threads.virtual.enabled=true 开启虚拟线程
  • Tomcat 运行在虚拟线程上
  • 高吞吐、低资源占用

Spring Boot 3.2+ 提供了 spring.threads.virtual.enabled=true 配置,开启后 Tomcat 等 Servlet 容器会改用虚拟线程处理请求,每个请求映射到一个虚拟线程,由 JDK 调度到少数平台线程(载体线程)上。虚拟线程在阻塞 IO 时自动挂起让出载体线程,从而用极少的平台线程支撑海量并发请求,降低线程创建与内存开销。Spring MVC 的编程模型不变(仍是阻塞式同步代码),但底层线程模型从"平台线程池"变为"虚拟线程 + 调度器"。

虚拟线程让 Spring MVC 的同步阻塞模型获得了媲美响应式的并发能力,同时保持简单。这是"用虚拟线程弥合阻塞与响应式差距"的关键路径,也是 Spring Boot 3.2+ 的重大演进。

#
★★★

3. DispatcherServlet 的请求处理流程,HandlerMapping、HandlerAdapter、HandlerInterceptor、ViewResolver 在责任链中的分工

请说明 DispatcherServlet 的请求处理流程,以及 HandlerMapping、HandlerAdapter、HandlerInterceptor、ViewResolver 在责任链中的分工?

  • DispatcherServlet 是前端控制器
  • HandlerMapping 找处理器、HandlerAdapter 执行处理器、ViewResolver 解析视图
  • HandlerInterceptor 的拦截时机

DispatcherServlet 是 Spring MVC 的前端控制器,所有请求先进入它。流程:先通过 HandlerMapping 根据请求 URL 找到对应的 Handler(处理器方法/控制器)与拦截器链;再通过 HandlerAdapter 找到对应适配器执行处理器,执行前会先经过 HandlerInterceptor 的 preHandle(若有拦截器返回 false 则终止);执行后经过 postHandle;随后用 ViewResolver 把逻辑视图名解析为 View(或方法直接返回 JSON 由 HttpMessageConverter 输出);最后渲染视图并执行 afterCompletion 清理。各组件分工:HandlerMapping 负责"映射",HandlerAdapter 负责"调用",HandlerInterceptor 负责"拦截",ViewResolver 负责"视图解析"。

DispatcherServlet 协调整个责任链,各组件通过策略模式解耦,职责单一。理解"Mapping→Adapter→Interceptor→ViewResolver"的调用顺序,是掌握 MVC 请求生命周期的基础。

#
★★

4. @Controller 与 @RestController 的差异

请说明 @Controller 与 @RestController 的差异,以及各自的使用场景?

  • @Controller 返回视图,@RestController 返回 JSON
  • @RestController = @Controller + @ResponseBody
  • 使用场景区分

@Controller 是传统 MVC 控制器,处理方法的返回值会交给 ViewResolver 解析为视图(JSP/Thymeleaf 模板),用于页面渲染场景;@RestController 是 @Controller 与 @ResponseBody 的组合注解,方法返回值会直接通过 HttpMessageConverter 序列化为 JSON/XML 等,不再经过视图解析,用于 RESTful API 场景。@RestController 等价于在每个方法上标注 @ResponseBody,本质是"以数据为响应"而非"以视图为响应"。

两者差异在于"响应的是视图还是数据"。微服务/前后端分离项目中几乎全用 @RestController,而需要服务端渲染页面的场景用 @Controller。

#
★★

5. @ControllerAdvice 的多模块拆分

请说明 @ControllerAdvice 在多模块项目中的拆分方式与注意事项?

  • @ControllerAdvice 全局异常处理与全局绑定
  • 按模块拆分多个 @ControllerAdvice
  • 优先级与作用范围控制

@ControllerAdvice 用于定义全局异常处理(@ExceptionHandler)、全局数据绑定(@InitBinder)、全局数据预处理(@ModelAttribute)。在多模块项目中,可按模块/业务域拆分多个 @ControllerAdvice 类,每个负责本模块的异常与绑定;通过 basePackages 属性指定生效的包范围,或使用 @RestControllerAdvice 限定 REST 场景。@Order 注解控制多个 @ControllerAdvice 的优先级,@ExceptionHandler 会按最精确匹配的异常类型选择处理逻辑。注意:当多个 @ControllerAdvice 都声明 @ExceptionHandler 处理同一异常时,优先级最高者生效。

多模块拆分 @ControllerAdvice 的核心是"按域隔离 + 用 basePackages 限定范围 + 用 @Order 控制优先级",避免全局配置互相覆盖,实现模块自治。

#
★★

6. @ControllerAdvice/@ExceptionHandler 的全局异常处理在 Spring 7 中的优先级与作用范围

请说明 @ControllerAdvice/@ExceptionHandler 的全局异常处理在 Spring 7 中的优先级与作用范围?

  • 全局异常处理的作用范围(跨控制器)
  • 异常匹配优先级(精确 > 继承)
  • 局部 @ExceptionHandler 优先于全局

@ExceptionHandler 既可以定义在单个 @Controller 内(局部),也可定义在 @ControllerAdvice 中(全局)。匹配优先级:局部 @ExceptionHandler 优先于全局 @ControllerAdvice 中的 @ExceptionHandler;同一异常匹配多个处理器时,按异常类型最精确优先,其次按 @Order 顺序。作用范围:@ControllerAdvice 内绑定所有控制器,可通过 basePackages 局限到指定包。在 Spring 7(Spring Framework 7.x,Spring Boot 4.x)中,异常解析机制沿用 ExceptionHandlerExceptionResolver,但 ErrorResponse 与 ProblemDetail 支持更完善,异常的响应结构更统一。

理解"局部优先于全局、精确优先于宽泛"的匹配规则,能避免多处理器互相覆盖。Spring 7 增强的 ProblemDetail 使全局异常响应更规范。

#
★★

7. @RequestMapping 与新版 @GetMapping/@PostMapping 在 Spring 7 中的注解元数据差异

请说明 @RequestMapping 与 @GetMapping/@PostMapping 等在 Spring 7 中的注解元数据差异?

  • @GetMapping 是 @RequestMapping(method=GET) 的组合
  • 注解组合与元数据继承
  • 语义差异

@GetMapping、@PostMapping、@PutMapping、@DeleteMapping、@PatchMapping 是 @RequestMapping 的特定方法组合注解:@GetMapping 即 @RequestMapping(method = RequestMethod.GET)。它们通过 Spring 的注解组合(@AliasFor 元注解)继承 @RequestMapping 的属性,限定 HTTP 方法,语义更清晰、减少错误。这些组合注解的元数据解析机制与 @RequestMapping 一致,只是限定了 method,本质等价而更明确。

组合注解是对 @RequestMapping 的二次封装,本质元数据相同,只是"限定了 method"。选 @GetMapping 是编码规范,可读性与防错性更好。

#
★★

8. @RestControllerAdvice/@ControllerAdvice 与全局异常处理

请说明 @RestControllerAdvice 与 @ControllerAdvice 的区别,以及它们如何用于全局异常处理?

  • @RestControllerAdvice = @ControllerAdvice + @ResponseBody
  • 全局异常处理(@ExceptionHandler)
  • 响应序列化

@RestControllerAdvice 是 @ControllerAdvice 与 @ResponseBody 的组合注解,其 @ExceptionHandler 方法返回值会直接序列化为 JSON 等数据格式,适用于 REST API 的全局异常处理;@ControllerAdvice 的 @ExceptionHandler 返回值默认走视图解析,适用于 MVC 页面场景。二者都用于集中定义全局异常处理、数据绑定与预处理。实践上,REST 项目用 @RestControllerAdvice 把所有业务异常统一转换为标准错误响应(如自定义 ErrorResult),避免每个控制器重复 try-catch。

区别本质是"响应数据还是视图"。@RestControllerAdvice 让全局异常处理直接输出 JSON,是统一微服务异常响应结构的标准做法。

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(BusinessException.class)
    public ErrorResult handleBusiness(BusinessException e) {
        return new ErrorResult(e.getCode(), e.getMessage());
    }
}
#
★★

9. Spring MVC 与 R2DBC 的反应式支持

请说明 Spring MVC 与 R2DBC 的反应式数据库支持,以及阻塞栈如何使用反应式数据库?

  • R2DBC 是反应式关系数据库驱动
  • 与 Spring MVC 阻塞栈的适配
  • WebFlux 中 R2DBC 的典型场景

R2DBC(Reactive Relational Database Connectivity)是反应式关系数据库驱动标准,提供基于 Project Reactor 的非阻塞数据库访问。它主要与 Spring WebFlux(反应式栈)配套使用,通过 DatabaseClient 或 Repository 提供非阻塞数据访问。Spring MVC 是阻塞栈,理论上可通过 block() 阻塞等待 R2DBC 结果,但会违背非阻塞初衷;因此 R2DBC 的典型场景是 WebFlux + R2DBC 全链路非阻塞。Spring Boot 对 R2DBC 提供 spring-boot-starter-data-r2dbc 自动配置,与 WebFlux、R2DBC 事务(TransactionalOperator)配合。

R2DBC 绑定反应式生态,与 MVC 阻塞栈匹配度低。选型上,"WebFlux + R2DBC"是纯反应式方案,而"MVC + JDBC/JPA"是传统阻塞方案。阻塞访问题在 MVC 中使用 R2DBC 需 block(),会阻塞线程,不推荐。

#
★★

10. Spring MVC 与 WebFlux 的选型边界

请说明 Spring MVC 与 WebFlux 的选型边界,什么场景该选 MVC,什么场景该选 WebFlux?

  • MVC 阻塞式、WebFlux 反应式
  • 选型依据:编程模型、吞吐、生态
  • 虚拟线程出现后的选型变化

Spring MVC 采用阻塞式编程与 Servlet 容器,代码简单、生态成熟、调试容易,适合绝大多数传统业务(常规 CRUD、事务、同步业务);WebFlux 采用反应式非阻塞,基于 Netty/Reactor,高并发、低线程占用,适合需要极低资源消耗支撑海量连接的场景(如网关、流式服务、高并发 IO 密集)。选型边界:高吞吐 IO 密集、连接数巨大、需要流式/背压场景选 WebFlux;业务复杂、开发者熟悉同步编程、需要成熟事务与生态选 MVC。JDK 21+ 虚拟线程出现后,MVC 也能以简单方式获得高并发,进一步缩小了 WebFlux 的选型优势,传统项目更倾向于 MVC + 虚拟线程。

"先 MVC,除非有明确的高并发/流式需求再考虑 WebFlux"是常见原则。虚拟线程削弱了 WebFlux 的并发优势,使大多数团队选 MVC 更务实。

#
★★

11. Spring MVC 中 WebSocket(@ServerEndpoint/TextWebSocketHandler)与 STOMP 消息代理的集成方式与边界

请说明 Spring MVC 中 WebSocket 的集成方式,以及基于 STOMP 消息代理的架构与边界?

  • @ServerEndpoint 与 TextWebSocketHandler 两种方式
  • Spring WebSocket 与 STOMP broker
  • 实时推送与消息路由

Spring MVC 中 WebSocket 集成有两种方式:一是基于 JSR-356 的 @ServerEndpoint(需要注册 ServerEndpointExporter),二是 Spring 自带的 TextWebSocketHandler/WebSocketHandler 抽象(更灵活,支持拦截器)。对于更复杂的实时通信,Spring 基于 WebSocket 提供 STOMP 消息协议支持:通过 WebSocketMessageBrokerConfigurer 配置消息端点(/topic、/queue 前缀)与 STOMP 消息代理(内存 SimpleBroker 或外部 RabbitMQ/ActiveMQ),客户端通过 @SendToUser、@MessageMapping 订阅与发送消息,实现发布/订阅的实时推送。边界:WebSocket 是长连接传输层,STOMP 是上层消息协议,Spring 负责两者的桥接与鉴权。

WebSocket 解决"长连接双向通信",STOMP 解决"其上消息路由与订阅"。Spring 的 WebSocketMessageBroker 把两者结合,实现聊天、推送等实时功能。

#
★★

12. Spring MVC 内容协商在 produces/consumes 与 HttpMessageConverter 链中的协作

请说明 Spring MVC 内容协商中 produces/consumes 与 HttpMessageConverter 链的协作机制?

  • produces/consumes 限定请求/响应媒体类型
  • HttpMessageConverter 完成消息序列化/反序列化
  • 内容协商(Accept 头)选择 converter

Spring MVC 的内容协商由 produces/consumes 属性与 HttpMessageConverter 链协作完成:consumes 声明方法接受的请求 Content-Type,produces 声明方法可产出的响应类型;当请求到达时,Spring 根据请求的 Content-Type 与 Accept 头,结合方法声明的 produces/consumes 进行匹配,再从 HttpMessageConverter 链中选择合适的转换器(如 MappingJackson2HttpMessageConverter 处理 JSON、StringHttpMessageConverter 处理字符串)完成请求体反序列化与响应体序列化。若无法匹配则抛 HttpMediaTypeNotAcceptableException 等异常。

produces/consumes 是"声明式约束",HttpMessageConverter 是"具体实现",二者通过内容协商(ContentNegotiation)串联,决定"用什么格式读入、用什么格式写出"。

#
★★

13. Spring MVC 如何处理 MultipartFile 文件上传,大文件上传为何应改用流式(StreamingHttpOutputMessage)而非全量加载到内存

请说明 Spring MVC 如何处理 MultipartFile 文件上传,以及大文件上传为何应改用流式处理而非全量加载到内存?

  • MultipartFile 与 MultipartResolver 机制
  • 全量加载到内存的内存风险
  • 流式处理(OutputStream/分块)避免 OOM

Spring MVC 通过 MultipartResolver(如 StandardServletMultipartResolver)解析 multipart/form-data 请求,将上传文件封装为 MultipartFile 对象,可通过 getInputStream 读取或 transferTo 保存。若直接调用 getBytes() 或把整个文件读入内存再处理,大文件会占用大量内存甚至 OOM。因此大文件上传应改用流式处理:通过 getInputStream() 逐块读取并写入目标(如文件系统、对象存储),或使用 StreamingHttpOutputMessage 等流式输出,避免一次性加载全部内容。同时配置 multipart 的 max-file-size、max-request-size 限制。

核心是"内存 vs 流式"。全量加载适合小文件,流式处理通过逐块读写控制内存占用,是大文件上传与下载的必备手段。

#
★★

14. Spring MVC 的 CORS 配置(@CrossOrigin/CorsConfigurationSource)

请说明 Spring MVC 的 CORS 配置方式,包括 @CrossOrigin 注解与 CorsConfigurationSource?

  • @CrossOrigin 注解级配置
  • CorsConfigurationSource 全局配置
  • 预检请求处理

Spring MVC 的 CORS 配置有两种方式:一是 @CrossOrigin 注解标注在控制器方法或类上,配置允许的来源、方法、请求头等;二是通过 CorsConfigurationSource 注册全局 CORS 配置,在 WebMvcConfigurer#addCorsMappings 中为路径注册 CorsConfiguration,或返回 CorsFilter 由容器处理。CORS 预检请求(OPTIONS)由 Spring 的 CorsFilter 或 HandlerMapping 的 CORS 处理拦截,返回允许的 CORS 头。与 Spring Security 配合时,需注意 CorsFilter 的处理顺序(通常应在 SecurityFilterChain 之前或按需配置)。

注解级适合局部、全局配置适合统一策略。CORS 本质是浏览器跨域策略,服务端返回对应的 Access-Control-* 头即可放行。

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("https://example.com")
                .allowedMethods("GET", "POST");
    }
}
#
★★

15. Spring MVC 的参数绑定与类型转换,ConversionService、Converter/Formatter 如何介入 @RequestParam/@PathVariable 绑定

请说明 Spring MVC 的参数绑定与类型转换机制,以及 ConversionService、Converter/Formatter 如何介入 @RequestParam/@PathVariable 绑定?

  • 参数绑定(@RequestParam/@PathVariable/@ModelAttribute)
  • ConversionService 类型转换体系
  • Converter 与 Formatter 的注册与介入

Spring MVC 在把请求参数绑定到方法参数时,会先按名称匹配,再通过 ConversionService 进行类型转换。ConversionService 是统一的类型转换入口,内置大量转换器(字符串转数字、日期等)。开发者可通过注册 Converter<S,T>(通用转换)或 Formatter(更面向展示层,含格式化)来扩展转换逻辑,在 WebMvcConfigurer#addFormatters 中注册。@RequestParam、@PathVariable、@ModelAttribute 的参数绑定都会经过 ConversionService 完成字符串到目标类型的转换,转换失败抛出 MethodArgumentTypeMismatchException。

参数绑定 = 名称匹配 + 类型转换。ConversionService 是可扩展的转换中枢,Converter/Formatter 是自定义扩展点,理解它的介入时机可解决"自定义类型参数绑定"问题。

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToUserConverter());
    }
}
#
★★

16. Servlet Filter 与 HandlerInterceptor 的执行顺序差异,以及它们分别适合做什么(字符集/鉴权/日志)

请说明 Servlet Filter 与 HandlerInterceptor 的执行顺序差异,以及各自适合承担哪些职责(字符集、鉴权、日志)?

  • Filter 在 Servlet 容器层,Interceptor 在 Spring MVC 层
  • Filter 先于 Interceptor 执行
  • 各自职责划分

Servlet Filter 属于 Servlet 容器规范,在请求进入 DispatcherServlet 之前执行,是"最外层"的拦截;HandlerInterceptor 属于 Spring MVC,在 DispatcherServlet 分发请求到控制器方法时执行(preHandle 在方法前、postHandle 在方法后、afterCompletion 在完成后)。因此 Filter 先于 Interceptor 执行。职责划分:Filter 适合容器级通用处理,如字符集编码(CharacterEncodingFilter)、全局鉴权、日志、CORS、压缩;HandlerInterceptor 适合与 Spring MVC 控制器相关的横切逻辑,如登录态校验、权限校验、日志埋点,且能访问 Handler 方法信息。原则是"越通用越靠前(Filter),越 MVC 相关越靠后(Interceptor)"。

执行顺序本质是"容器层 vs MVC 层"。Filter 看不到 Spring 的 Handler/Controller 信息,Interceptor 能访问。理解分层可正确选择拦截点。

#

17. Spring MVC 在 Spring Boot 3.5+/4.x 对 Jakarta EE 10/11 API(Servlet 6.0/6.1)的兼容演进

请说明 Spring MVC 在 Spring Boot 3.5+/4.x 中对 Jakarta EE 10/11 API(Servlet 6.0/6.1)的兼容演进?

  • jakarta.* 命名空间替代 javax.*
  • Servlet 6.0/6.1 的演进
  • Spring MVC 对 Jakarta EE 的适配

Spring Framework 6 起全面转向 Jakarta EE 9 命名空间(javax.* 改为 jakarta.*),Spring Boot 3.x 基于 Jakarta EE 10(Servlet 6.0),Spring Boot 4.x / Spring Framework 7 进一步支持 Jakarta EE 11(Servlet 6.1)。Spring MVC 在这套演进中持续适配新 Servlet 规范,兼容 Servlet 6.0/6.1 的 API。对开发者而言,导入的包名从 javax.servlet 变为 jakarta.servlet,Servlet 容器(Tomcat 10/11 等)也相应升级。Spring Boot 4.x 的基线要求 Tomcat 11 / Jetty 12 等支持 Jakarta EE 11 的容器。

关键演进是"命名空间迁移 + 规范版本升级"。理解 Jakarta EE 命名空间与 Servlet 版本对应关系,是排查容器兼容性问题的前提。

#

18. Spring MVC 异步请求(DeferredResult/Callable)在 JDK 25 虚拟线程下的简化实现

请说明 Spring MVC 异步请求(DeferredResult/Callable)在 JDK 25 虚拟线程下的简化实现?

  • DeferredResult 与 Callable 异步返回
  • 虚拟线程下请求线程释放
  • 简化实现:阻塞变异步

Spring MVC 的异步请求通过返回 Callable 或 DeferredResult 实现:Callable 返回一个可执行的任务,由容器异步线程池执行;DeferredResult 由业务线程在任意时刻设置结果,实现请求线程与业务线程解耦。在 JDK 25 虚拟线程下,可以更简单地实现"异步效果":因为虚拟线程阻塞代价极低,只需在方法内用普通同步阻塞调用(同步 IO 会挂起虚拟线程而非阻塞载体线程),无需显式 DeferredResult/Callable 也能获得高并发。因此虚拟线程让原本需要异步 API 的同步阻塞业务变得简单可行,DeferredResult 主要用于真正需要"推后返回"的复杂场景。

虚拟线程的"阻塞几乎免费"特性,让"同步代码 + 虚拟线程"可以替代多数异步编程诉求,DeferredResult/Callable 的适用性在虚拟线程时代收窄,回归其本来定位。

#

19. Spring MVC 静态资源处理(ResourceHttpRequestHandler)与 ETag/Last-Modified 缓存协商

请说明 Spring MVC 静态资源处理(ResourceHttpRequestHandler)以及 ETag/Last-Modified 缓存协商机制?

  • ResourceHttpRequestHandler 处理静态资源
  • 缓存协商(ETag/Last-Modified)
  • 静态资源缓存配置

Spring MVC 通过 ResourceHttpRequestHandler 处理静态资源(如 JS/CSS/图片),配置在 WebMvcConfigurer#addResourceHandlers 中将 URL 路径映射到资源目录(classpath:/static/ 等)。资源处理器支持 Last-Modified 与 ETag 缓存协商:基于资源最后修改时间生成 Last-Modified,基于资源内容生成 ETag。浏览器请求时携带 If-Modified-Since/If-None-Match,Spring 判断资源未变化则返回 304 Not Modified,节省带宽。同时可通过 ResourceChain 与缓存策略(CacheControl)配置浏览器缓存与离线功能。

静态资源优化核心是"缓存协商 + 缓存控制"。ETag/Last-Modified 减少重复传输,Cache-Control 控制浏览器缓存时长,是 Web 性能优化基础。