一、核心概念
Spring Boot 控制器是处理 HTTP 请求的核心组件,负责接收客户端请求、处理业务逻辑(通常委托给 Service 层)、并返回响应。它是 Model-View-Controller (MVC) 模式中的 C (Controller)。
核心注解
@Controller:- 标记一个类为 Spring MVC 的控制器。
- 通常与
@RequestMapping或其衍生注解(如@GetMapping,@PostMapping)结合使用,将 HTTP 请求映射到具体的处理方法。 - 返回值通常用于指定视图名称(如 JSP、Thymeleaf 模板),由视图解析器渲染后返回给客户端(主要用于服务端渲染)。
@RestController:- 是
@Controller和@ResponseBody的组合注解。 @ResponseBody表示该控制器的方法返回值将直接作为 HTTP 响应体(Response Body)返回,而不是作为视图名称。- 这是构建 RESTful Web 服务的首选注解。返回值(如 POJO、
List、Map等)会被 Spring 的HttpMessageConverter(如 Jackson 的MappingJackson2HttpMessageConverter)自动序列化为 JSON 或 XML 等格式。
- 是
关键角色
- 请求映射 (Request Mapping): 使用
@RequestMapping及其衍生注解 (@GetMapping,@PostMapping,@PutMapping,@DeleteMapping,@PatchMapping) 将 HTTP 方法和 URL 路径映射到控制器方法。 - 参数绑定 (Parameter Binding): 将 HTTP 请求中的数据(路径变量、请求参数、请求体、请求头、Cookie 等)自动绑定到控制器方法的参数上。
- 响应处理 (Response Handling): 处理方法的返回值被转换为 HTTP 响应体(对于
@RestController)或视图名称(对于@Controller)。 - 异常处理 (Exception Handling): 使用
@ExceptionHandler处理控制器内部或全局的异常。
二、操作步骤(非常详细)
步骤 1:创建基础控制器类
- 创建 Java 类: 在
src/main/java目录下的合适包(如com.example.demo.controller)中创建一个 Java 类。 - 添加
@RestController注解:import org.springframework.web.bind.annotation.RestController; @RestController // 标记此类为 REST 控制器 public class UserController { // 将在此处添加处理请求的方法 }- 使用
@RestController表示这是一个返回数据(如 JSON)的 REST API 控制器。 - 替代方案: 如果需要返回视图(如 HTML 页面),使用
@Controller并在方法上添加@ResponseBody(不推荐用于 REST API)。
- 使用
步骤 2:定义请求映射(Mapping)
在类上使用
@RequestMapping(可选,但推荐用于基路径):import org.springframework.web.bind.annotation.RequestMapping; @RestController @RequestMapping("/api/users") // 所有该类中的方法的 URL 前缀 public class UserController { // ... }- 这样,该控制器下的所有端点都将以
/api/users开头。
- 这样,该控制器下的所有端点都将以
在方法上使用 HTTP 特定的映射注解:
@GetMapping: 处理 HTTP GET 请求。@PostMapping: 处理 HTTP POST 请求。@PutMapping: 处理 HTTP PUT 请求。@DeleteMapping: 处理 HTTP DELETE 请求。@PatchMapping: 处理 HTTP PATCH 请求。@RequestMapping: 通用映射,可通过method属性指定方法。
示例:
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/users") public class UserController { // GET /api/users - 获取所有用户列表 @GetMapping public List<User> getAllUsers() { // 实现获取所有用户的逻辑 return userService.findAll(); } // GET /api/users/123 - 获取 ID 为 123 的用户 @GetMapping("/{id}") public User getUserById(@PathVariable Long id) { // 实现根据 ID 获取用户的逻辑 return userService.findById(id); } // POST /api/users - 创建一个新用户 @PostMapping public User createUser(@RequestBody User user) { // 实现创建用户的逻辑 return userService.save(user); } // PUT /api/users/123 - 更新 ID 为 123 的用户 @PutMapping("/{id}") public User updateUser(@PathVariable Long id, @RequestBody User user) { // 实现更新用户的逻辑 user.setId(id); // 确保 ID 正确 return userService.save(user); } // DELETE /api/users/123 - 删除 ID 为 123 的用户 @DeleteMapping("/{id}") public ResponseEntity<Void> deleteUser(@PathVariable Long id) { userService.deleteById(id); // 返回 204 No Content return ResponseEntity.noContent().build(); } }@GetMapping("/{id}"):{id}是路径变量 (Path Variable),其值将通过@PathVariable注解绑定到方法参数。@PostMapping: 通常用于创建资源,请求体(Request Body)包含要创建的资源数据。@PutMapping("/{id}"): 用于更新资源,通常需要客户端提供完整的资源表示。@DeleteMapping("/{id}"): 用于删除资源。ResponseEntity<Void>: 用于返回特定的 HTTP 状态码(如 204 No Content)而不返回响应体。
步骤 3:处理请求参数
路径变量 (
@PathVariable):@GetMapping("/{id}/orders/{orderId}") public Order getOrder(@PathVariable Long id, @PathVariable("orderId") Long orderNumber) { // id 对应 {id}, orderNumber 对应 {orderId} return orderService.findByUserIdAndOrderId(id, orderNumber); }- 参数名与路径变量名相同时,
@PathVariable的value属性可省略。 - 名称不同时,需用
@PathVariable("实际路径变量名")显式指定。
- 参数名与路径变量名相同时,
请求参数 (
@RequestParam):@GetMapping public List<User> getUsers( @RequestParam(defaultValue = "0") int page, // /api/users?page=1 @RequestParam(defaultValue = "10") int size, @RequestParam(required = false) String name, // /api/users?name=John (可选) @RequestParam List<String> roles // /api/users?roles=ADMIN&roles=USER (多值) ) { // 实现分页、过滤查询逻辑 return userService.findByPageAndFilters(page, size, name, roles); }defaultValue: 指定默认值,当请求中无此参数时使用。required: 指定参数是否必须。false表示可选。- 可绑定到
List,Set等集合类型处理多值参数。
请求体 (
@RequestBody):@PostMapping public User createUser(@RequestBody @Valid User user, BindingResult result) { // User 对象的属性将从 JSON/XML 请求体自动反序列化 if (result.hasErrors()) { // 处理验证错误 throw new IllegalArgumentException("Invalid user data"); } return userService.save(user); }@RequestBody: 告诉 Spring 将 HTTP 请求体反序列化为指定的对象(User)。- 通常与
@Valid结合进行数据验证(需要javax.validation依赖,如 Hibernate Validator)。 BindingResult参数用于捕获验证错误,必须紧跟在@Valid参数之后。
请求头 (
@RequestHeader):@GetMapping public String getInfo(@RequestHeader("User-Agent") String userAgent, @RequestHeader(value = "X-Custom-Header", required = false) String customHeader) { return "User-Agent: " + userAgent + ", Custom Header: " + customHeader; }Cookie (
@CookieValue):@GetMapping("/welcome") public String welcome(@CookieValue("JSESSIONID") String sessionId) { return "Welcome! Your session ID is " + sessionId; }
步骤 4:处理响应
直接返回对象 (最常见):
@GetMapping("/{id}") public User getUser(@PathVariable Long id) { return userService.findById(id); // 自动序列化为 JSON }- 对于
@RestController,返回的User对象会自动被HttpMessageConverter(如 Jackson) 序列化为 JSON。
- 对于
返回
ResponseEntity(更灵活):@PostMapping public ResponseEntity<User> createUser(@RequestBody User user) { User savedUser = userService.save(user); // 返回 201 Created 状态码,并在 Location 头中包含新资源的 URL URI location = ServletUriComponentsBuilder .fromCurrentRequest() .path("/{id}") .buildAndExpand(savedUser.getId()) .toUri(); return ResponseEntity.created(location).body(savedUser); } @GetMapping("/{id}") public ResponseEntity<User> getUser(@PathVariable Long id) { User user = userService.findById(id); if (user != null) { return ResponseEntity.ok(user); // 200 OK } else { return ResponseEntity.notFound().build(); // 404 Not Found } }ResponseEntity允许精确控制 HTTP 状态码、响应头和响应体。
返回
String(直接作为响应体):@GetMapping("/status") public String getStatus() { return "OK"; // 返回纯文本 "OK" }- 注意:这会返回
text/plain类型,除非配置了HttpMessageConverter。
- 注意:这会返回
步骤 5:处理异常
控制器内部异常处理 (
@ExceptionHandler):@RestController @RequestMapping("/api/users") public class UserController { @GetMapping("/{id}") public User getUser(@PathVariable Long id) { if (id <= 0) { throw new IllegalArgumentException("Invalid user ID"); } User user = userService.findById(id); if (user == null) { throw new UserNotFoundException("User not found with ID: " + id); } return user; } // 处理本控制器中抛出的 IllegalArgumentException @ExceptionHandler(IllegalArgumentException.class) public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) { return ResponseEntity.badRequest().body(ex.getMessage()); } // 处理本控制器中抛出的 UserNotFoundException @ExceptionHandler(UserNotFoundException.class) public ResponseEntity<String> handleUserNotFoundException(UserNotFoundException ex) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage()); } }@ExceptionHandler方法处理在同一个控制器类中抛出的指定类型或其子类的异常。- 可以返回
ResponseEntity来定制错误响应。
全局异常处理 (
@ControllerAdvice):- 创建一个用
@ControllerAdvice注解的类来处理整个应用的异常。 - 这超出了单个控制器的范围,但通常是处理异常的最佳实践。
- 创建一个用
三、常见错误
忘记
@ResponseBody(当使用@Controller时):- 错误: 使用
@Controller但方法返回对象(如User),期望返回 JSON。 - 结果: Spring 会尝试将返回值(如 "User" 字符串)当作视图名称去查找模板,导致
DispatcherServlet抛出NoSuchRequestHandlingMethodException或视图解析错误。 - 正确: 使用
@RestController,或在@Controller的方法上添加@ResponseBody。
- 错误: 使用
@PathVariable名称不匹配:- 错误:
@GetMapping("/{userId}")方法参数@PathVariable Long id。 - 结果: Spring 无法将
{userId}绑定到id参数,抛出MissingPathVariableException。 - 正确: 确保
@PathVariable的value(或参数名) 与路径变量名一致,或使用@PathVariable("userId") Long id。
- 错误:
@RequestBody与@RequestParam混用不当:- 错误: 在同一个 POST 请求中,既想用
@RequestBody接收 JSON 数据,又想用@RequestParam接收查询参数,但方法签名错误。 - 正确: 可以同时使用,但需注意
@RequestBody只能有一个,且通常用于接收请求体。查询参数用@RequestParam。 - 限制: 不能同时有多个
@RequestBody参数。
- 错误: 在同一个 POST 请求中,既想用
@Valid后缺少BindingResult:- 错误:
@PostMapping public User createUser(@RequestBody @Valid User user) { // 缺少 BindingResult return userService.save(user); } - 结果: 当验证失败时,Spring 会抛出
MethodArgumentNotValidException,默认返回 400 Bad Request,但可能缺乏详细的错误信息。 - 正确: 在
@Valid参数后紧跟BindingResult参数来捕获和处理验证错误。@PostMapping public ResponseEntity<?> createUser(@RequestBody @Valid User user, BindingResult result) { if (result.hasErrors()) { // 处理错误,例如返回错误详情 return ResponseEntity.badRequest().body(result.getAllErrors()); } return ResponseEntity.ok(userService.save(user)); }
- 错误:
@RequestParam未设置required = false导致 400:- 错误:
@RequestParam String name,但请求中没有name参数。 - 结果: 抛出
MissingServletRequestParameterException,返回 400 Bad Request。 - 正确: 对于可选参数,设置
@RequestParam(required = false),并考虑提供defaultValue。
- 错误:
@RestController不能返回视图名称:- 错误: 在
@RestController中返回"userList"期望渲染userList.html模板。 - 结果: 返回字符串
"userList"作为 JSON 响应体。 - 正确: 如果需要返回视图,使用
@Controller并返回视图名称。
- 错误: 在
四、注意事项
- 选择
@RestController还是@Controller:- REST API / 返回数据: 优先使用
@RestController。 - 服务端渲染 (SSR) / 返回 HTML 视图: 使用
@Controller。
- REST API / 返回数据: 优先使用
@RequestMapping的produces和consumes:produces: 指定控制器方法能产生的媒体类型(如application/json)。客户端可通过Accept头匹配。consumes: 指定控制器方法能消费的媒体类型(如application/json)。客户端请求的Content-Type必须匹配。- 示例:
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
- 线程安全: 控制器实例是单例(Singleton),由 Spring 容器管理。不要在控制器中定义可变的实例变量。方法参数和局部变量是线程安全的。
- 业务逻辑分离: 控制器应尽量薄,只负责请求/响应处理、参数验证和调用 Service 层。复杂的业务逻辑应在
@Service注解的类中实现。 - 异常处理策略: 优先考虑使用
@ControllerAdvice进行全局异常处理,保持控制器代码简洁。 - DTO (Data Transfer Object): 考虑使用专门的 DTO 类来接收请求数据 (
@RequestBody) 和返回响应数据,而不是直接暴露领域模型(Entity)。这有助于解耦、安全和灵活性。
五、使用技巧
- 使用
@GetMapping,@PostMapping等代替@RequestMapping(method = ...): 语义更清晰,代码更简洁。 - 利用
@RequestParam处理分页和排序: 结合Pageable或Sort参数(Spring Data JPA 支持)。@GetMapping public Page<User> getUsers(Pageable pageable) { // /api/users?page=0&size=10&sort=name,asc return userService.findAll(pageable); } - 使用
@MatrixVariable(较少用): 处理 URL 路径中的矩阵参数(;color=red;size=large)。 @RequestAttribute: 访问请求作用域的属性(通常由过滤器或拦截器设置)。@ModelAttribute: 用于表单提交或在方法参数上预填充对象(在@Controller中更常见)。@CrossOrigin: 在控制器或方法上启用 CORS (跨域资源共享)。@RestController @CrossOrigin(origins = "https://myfrontend.com") // 允许特定源 public class ApiController { ... }- 使用
ResponseEntity返回自定义状态码和头: 如201 Created,204 No Content,302 Found(重定向)。
六、最佳实践
- 清晰的 URL 设计: 遵循 RESTful 原则,使用名词复数、合理的路径层次、正确的 HTTP 方法。
- 使用
@RestController: 对于现代前后端分离应用,@RestController是标准。 - 依赖注入 Service 层: 使用
@Autowired或构造函数注入将业务逻辑委托给@Service。@RestController @RequestMapping("/api/users") public class UserController { private final UserService userService; // 推荐使用构造函数注入 public UserController(UserService userService) { this.userService = userService; } // ... 方法 } - 参数验证: 在
@RequestBody对象上使用@Valid和 Bean Validation 注解(如@NotNull,@Size,@Email)。 - 使用 DTO: 定义
UserRequestDto,UserResponseDto等,避免直接暴露 Entity。 - 全局异常处理: 使用
@ControllerAdvice和@ExceptionHandler统一处理异常,返回结构化的错误响应(如包含错误码、消息、时间戳的 JSON)。 - 日志记录: 在关键操作(如创建、更新、删除)时记录日志。
- API 文档: 使用 Swagger (Springfox 或 Springdoc-openapi) 为 API 生成文档。
- 安全性: 考虑使用 Spring Security 保护端点。
七、性能优化
- 避免在控制器中执行耗时操作: 控制器方法应快速响应。耗时的业务逻辑、I/O 操作(数据库、远程调用)应在异步线程或 Service 层处理,必要时考虑异步控制器 (
@Async结合Callable/DeferredResult/WebAsyncTask)。 - 合理使用缓存: 对于频繁读取且不常变的数据,考虑在 Service 层使用
@Cacheable。 - 序列化/反序列化优化:
- 确保
HttpMessageConverter(如 Jackson) 配置合理。 - 避免返回过大的对象图(防止 N+1 查询,使用 DTO 或投影)。
- 考虑使用
@JsonIgnore,@JsonView等 Jackson 注解控制序列化内容。
- 确保
- GZIP 压缩: 启用服务器的 GZIP 压缩,减少响应体大小。
# application.yml server: compression: enabled: true mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json - 连接池和超时: 确保数据库连接池(如 HikariCP)和 HTTP 客户端(如 RestTemplate, WebClient)配置了合理的连接数、超时时间。
- 监控: 使用 Spring Boot Actuator 监控应用的健康、指标(如 HTTP 请求计数、处理时间),及时发现性能瓶颈。
- 减少不必要的对象创建: 在控制器方法中避免创建大量临时对象。
总结: Spring Boot 控制器是构建 Web 应用和 REST API 的入口。掌握 @RestController、请求映射、参数绑定和响应处理是核心。遵循最佳实践,如使用 DTO、分离关注点、全局异常处理,并注意性能优化,可以构建出高效、健壮、易于维护的 API。