Skip to content

二、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=1GET / POSTServletRequest.getParameter
@PathVariableURI 路径变量/user/1GETURI 模板匹配
@RequestBodyHTTP 请求体POST JSONPOST / PUTHttpMessageConverter
@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能否用多个
@RequestBodyHTTP 请求体HttpMessageConverterapplication/json 等否(流只能读一次)
@ModelAttribute查询参数 + 表单WebDataBinderapplication/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。
  • 版本差异:注解稳定。
  • 工程注意点:生产勿暴露堆栈;错误码体系化;日志与响应分离。

持续学习,持续积累。