一、核心概念
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。