java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring @Controller与@RestController

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

作者: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 模式下,控制器方法可返回:

由此可见,@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 指令级几乎等价;差异主要体现在:

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

3.4 常见误区

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

4.1 属性总览

@RequestMapping 提供六大核心属性:

以上属性可组合成细粒度的匹配条件,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 风格设计要点

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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文