Spring中@Controller与@RestController核心解析实战指南

 更新时间:2025年11月05日 09:19:17   作者:Kay_Liang  
本文系统解析了Spring MVC中@Controller、@RestController和@RequestMapping的核心功能与最佳实践,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧

在 Spring 生态中,@Controller@RestController 往往被视为“孪生”组件,而 @RequestMapping 及其派生注解则承担了路由枢纽的角色。本文依托 Spring Framework 官方文档与社区实践,系统梳理三者背后的设计动机、运行机理、协作流程与常见误区,并给出可落地的编码建议。全文约 6000 字,力求在学术严谨性与博客可读性之间取得平衡,与各位 Java 学习者一起进阶学习。

1 引言

Spring MVC 自 2.5 引入注解驱动编程模型以来,Java Web 开发逐步摆脱厚重的 XML 配置。@Controller 作为模式层与前端控制器 DispatcherServlet 之间的桥梁,率先被开发者熟知;随后 Spring 4.0 推出 @RestController,以“组合注解”形态将 @ResponseBody 的能力固化到控制器层,顺应了前后端分离与云原生浪潮。与此同时,@RequestMapping 及其细化派生(@GetMapping@PostMapping 等)构成了请求路由的“元语言”,在类与方法两级提供精确映射。理解它们之间的分工与协作,是构建高可维护、高测试覆盖率 Web 应用的必要前提。

2@Controller:表现层模式入口

2.1 语义与定位

@Controller 是 Spring stereotype 注解家族的一员,与 @Service@Repository 并列。该注解仅做“标记”用途,本身不依赖 Servlet API,也不直接处理线程并发逻辑。Spring 容器在刷新阶段通过 ClassPathBeanDefinitionScanner 识别 @Controller 类,将其注册为独立的 BeanDefinition,并设置 singleton 作用域。此后,DispatcherServlet 借助 HandlerMappingHandlerAdapter 完成 URL 到 Bean 的关联。

2.2 返回值类型与视图解析

在纯 MVC 模式下,控制器方法可返回:

  • String——逻辑视图名,由 ViewResolver 解析为具体 JSP、Thymeleaf、FreeMarker 模板;
  • ModelAndView——同时携带模型数据与视图对象;
  • void——直接利用 HttpServletResponse 写流,常用于文件下载;
  • 其他类型——若未标注 @ResponseBody,则会被当成模型属性,默认视图名为“URL 路径简化形式”。

由此可见,@Controller 默认面向“页面渲染”场景,其核心扩展点在于视图抽象层。

2.3 与@Component的层级关系

@Controller 元注解标注了 @Component,因而被 <context:component-scan>@ComponentScan 扫描时同样享受依赖注入、AOP 代理、生命周期回调等能力。区别仅在于语义层面:Spring MVC 会在运行时利用注解元数据区分 Web 层组件,与业务层、持久层隔离,方便做全局异常处理、权限切面等。

3@RestController:面向 REST 的组合式革新

3.1 源码级别的“语法糖”

Spring 4.0 新增的 @RestController 实现极为简洁,其源码仅由两个元注解构成:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController { 
    String value() default "";
}

这意味着被 @RestController 标注的类首先依旧是 @Controller,因而能被 DispatcherServlet 识别;其次,类中所有方法默认携带 @ResponseBody 语义,即返回值直接由 HttpMessageConverter 序列化,不再走视图解析链。

3.2 消息转换器链路

当方法返回非 void 且未显式标注 @ResponseBody 时,Spring MVC 通过 RequestMappingHandlerAdapter 调用 ServletInvocableHandlerMethod,进而激活 HandlerMethodReturnValueHandler 链。@RestController 场景下,首要的处理器为 RequestResponseBodyMethodProcessor,它依据请求头 Accept 与生产端 produces 属性,借助已注册的 HttpMessageConverter(常见如 MappingJackson2HttpMessageConverterJaxb2RootElementHttpMessageConverter)完成 POJO→JSON/XML 的序列化,并写入 ServletOutputStream。整个流程不再依赖 JSP 或模板引擎,因此前后端分离项目通常剔除 spring-boot-starter-thymeleaf 等视图模块,以降低部署包体积。

3.3 与@Controller的性能差异

就单次 HTTP 请求而言,两者在 CPU 指令级几乎等价;差异主要体现在:

  • 视图解析环节被跳过,减少一次 ViewResolver 链遍历;
  • 内存占用方面,免去了 ModelAndView 等中间对象的构造;
  • 线程安全上,二者皆基于无状态 Servlet 模型,无额外锁竞争。

在 QPS 万级以下场景,差距可忽略;高并发压测中,@RestController 的吞吐量略高 2%~4%,可归因于视图解析的省略。

3.4 常见误区

  • 认为 @RestController 必须搭配 @RequestMapping("/api") 使用——其实类级别路径可选,可放在方法级;
  • @RestController 当成 @Service 使用,导致事务代理失效——事务应置于业务层;
  • 误以为 @RestController 只能返回 JSON——通过 produces = "application/xml" 或自定义 HttpMessageConverter 同样支持 XML、MessagePack 等;
  • 把全局异常处理类也标注 @RestController——正确做法是 @ControllerAdvice 配合 @ResponseBody@RestControllerAdvice

4@RequestMapping:路由映射的“元注解”

4.1 属性总览

@RequestMapping 提供六大核心属性:

  • value / path:URI 模板,支持 Ant 风格通配符与 {pathVariable}
  • method:HTTP 方法数组,为空时接受任意方法;
  • params:请求参数断言,如 params = "version=2"
  • headers:请求头断言,如 headers = "Content-Type=application/json"
  • consumes:限定请求的 Content-Type,对应 Content-Type 头;
  • produces:限定响应的 Content-Type,对应 Accept 头。

以上属性可组合成细粒度的匹配条件,Spring 在 RequestMappingHandlerMapping 阶段利用 RequestCondition 体系进行评分排序,得分高者优先。

4.2 类级别与方法级别的协同

@RequestMapping 允许“两段式”映射:

@RestController
@RequestMapping("/shop")
public class OrderController {
    @GetMapping("/order/{id}")            // 实际映射 /shop/order/{id}
    public Order getOrder(@PathVariable Long id){ … }
}

类级别路径作为前缀,方法级别作为后缀,二者拼接后去除重复 /。该策略使得同一业务单元可共享根路径,减少重复编码。需要强调的是,类级别属性(如 produces)会被方法级别同名属性覆盖,而非追加。

4.3 RESTful 风格设计要点

  • 资源名词复数化:/users/orders
  • 利用路径变量表达层级:/users/{userId}/orders/{orderId}
  • 用 HTTP 方法表达动作:GET 查询、POST 创建、PUT 全量更新、PATCH 部分更新、DELETE 删除;
  • 版本化:推荐将版本置于 URL 前缀 /v1/users,或利用 Accept: application/vnd.company.api.v2+json 协商;
  • 无状态:拒绝将 session 作为业务依据,由 JWTOAuth2 令牌承载身份。

5 派生注解:语义化与最佳实践

Spring 4.3 起引入五个方法级派生注解,分别对应标准 HTTP 方法:

注解等效写法常见场景
@GetMapping@RequestMapping(method=GET)查询、分页、导出
@PostMapping@RequestMapping(method=POST)新增、复杂查询
@PutMapping@RequestMapping(method=PUT)全量更新
@PatchMapping@RequestMapping(method=PATCH)部分字段更新
@DeleteMapping@RequestMapping(method=DELETE)删除

使用派生注解可显著降低 method 硬编码出错率,并提升代码自描述能力。需要注意的是,派生注解不支持 consumes/params 等属性,但可通过组合 @RequestMapping 达到同样效果。

6 参数绑定与注解协作

6.1 路径变量@PathVariable

URI 模板变量需与方法形参一一对应,支持基本类型及自定义类型转换。若名称为驼峰,可在 {} 中保留短横线 /users/{user-id},再通过 @PathVariable("user-id") 显式绑定。

6.2 查询参数@RequestParam

默认必填,可通过 required=falseOptional<T> 接收空值;多值场景用 List<T> 接收。对于复杂对象,Spring 会调用 WebDataBinder 进行级联绑定,但建议保持扁平,避免多层嵌套。

6.3 请求体@RequestBody

仅支持 POST/PUT/PATCH,Content-Type 需为 application/json 等可序列化格式。默认由 Jackson 反序列化,可通过 @Valid 触发 Bean Validation,校验失败将抛出 MethodArgumentNotValidException,由 @ExceptionHandler 统一处理。

6.4 请求头@RequestHeader

可用于读取 AuthorizationX-Request-ID 等头信息,支持 Map<String,String> 批量接收。需要注意大小写不敏感,因 HTTP 头本身不区分大小写。

6.5 矩阵变量@MatrixVariable

URI 路径中的键值对,如 /cars;color=red;year=2020,需开启 <mvc:annotation-driven enable-matrix-variables="true"/>,并配合 UrlPathHelper 移除 ; 后的分号截断。

7 异常处理与统一响应

7.1@ControllerAdvice全局拦截

通过 @ControllerAdvice(basePackages="com.example.web") 限定扫描范围,配合 @ExceptionHandler(MethodArgumentNotValidException.class) 返回统一 JSON 响应体。该机制对 @RestController@Controller 同时生效,但后者若返回视图,则需额外配置 ModelAndView resolver。

7.2 响应封装建议

不推荐直接返回实体对象,应定义 CommonResponse<T> 统一包装:

public class CommonResponse<T> {
    private int code;
    private String message;
    private T data;
    private long timestamp;
}

全局封装可通过 ResponseBodyAdvice<T> 实现,在 beforeBodyWrite 处拦截,避免每个接口手动包装。

8 测试与可维护性

8.1 单元测试

利用 MockMvc 可快速验证路由、参数、响应体,无需启动 Servlet 容器:

@Autowired
private MockMvc mvc;
@Test
public void shouldReturnUser() throws Exception {
    mvc.perform(get("/v1/users/1")
           .accept(MediaType.APPLICATION_JSON))
       .andExpect(status().isOk())
       .andExpect(jsonPath("$.id").value(1));
}

8.2 层间隔离

控制器仅负责协议转换与参数校验,业务逻辑下沉至 Service Facade,可避免出现“贫血控制器”与“事务脚本”陷阱。通过 @WebMvcTest 可只加载 MVC 组件,加速 CI 流水线。

9 版本演进与未来趋势

Spring Framework 6 已全面兼容 Jakarta EE 9,包名由 javax.servlet 迁移至 jakarta.servlet,但注解层保持零改动,因此 @Controller@RestController@RequestMapping 依旧稳定。随着 Spring Boot 3 原生镜像(GraalVM Native Image)的成熟,注解解析阶段可在编译期完成,进一步缩短冷启动时间。另一方面,Spring WebFlux 的 @RestController 在语义层面与 Servlet 栈保持一致,仅底层实现由阻塞式改为事件循环,因此本文结论同样适用于响应式编程模型。

10 结论

@Controller@RestController 并非简单的“新旧替换”,而是面向不同交互模式的互补组件:前者聚焦页面渲染,后者专注数据服务;@RequestMapping 及其派生注解则提供灵活而强大的路由能力。开发者应依据业务场景、团队技能与历史资产,合理选用注解组合,并辅以统一的异常、响应、日志规范,才能在快速迭代与长期维护之间取得平衡。

到此这篇关于Spring中@Controller与@RestController核心解析实战指南的文章就介绍到这了,更多相关Spring @Controller与@RestController内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot中配置Redis连接池的完整指南

    SpringBoot中配置Redis连接池的完整指南

    这篇文章主要为大家详细介绍了SpringBoot中配置Redis连接池的完整指南,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下
    2025-04-04
  • jdk中keytool的使用以及如何提取jks文件中的公钥和私钥

    jdk中keytool的使用以及如何提取jks文件中的公钥和私钥

    JKS文件由公钥和密钥构成利用Java Keytool工具生成的文件,它是由公钥和密钥构成的,下面这篇文章主要给大家介绍了关于jdk中keytool的使用以及如何提取jks文件中公钥和私钥的相关资料,需要的朋友可以参考下
    2024-03-03
  • Java中如何调用cmd压缩文件

    Java中如何调用cmd压缩文件

    以下是对Java调用cmd压缩文件的实现方法进行了分析介绍,需要的朋友可以参考下
    2013-07-07
  • Java打包Jar包后使用脚本执行

    Java打包Jar包后使用脚本执行

    本文详细讲解了Java打包Jar包后使用脚本执行的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-12-12
  • springboot3.x版本集成log4j冲突以及解决log4j冲突不生效问题

    springboot3.x版本集成log4j冲突以及解决log4j冲突不生效问题

    由于Spring Boot自带的Logback与Log4j冲突,去除了Logback的jar包后仍存在,原因是其他包也引入了Logback,解决方法是找到并去除引入Logback的其他包,如actuator包,并更新Maven
    2024-11-11
  • Spring Boot利用@Async如何实现异步调用:自定义线程池

    Spring Boot利用@Async如何实现异步调用:自定义线程池

    这篇文章主要给大家介绍了关于Spring Boot利用@Async如何实现异步调用:自定义线程池的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2018-05-05
  • Java实现Token工具类进行登录和拦截

    Java实现Token工具类进行登录和拦截

    在应用的登录时需要生成token进行验证,并放入信息,之后的话可以直接使用浏览器的session进行登录,本文就来利用java编写一个token工具类,可以很方便的生成和解析token,感兴趣的可以了解下
    2023-12-12
  • 如何使用Spring AOP预处理Controller的参数

    如何使用Spring AOP预处理Controller的参数

    这篇文章主要介绍了如何使用Spring AOP预处理Controller的参数操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • java微信公众号开发案例

    java微信公众号开发案例

    这篇文章主要为大家详细介绍了java微信公众号开发案例,如何接入公众号,订阅号怎么样接收消息,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-11-11
  • 深入了解Spring的Bean生命周期

    深入了解Spring的Bean生命周期

    这篇文章主要为大家介绍了Spring的Bean生命周期,文中的示例代码讲解详细,对我们学习或工作有一定的参考价值,感兴趣的可以学习一下
    2022-01-01

最新评论