二、Spring Boot Web 开发(S09–S12)
S09 · 知识点:@RestController 与 @Controller
【题目】关于 @RestController 与 @Controller,下列说法正确的是?
A. @RestController 的方法返回值会经过视图解析器解析 B. @RestController 只能返回 JSON 数据,不能返回字符串 C. @Controller 的方法返回值一定被解析为 JSON D. @RestController 等价于 @Controller + @ResponseBody
答案:D
【考点】@RestController 的组合关系与 @Controller 的视图解析机制。
【结论】选 D。@RestController 是 @Controller 与 @ResponseBody 的组合注解,方法返回值直接写入 HTTP 响应体,不会经过视图解析器。
【逐项辨析】 D. 正确。@RestController 的源码上同时标注了 @Controller 与 @ResponseBody,属于组合注解,语义完全等价。 B. 错误。@RestController 的返回值由 HttpMessageConverter 负责转换,既可以返回 JSON 对象,也可以返回普通字符串、byte[]、ResponseEntity 等。 C. 错误。@Controller 单独使用时,返回 String 默认被当作视图名,由视图解析器(如 Thymeleaf、InternalResourceViewResolver)渲染页面;只有加上 @ResponseBody 才会返回 JSON。 A. 错误。@RestController 隐含 @ResponseBody,返回值直接序列化后写入响应体,不走视图解析流程。
【知识点】 @RestController 是 Spring 4.0 引入的 convenience annotation,其元注解包含 @Target、@Retention、@Documented、@Controller、@ResponseBody。当 DispatcherServlet 处理请求时,RequestMappingHandlerAdapter 会检查方法或类上是否存在 @ResponseBody;若存在,则跳过 ModelAndView 的构造,直接通过 MessageConverter 处理返回值。
@Controller 单独使用时,HandlerMethodReturnValueHandler 会走 ViewNameMethodReturnValueHandler 或 RequestResponseBodyMethodProcessor(取决于是否有 @ResponseBody)。返回 String 时默认被视为视图名,随后 ViewResolver 解析为具体 View 对象,执行 render() 输出 HTML。若需返回 JSON,必须在方法或类级别添加 @ResponseBody。
HttpMessageConverter 的默认注册顺序中,MappingJackson2HttpMessageConverter 在类路径存在 Jackson 时自动生效,负责 application/json 的读写。因此 POJO 会被自动序列化为 JSON,而 String 会被 StringHttpMessageConverter 以 text/plain 写入。
【记忆锚点】 “Rest 直接写进身体,Controller 还要找视图。”——Rest 的响应直接写入响应体(Body),Controller 的返回 String 则当作视图名去解析。
【易混对比】
换问法:若题目问“@Controller 如何返回 JSON?”,答案是在方法或类上加 @ResponseBody,或者直接将类注解替换为 @RestController。
| 维度 | @Controller | @RestController |
|---|---|---|
| 注解组成 | 单一注解 | @Controller + @ResponseBody |
| 返回 String | 视为视图名 | 纯文本直接写入响应体 |
| 返回 POJO | 需加 @ResponseBody 才转 JSON | 自动转 JSON |
| 是否经过视图解析器 | 是 | 否 |
| 典型场景 | 传统 MVC 页面渲染 | RESTful API 开发 |
【自测】 某类标注了 @Controller,其中方法返回 "index",需要做什么才能让其返回 JSON 而不是渲染 index.html?
答:在方法或类上添加 @ResponseBody,或者将类上的 @Controller 替换为 @RestController。与第 S10 题连考。
【知识关联】
- 同库关联:与 S10/S11/S12 MVC 题群。
- 实现层:@RestController = @Controller + @ResponseBody。
- 面试追问:① 返回页面用谁?② @ResponseBody 原理?
【拓展延伸】
- 变式问法:同时写两个注解是否冗余。
- 版本差异:WebFlux 有对应注解体系。
- 工程注意点:REST API 统一 @RestController;视图控制器用 @Controller。
S10 · 知识点:请求参数绑定
【题目】前端请求 /user/detail?id=1001,后端方法签名为 public String detail(Long id),应使用哪个注解接收 id?
A. @RequestBody Long id B. @PathVariable Long id C. @RequestParam Long id D. @RequestHeader Long id
答案:C
【考点】@RequestParam、@PathVariable、@RequestBody 的适用场景区分。
【结论】选 C。URL 中 ?id=1001 属于查询参数,应使用 @RequestParam 进行绑定。
【逐项辨析】 A. 错误。@RequestBody 用于读取 HTTP 请求体(通常是 JSON 或 XML),依赖 HttpMessageConverter 反序列化,不处理 URL 查询参数。 B. 错误。@PathVariable 绑定的是 URI 路径中的占位符,例如 /user/detail/{id} 对应 /user/detail/1001;而题干是 ?id=1001,属于查询串而非路径。 C. 正确。/user/detail?id=1001 中的键值对属于查询字符串(query string),Spring MVC 通过 @RequestParam 将其绑定到方法入参。 D. 错误。@RequestHeader 用于获取请求头信息,例如 Content-Type、Authorization 等,与业务参数 id 无关。
【知识点】 Spring MVC 的参数解析由 HandlerMethodArgumentResolver 链负责。其中 RequestParamMethodArgumentResolver 处理 @RequestParam、简单类型(如 Long、String)以及 Map 类型的参数,数据来源为 ServletRequest.getParameter(),即查询参数和表单参数的统一入口。
@RequestParam 支持 required 与 defaultValue 属性:required=false 表示参数可选;defaultValue 可在参数缺失时提供默认值。若方法入参名与请求参数名一致,Spring 在编译时保留参数名(需 -parameters)或通过 Debug 信息即可省略 @RequestParam 注解,但显式标注是编码最佳实践。
@PathVariable 由 PathVariableMethodArgumentResolver 处理,依赖 URI 模板变量,是 RESTful 设计的核心注解。@RequestBody 由 RequestResponseBodyMethodProcessor 处理,读取 InputStream 并通过 MessageConverter 转换,因 InputStream 只能读取一次,所以一个方法通常只能使用一次 @RequestBody。
【记忆锚点】 “Param 是问号,Path 是斜杠,Body 是肚子里面藏。”——Param 对应 ?key=value,Path 对应 /{value},Body 藏在请求体里。
【易混对比】
换问法:若 URL 改为 /user/detail/1001,应使用什么注解接收 1001?答案是 @PathVariable("id")。
| 注解 | 数据来源 | 典型 URL 示例 | 常见请求方式 | 绑定机制 |
|---|---|---|---|---|
| @RequestParam | 查询参数 / 表单字段 | /user?id=1 | GET / POST | ServletRequest.getParameter |
| @PathVariable | URI 路径变量 | /user/1 | GET | URI 模板匹配 |
| @RequestBody | HTTP 请求体 | POST JSON | POST / PUT | HttpMessageConverter |
| @RequestHeader | 请求头 | — | 任意 | HttpServletRequest.getHeader |
【自测】 写出接收 POST 请求 /user/create 且请求体为 {"name":"Tom"} 的后端方法签名。
答:@PostMapping("/create") public User create(@RequestBody User user) { ... }。与第 S11 题连考。
【知识关联】
- 同库关联:与 S11;@RequestParam/@PathVariable/@ModelAttribute。
- 实现层:HandlerMethodArgumentResolver 解析。
- 面试追问:① query 与 path 区别?② 类型转换失败?
【拓展延伸】
- 变式问法:可选参数 defaultValue;日期格式。
- 版本差异:注解稳定。
- 工程注意点:校验入参;避免可变对象绑定脏数据。
S11 · 知识点:@RequestBody 接收 JSON
【题目】前端以 POST 方式提交一段 JSON 请求体,后端使用以下哪个注解将其反序列化为 Java 对象?
A. @RequestParam B. @ModelAttribute C. @RequestBody D. @PathVariable
答案:C
【考点】JSON 请求体的反序列化入口注解。
【结论】选 C。@RequestBody 指示 Spring 使用 HttpMessageConverter(默认 Jackson)将 HTTP 请求体中的 JSON 反序列化为 Java 对象。
【逐项辨析】 A. 错误。@RequestParam 只能绑定查询参数或表单字段,无法解析请求体中的 JSON 结构。 B. 错误。@ModelAttribute 主要用于表单数据绑定到对象,走 WebDataBinder 机制,适用于 application/x-www-form-urlencoded,不处理请求体 JSON。 C. 正确。@RequestBody 通过 RequestResponseBodyMethodProcessor 调用 HttpMessageConverter 读取请求体并完成反序列化,是接收 JSON 的标准方式。 D. 错误。@PathVariable 用于提取 URL 路径变量,与请求体内容完全无关。
【知识点】 Spring MVC 的请求体处理建立在 HttpMessageConverter 体系之上。当请求 Content-Type 为 application/json 时,MappingJackson2HttpMessageConverter(类路径存在 Jackson 时自动注册)负责将输入流反序列化为 Java 对象。反序列化过程依赖目标类的无参构造器或 @JsonCreator 标注的构造器,字段映射通过 getter/setter 或直接字段反射完成。
@RequestBody 只能使用一次的本质原因是 ServletInputStream 的不可重复读性。若尝试在同一方法中使用两个 @RequestBody,第二个参数读取时流已耗尽,将导致空数据或异常。正确做法是将多个字段封装为一个 DTO,只使用一个 @RequestBody。
@ModelAttribute 与 @RequestBody 的核心差异在于:前者走数据绑定(DataBinder),支持类型转换、格式化、校验(Validator),数据来源为请求参数;后者走消息转换(MessageConverter),直接按 MIME 类型反序列化,数据来源为请求体。因此 application/x-www-form-urlencoded 应优先使用 @ModelAttribute 或省略注解让 Spring 自动绑定,而 application/json 必须使用 @RequestBody。
【记忆锚点】 “Body 装肚子,JSON 进肚里;Param 挂嘴上,Path 踩脚下。”——Body 在请求体里,Param 在 URL 后面,Path 在 URL 路径里。
【易混对比】
换问法:若前端提交的是表单(application/x-www-form-urlencoded),应优先使用 @ModelAttribute 还是 @RequestBody?答案是优先使用 @ModelAttribute(或省略注解),因为 @RequestBody 在此场景下无法直接解析为简单 POJO,而 @ModelAttribute 专为表单数据绑定设计。
| 注解 | 数据来源 | 绑定机制 | 适用 Content-Type | 能否用多个 |
|---|---|---|---|---|
| @RequestBody | HTTP 请求体 | HttpMessageConverter | application/json 等 | 否(流只能读一次) |
| @ModelAttribute | 查询参数 + 表单 | WebDataBinder | application/x-www-form-urlencoded | 否(通常一个表单对象) |
| @RequestParam | 单个参数 | 类型转换器 | 任意 | 是(每个参数独立) |
【自测】 若 Controller 方法有两个参数都标注 @RequestBody,会出现什么问题?
答:HTTP 请求体只能读取一次,第二个 @RequestBody 会拿不到数据或抛异常;应将多个字段封装到一个 DTO 中,只使用一个 @RequestBody。与第 S12 题连考。
【知识关联】
- 同库关联:与 S10;HttpMessageConverter(Jackson)。
- 实现层:@RequestBody 读 body 反序列化;需要 Content-Type。
- 面试追问:① 与 @RequestParam 区别?② 解析失败状态码?
【拓展延伸】
- 变式问法:List<DTO>;局部 @Valid。
- 版本差异:Jackson 2/3 与 Boot 版本对应。
- 工程注意点:DTO 与实体分离;大 body 限流;注意日期时区。
S12 · 知识点:全局异常处理
【题目】以下哪个注解组合可以定义一个全局异常处理器,统一处理各 Controller 抛出的业务异常?
A. @Configuration + @Bean B. @Aspect + @Around C. @Controller + @RequestMapping D. @ControllerAdvice + @ExceptionHandler
答案:D
【考点】全局异常处理机制 @ControllerAdvice/@RestControllerAdvice 与 @ExceptionHandler。
【结论】选 D。在类上标注 @ControllerAdvice(或 @RestControllerAdvice),方法上标注 @ExceptionHandler,即可集中捕获并统一处理各 Controller 抛出的异常。
【逐项辨析】 D. 正确。@ControllerAdvice 是全局控制器增强注解,其内部标注 @ExceptionHandler 的方法可拦截所有 Controller 中匹配的异常类型,实现统一异常处理。 B. 错误。@Aspect + @Around 属于 Spring AOP 切面编程,用于横切关注点(如日志记录、性能监控、事务管理),不专门用于异常处理,且无法直接按异常类型分发。 C. 错误。@Controller + @RequestMapping 用于定义请求映射和处理具体业务请求,不具备全局异常捕获能力。 A. 错误。@Configuration + @Bean 用于向 Spring 容器注册 Bean,属于配置层,与请求处理和异常捕获无关。
【知识点】 @ControllerAdvice 自 Spring 3.2 引入,作用于所有标注了 @RequestMapping 的控制器方法。其核心原理是 ExceptionHandlerExceptionResolver,在请求处理链的异常解析阶段,遍历所有 @ControllerAdvice 类中的 @ExceptionHandler 方法,按异常类型的最近继承关系匹配最优处理方法。
@RestControllerAdvice 是 Spring 4.3 提供的组合注解,等同于 @ControllerAdvice + @ResponseBody,适用于 RESTful 场景,确保异常响应直接以 JSON 写入响应体,无需每个方法手动标注 @ResponseBody。
@ExceptionHandler 的匹配遵循“最具体异常优先”原则。例如同时存在 @ExceptionHandler(RuntimeException.class) 和 @ExceptionHandler(NullPointerException.class),当抛出 NullPointerException 时,后者优先执行。支持声明多个异常类,如 @ExceptionHandler({IOException.class, SQLException.class})。
可与 @ResponseStatus 配合设置 HTTP 状态码,或与 ResponseEntity 配合定制响应头、响应体。执行优先级为:Controller 局部 @ExceptionHandler > @ControllerAdvice 全局 > Spring 默认异常解析器(如 DefaultHandlerExceptionResolver)。在实际项目中,全局异常处理通常用于统一封装错误码、错误信息、隐藏堆栈细节,防止前端收到 500 白页或敏感信息泄露。
【记忆锚点】 “Advice 是忠告,全局给忠告;Exception 找上门,Handler 来接待。”——Advice 增强所有 Controller,Handler 专门处理异常。
【易混对比】
换问法:若要求全局异常处理器返回 JSON,应将 @ControllerAdvice 换成什么?答案是 @RestControllerAdvice。
| 维度 | @ControllerAdvice | @RestControllerAdvice |
|---|---|---|
| 注解组成 | 单一注解 | @ControllerAdvice + @ResponseBody |
| 返回视图 | 支持(可返回视图名) | 不支持(直接写响应体) |
| 适用场景 | 传统 MVC 页面应用 | RESTful API 项目 |
| 异常处理注解 | @ExceptionHandler | @ExceptionHandler |
| 状态码控制 | @ResponseStatus / ResponseEntity | @ResponseStatus / ResponseEntity |
【自测】 如何在全局异常处理器中捕获所有 RuntimeException 并返回 HTTP 400 状态码?
答:在 @ControllerAdvice 类中编写方法:@ExceptionHandler(RuntimeException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handle(RuntimeException e) { return new ErrorResponse(e.getMessage()); }。与第 S09 题连考。
【知识关联】
- 同库关联:与 S09–S11、J45–J49(Java 异常)衔接。
- 实现层:@ControllerAdvice + @ExceptionHandler;ResponseStatusException。
- 面试追问:① 全局与局部异常优先级?② 如何统一错误响应体?
【拓展延伸】
- 变式问法:处理 MethodArgumentNotValidException。
- 版本差异:注解稳定。
- 工程注意点:生产勿暴露堆栈;错误码体系化;日志与响应分离。