Spring Boot与Spring MVC核心注解
- 发布时间:2026-04-18 06:01:50
- 本文热度:浏览 6 赞 0 评论 0
- 文章标签: Spring Boot Spring MVC Java
- 全文共1字,阅读约需1分钟
一、先把注解体系分清楚
Spring Boot 和 Spring MVC 的注解经常被放在一起讲,但它们解决的问题并不一样:
- Spring Boot 注解:负责应用启动、自动配置、组件装配。
- Spring MVC 注解:负责请求映射、参数绑定、响应返回、异常处理。
很多项目里看起来只是“几个注解组合起来能跑”,但真正影响代码可维护性的,不是会不会写注解,而是是否知道:
- 这个注解由谁解析;
- 解析发生在启动期还是请求期;
- 它和哪些默认机制绑定;
- 出问题时应该怀疑哪一层。
可以先建立一个最小认知模型:
@SpringBootApplication决定应用怎么启动。@Controller/@RestController决定类是不是 Web 处理器。@RequestMapping体系决定请求能不能进来。@RequestParam、@PathVariable、@RequestBody决定参数怎么绑定。@ResponseBody决定返回值是视图还是 HTTP 响应体。@ControllerAdvice、@ExceptionHandler决定异常怎么统一收口。
把这条链路吃透,Spring MVC 的主体就掌握得差不多了。
二、Spring Boot 启动入口的核心注解
1. @SpringBootApplication
这是 Spring Boot 最常见、也最容易被“用会了但没理解”的注解。
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
它本质上是一个组合注解,等价于:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
作用拆解
@SpringBootConfiguration
它本质上又是 @Configuration,表示这是一个配置类,会向 Spring 容器注册 Bean。
@EnableAutoConfiguration
这是 Spring Boot 的关键能力,表示启用自动配置。 Spring Boot 会根据:
- 当前 classpath 中是否存在某个类;
- 容器中是否已经存在某个 Bean;
- 配置文件里是否开启或关闭某个功能;
来决定是否自动装配对应组件。
例如引入 spring-boot-starter-web 后,Spring Boot 会根据条件自动配置:
- DispatcherServlet
- Jackson JSON 转换器
- 内嵌 Tomcat
- Spring MVC 基础设施
@ComponentScan
默认扫描启动类所在包及其子包,把带有组件语义的类注册到容器中,例如:
@Component@Service@Repository@Controller@RestController@Configuration
常见问题
启动类放错包
如果启动类放在过深的子包中,可能导致扫描范围不完整,出现:
- Controller 扫描不到
- Service 注入失败
- Configuration 不生效
最佳实践是把启动类放在项目根包下。
误以为自动配置是“无条件生效”
自动配置从来不是“看见依赖就一定注入”,而是条件装配。 很多问题的根源都在于条件不满足,比如:
- 缺少类依赖;
- 自己手动定义了同类型 Bean,覆盖了默认配置;
- 属性开关关闭了自动配置。
三、Bean 注册与装配层的核心注解
虽然主题重点是 Spring MVC,但不理解容器装配层,很多 MVC 问题也定位不了。
1. @Configuration
表示当前类是配置类,通常用于显式声明 Bean。
@Configuration
public class WebConfig {
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
核心作用
- 替代早期 XML 配置;
- 让一个类承担“Bean 定义入口”的角色;
- 通常配合
@Bean使用。
2. @Bean
用于把方法返回值注册到 Spring 容器。
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
使用场景
当第三方类你无法修改源码,不能直接加 @Component 时,用 @Bean 最合适。
与 @Component 的区别
@Component:作用在类上,偏向“让 Spring 去扫描发现”。@Bean:作用在方法上,偏向“我显式告诉 Spring 注册这个对象”。
3. @Component、@Service、@Repository
这几个注解本质上都属于组件标记注解,都会被组件扫描识别为 Bean。
@Service
public class UserService {
}
区别不在“能不能注入”,而在语义
@Component:通用组件;@Service:业务层;@Repository:持久层;@Controller:表现层控制器。
@Repository 还有一个额外价值:它在某些场景下会参与数据访问异常转换,把底层异常转换为 Spring 统一的数据访问异常体系。
4. @Autowired
用于自动注入 Bean。
@Service
public class OrderService {
private final UserService userService;
public OrderService(UserService userService) {
this.userService = userService;
}
}
当前项目更推荐使用构造器注入,而不是字段注入。
为什么更推荐构造器注入
- 依赖关系更明确;
- 便于单元测试;
- 可以保证对象创建后依赖完整;
- 更适合
final字段,提升不可变性。
常见问题
当出现注入失败时,优先检查:
- 这个类有没有被 Spring 管理;
- 扫描路径是否覆盖;
- 是否存在多个同类型 Bean;
- 是否存在循环依赖。
四、Spring MVC 入口:控制器相关注解
1. @Controller
表示当前类是一个 MVC 控制器。
@Controller
@RequestMapping("/page")
public class PageController {
@GetMapping("/home")
public String home() {
return "home";
}
}
它的默认语义
如果方法返回 String,默认会被当作视图名处理,而不是响应体。
这意味着:
- 返回
"home",框架会尝试找名为home的视图; - 不会直接把
"home"输出到 HTTP 响应中。
2. @RestController
这是 @Controller + @ResponseBody 的组合注解。
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public UserVO getById(@PathVariable Long id) {
return new UserVO(id, "Tom");
}
}
它的核心语义
类中所有方法的返回值,默认都写入 HTTP 响应体,通常序列化为 JSON。
常见误区
@RestController 不是“专门给前后端分离用的”,而是明确告诉 Spring:
这个控制器返回的不是视图,而是响应数据。
五、请求映射:决定请求能否进入方法
1. @RequestMapping
这是最基础的请求映射注解,可标在类上和方法上。
@RestController
@RequestMapping("/orders")
public class OrderController {
@RequestMapping(value = "/detail", method = RequestMethod.GET)
public String detail() {
return "ok";
}
}
类级别与方法级别组合规则
- 类上定义公共路径前缀;
- 方法上定义具体子路径;
- 最终路径为两者拼接。
例如:
- 类上:
/orders - 方法上:
/detail - 最终:
/orders/detail
2. 派生注解:@GetMapping、@PostMapping、@PutMapping、@DeleteMapping、@PatchMapping
这些注解本质上是 @RequestMapping 的语义化快捷写法。
@GetMapping("/{id}")
public OrderVO get(@PathVariable Long id) {
return orderService.get(id);
}
为什么更推荐用派生注解
- 可读性更强;
- HTTP 方法一眼可见;
- 不容易把 method 写错。
3. @PathVariable
用于绑定 URL 路径中的模板变量。
@GetMapping("/users/{id}")
public UserVO getUser(@PathVariable Long id) {
return userService.getById(id);
}
如果参数名和路径变量名不一致,要显式指定:
@GetMapping("/users/{userId}")
public UserVO getUser(@PathVariable("userId") Long id) {
return userService.getById(id);
}
适用场景
适合表示资源定位信息,例如:
/users/1/orders/1001/items/3
常见问题
路径参数是 URL 结构的一部分,不适合承载复杂筛选条件。 筛选条件更适合放在查询参数里。
4. @RequestParam
用于绑定查询参数或表单参数。
@GetMapping("/search")
public List<UserVO> search(@RequestParam String keyword,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
return userService.search(keyword, page, size);
}
请求示例:
GET /search?keyword=spring&page=1&size=10
常用属性
required:是否必须;defaultValue:默认值。
使用建议
- 分页参数、排序参数、筛选参数,优先用
@RequestParam; - 简单单值参数适合直接写;
- 参数一多,建议封装成对象,而不是在方法参数里堆十几个字段。
5. @RequestBody
用于把请求体中的 JSON/XML 等内容反序列化为 Java 对象。
@PostMapping("/users")
public Long create(@RequestBody UserCreateRequest request) {
return userService.create(request);
}
public class UserCreateRequest {
private String username;
private Integer age;
// getter/setter
}
底层机制
Spring MVC 会通过 HttpMessageConverter 读取请求体,再结合 Jackson 等转换器把 JSON 转成 Java 对象。
关键特点
- 一个请求通常只能有一个
@RequestBody; - 它读取的是 HTTP Body,不是 URL 参数;
- 常用于
POST、PUT、PATCH请求。
常见问题
问题一:GET 请求配 @RequestBody
技术上不是绝对禁止,但绝大多数场景都不推荐,而且很多客户端、代理、中间件也不按这个模式工作。
问题二:参数绑定失败
常见原因包括:
- 请求头
Content-Type不正确; - 字段名对不上;
- 没有默认构造器或 setter;
- JSON 格式错误;
- 没有对应消息转换器。
6. @ResponseBody
用于把返回值直接写入响应体。
@Controller
@RequestMapping("/api")
public class ApiController {
@GetMapping("/ping")
@ResponseBody
public String ping() {
return "pong";
}
}
如果类上已经使用 @RestController,通常不需要再写 @ResponseBody。
六、参数绑定进阶:Spring MVC 真正高频的注解
1. @ModelAttribute
@ModelAttribute 容易被忽略,但它在 Spring MVC 里非常重要。
用法一:绑定请求参数到对象
@GetMapping("/users")
public List<UserVO> list(@ModelAttribute UserQuery query) {
return userService.list(query);
}
public class UserQuery {
private String username;
private Integer page = 1;
private Integer size = 10;
// getter/setter
}
请求示例:
GET /users?username=tom&page=1&size=10
Spring 会把查询参数按字段名绑定到 UserQuery 对象。
用法二:在请求处理前向模型中放数据
@ModelAttribute("module")
public String module() {
return "user";
}
这在传统服务端页面渲染里更常见。
什么时候优先用它
当查询参数较多,且这些参数天然组成一个“查询对象”时,优先用 @ModelAttribute,而不是堆多个 @RequestParam。
2. @RequestHeader
用于读取请求头。
@GetMapping("/headers")
public String header(@RequestHeader("User-Agent") String userAgent) {
return userAgent;
}
适用场景
- 读取 token 之外的自定义头;
- 读取版本号、渠道号、追踪 ID;
- 调试网关转发字段。
3. @CookieValue
用于读取 Cookie。
@GetMapping("/cookie")
public String cookie(@CookieValue("SESSION") String sessionId) {
return sessionId;
}
在现代前后端分离项目中,它不如 Header 常见,但在会话系统、老项目或某些网关场景下仍然会用到。
4. @RequestPart
用于处理 multipart/form-data 请求中的某个部分,常见于文件上传加结构化参数混合提交。
@PostMapping("/upload")
public String upload(@RequestPart("file") MultipartFile file,
@RequestPart("meta") FileMeta meta) {
return "success";
}
与 @RequestParam 的区别
@RequestParam更偏简单字段;@RequestPart更明确面向 multipart 中的独立 part,并可触发消息转换。
七、校验注解:让参数错误在入口处拦住
1. @Valid 与 @Validated
这两个注解经常一起出现,但不完全相同。
@PostMapping("/users")
public Long create(@Validated @RequestBody UserCreateRequest request) {
return userService.create(request);
}
public class UserCreateRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@Min(value = 1, message = "年龄必须大于0")
private Integer age;
// getter/setter
}
@Valid
来自 Jakarta Validation 标准,主要用于触发基础校验。
@Validated
来自 Spring,功能更强,支持分组校验。
常见结论
- 不需要分组时,
@Valid和@Validated都能完成大多数工作; - 需要分组校验时,用
@Validated。
容易踩坑的点
校验注解写了,不代表一定生效。还要满足:
- 控制器参数上加了
@Valid或@Validated; - 项目引入了校验相关依赖;
- 全局异常处理能正确接住校验异常。
八、响应状态与异常处理注解
1. @ResponseStatus
可以直接指定响应状态码。
@ResponseStatus(HttpStatus.CREATED)
@PostMapping("/users")
public Long create(@RequestBody UserCreateRequest request) {
return userService.create(request);
}
也可以用于自定义异常类:
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
适用场景
适合语义非常固定的状态输出,比如资源不存在、创建成功。
2. @ExceptionHandler
用于在控制器内部或全局处理特定异常。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) {
return ResponseEntity.status(404).body(ex.getMessage());
}
}
它解决的核心问题
把异常处理逻辑从业务代码中剥离出来,避免每个方法都写重复的 try-catch。
3. @ControllerAdvice 与 @RestControllerAdvice
这是全局增强注解。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleValidException(MethodArgumentNotValidException ex) {
Map<String, Object> body = new HashMap<>();
body.put("code", 400);
body.put("message", ex.getBindingResult().getFieldError().getDefaultMessage());
return ResponseEntity.badRequest().body(body);
}
}
区别
@ControllerAdvice:返回值默认仍按 MVC 规则处理,可能走视图解析;@RestControllerAdvice:等价于@ControllerAdvice + @ResponseBody,更适合 REST API。
常见职责
- 统一异常响应格式;
- 统一处理参数校验异常;
- 统一处理业务异常;
- 统一处理系统异常。
九、数据绑定细节:为什么“参数明明传了却拿不到”
Spring MVC 参数绑定不是“看起来字段名一样就一定能成功”,它背后有一套清晰规则。
1. 常见绑定来源
Spring MVC 会从不同位置取值:
- URL 路径:
@PathVariable - 查询字符串:
@RequestParam - 请求头:
@RequestHeader - Cookie:
@CookieValue - 请求体:
@RequestBody - 表单/查询参数聚合对象:
@ModelAttribute
2. 常见失败原因
参数名不匹配
例如前端传的是 user_name,后端对象字段是 username,没有额外映射规则时就绑定不上。
Content-Type 不匹配
JSON 请求必须保证请求头正确,例如:
Content-Type: application/json
否则 @RequestBody 可能无法解析。
时间类型格式不兼容
例如前端传 2026-04-14 10:20:30,后端是 LocalDateTime,没有统一格式转换时可能失败。
复杂对象混用错误
例如把查询参数场景写成 @RequestBody,或者把 JSON 体场景写成多个 @RequestParam,都会增加绑定失败概率。
十、最容易混淆的几组注解
1. @Controller 和 @RestController
@Controller
- 偏传统 MVC;
- 返回视图名;
- 适合页面渲染。
@RestController
- 偏 REST API;
- 返回响应体;
- 通常用于 JSON 接口。
2. @RequestParam 和 @RequestBody
@RequestParam
- 取 URL 参数或表单参数;
- 适合简单字段。
@RequestBody
- 取请求体;
- 适合 JSON 对象。
一个典型判断标准是: 参数在 URL 上,就优先想 @RequestParam;参数在 Body 里,就优先想 @RequestBody。
3. @PathVariable 和 @RequestParam
@PathVariable
表达“这是路径的一部分,是资源标识”。
@RequestParam
表达“这是筛选条件、辅助参数、控制参数”。
示例:
GET /users/100
GET /users?name=tom&page=1
前者适合 @PathVariable,后者适合 @RequestParam。
4. @Component 和 @Bean
@Component
类本身可控,交给 Spring 自动扫描。
@Bean
类本身不可控,或者需要精细化实例化逻辑。
十一、版本差异必须明确的地方
以下内容以 Spring Boot 3.x / Spring Framework 6.x 为主。
1. javax.* 迁移为 jakarta.*
这是 Boot 3.x 非常关键的变化。
例如校验相关注解、Servlet API、JPA API 等,包名大量从:
javax.validation.Valid
迁移为:
jakarta.validation.Valid
如果项目从 Boot 2.x 升级到 Boot 3.x,这类导包问题是最常见的编译错误来源之一。
2. 路径匹配与底层兼容细节
Spring Boot 2.x 与 3.x 在部分默认配置、底层依赖、路径匹配相关行为上存在差异。 升级时不要只看代码能不能编译,更要关注:
- 路径匹配是否与旧规则一致;
- 第三方 starter 是否兼容 Spring Framework 6;
- Servlet 容器版本是否兼容;
- 校验、序列化、拦截器等扩展点是否受影响。
3. Web 技术栈要区分 MVC 和 WebFlux
Spring MVC 注解体系主要对应 Servlet 模型。 虽然有些注解在 WebFlux 中也出现,但底层执行模型完全不同:
- Spring MVC:同步阻塞模型为主;
- WebFlux:响应式非阻塞模型。
讨论控制器注解时,不能把两套运行机制混为一谈。
十二、一个更接近实际项目的控制器示例
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {
@GetMapping("/{id}")
public UserVO getById(@PathVariable Long id) {
return new UserVO(id, "Tom", 20);
}
@GetMapping
public List<UserVO> list(@ModelAttribute UserQuery query) {
return List.of(new UserVO(1L, "Tom", 20));
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Long create(@Validated @RequestBody UserCreateRequest request) {
return 1001L;
}
@PutMapping("/{id}")
public Boolean update(@PathVariable Long id,
@Validated @RequestBody UserUpdateRequest request) {
return true;
}
@DeleteMapping("/{id}")
public Boolean delete(@PathVariable Long id) {
return true;
}
@GetMapping("/header")
public String header(@RequestHeader("X-Trace-Id") String traceId) {
return traceId;
}
}
public class UserQuery {
private String username;
private Integer page = 1;
private Integer size = 10;
// getter/setter
}
public class UserCreateRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@Min(value = 1, message = "年龄必须大于0")
private Integer age;
// getter/setter
}
public class UserUpdateRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@Min(value = 1, message = "年龄必须大于0")
private Integer age;
// getter/setter
}
这个例子基本覆盖了 REST 接口里最常见的注解组合:
@RestController@RequestMapping@GetMapping/@PostMapping/@PutMapping/@DeleteMapping@PathVariable@ModelAttribute@RequestBody@RequestHeader@Validated@ResponseStatus
十三、统一异常处理示例
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex) {
String message = ex.getBindingResult()
.getFieldErrors()
.stream()
.findFirst()
.map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage())
.orElse("参数校验失败");
Map<String, Object> result = new HashMap<>();
result.put("code", 400);
result.put("message", message);
return ResponseEntity.badRequest().body(result);
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<Map<String, Object>> handleUserNotFound(UserNotFoundException ex) {
Map<String, Object> result = new HashMap<>();
result.put("code", 404);
result.put("message", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(result);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handleException(Exception ex) {
Map<String, Object> result = new HashMap<>();
result.put("code", 500);
result.put("message", "系统异常");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
}
这类统一异常处理的价值不在于“写法统一”,而在于它能保证:
- 错误码统一;
- 响应结构统一;
- 前端处理逻辑统一;
- 日志追踪更容易收口。
十四、实际开发中的注解使用建议
1. 不要把一个控制器写成“参数绑定实验场”
控制器方法参数越多,越难维护。 简单参数用 @RequestParam,复杂查询条件封装成 @ModelAttribute 对象,请求体对象用 @RequestBody,保持边界清晰。
2. REST 接口优先用 @RestController
除非你明确在做服务端页面渲染,否则绝大多数接口项目直接使用 @RestController 更自然。
3. 不要滥用 @RequestMapping
能用 @GetMapping、@PostMapping 等派生注解时,优先使用派生注解。
4. 参数校验一定要前置
不要等到 Service 层再发现字段为空、数值越界。 入口层就应该用 @Validated + 校验注解拦住非法请求。
5. 异常处理必须全局统一
零散的 try-catch 会让接口风格混乱,也会让错误码体系失控。 业务异常、参数异常、系统异常应该进入统一处理机制。
6. 搞清楚“视图返回”和“响应体返回”是两条路径
很多初学者的问题本质上不是注解不会写,而是没有分清:
- 返回页面;
- 返回 JSON;
这会直接影响你该选 @Controller 还是 @RestController,以及方法返回值应该如何设计。
十五、从执行链路理解这些注解,才算真正掌握
一次典型请求进入 Spring MVC,大致会经历这条链路:
-
请求进入 Servlet 容器;
-
DispatcherServlet 统一接收请求;
-
根据
@RequestMapping等映射规则找到处理方法; -
根据参数注解完成绑定:
@PathVariable@RequestParam@RequestBody@RequestHeader
-
执行参数校验;
-
调用控制器方法;
-
处理返回值:
- 视图解析
- JSON 序列化
-
如果中途抛异常,由
@ExceptionHandler/@ControllerAdvice接管。
真正有经验的开发者看注解,不会只看“表面用途”,而是会直接联想到它在这条链路里的位置。 因为只有理解它在请求生命周期中的职责,才能在出现 404、400、415、500、参数绑定失败、JSON 转换异常、校验异常时快速定位问题。
十六、最后收束
Spring Boot 负责把应用启动起来,把该有的基础设施自动装配好;Spring MVC 负责把一次 HTTP 请求稳定、清晰地落到具体方法上。
核心注解真正重要的不是数量,而是分层:
- 启动装配层:
@SpringBootApplication、@Configuration、@Bean - 组件注册层:
@Component、@Service、@Repository - 控制器层:
@Controller、@RestController - 请求映射层:
@RequestMapping、@GetMapping、@PostMapping - 参数绑定层:
@PathVariable、@RequestParam、@RequestBody、@ModelAttribute - 响应处理层:
@ResponseBody、@ResponseStatus - 异常治理层:
@ExceptionHandler、@ControllerAdvice、@RestControllerAdvice - 校验层:
@Valid、@Validated
把这些注解按职责拆开,再放回一次完整请求链路里理解,Spring Boot / Spring MVC 的核心注解体系就不再是“靠记忆拼出来的写法”,而是一个可解释、可定位、可扩展的 Web 开发模型。