一、核心概念

Spring Boot 控制器是处理 HTTP 请求的核心组件,负责接收客户端请求、处理业务逻辑(通常委托给 Service 层)、并返回响应。它是 Model-View-Controller (MVC) 模式中的 C (Controller)

核心注解

  1. @Controller:

    • 标记一个类为 Spring MVC 的控制器。
    • 通常与 @RequestMapping 或其衍生注解(如 @GetMapping, @PostMapping)结合使用,将 HTTP 请求映射到具体的处理方法。
    • 返回值通常用于指定视图名称(如 JSP、Thymeleaf 模板),由视图解析器渲染后返回给客户端(主要用于服务端渲染)。
  2. @RestController:

    • @Controller@ResponseBody 的组合注解。
    • @ResponseBody 表示该控制器的方法返回值将直接作为 HTTP 响应体(Response Body)返回,而不是作为视图名称。
    • 这是构建 RESTful Web 服务的首选注解。返回值(如 POJO、ListMap 等)会被 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:创建基础控制器类

  1. 创建 Java 类:src/main/java 目录下的合适包(如 com.example.demo.controller)中创建一个 Java 类。
  2. 添加 @RestController 注解:
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController // 标记此类为 REST 控制器
    public class UserController {
        // 将在此处添加处理请求的方法
    }
    
    • 使用 @RestController 表示这是一个返回数据(如 JSON)的 REST API 控制器。
    • 替代方案: 如果需要返回视图(如 HTML 页面),使用 @Controller 并在方法上添加 @ResponseBody(不推荐用于 REST API)。

步骤 2:定义请求映射(Mapping)

  1. 在类上使用 @RequestMapping (可选,但推荐用于基路径):

    import org.springframework.web.bind.annotation.RequestMapping;
    
    @RestController
    @RequestMapping("/api/users") // 所有该类中的方法的 URL 前缀
    public class UserController {
        // ...
    }
    
    • 这样,该控制器下的所有端点都将以 /api/users 开头。
  2. 在方法上使用 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:处理请求参数

  1. 路径变量 (@PathVariable):

    @GetMapping("/{id}/orders/{orderId}")
    public Order getOrder(@PathVariable Long id, @PathVariable("orderId") Long orderNumber) {
        // id 对应 {id}, orderNumber 对应 {orderId}
        return orderService.findByUserIdAndOrderId(id, orderNumber);
    }
    
    • 参数名与路径变量名相同时,@PathVariablevalue 属性可省略。
    • 名称不同时,需用 @PathVariable("实际路径变量名") 显式指定。
  2. 请求参数 (@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 等集合类型处理多值参数。
  3. 请求体 (@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 参数之后
  4. 请求头 (@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;
    }
    
  5. Cookie (@CookieValue):

    @GetMapping("/welcome")
    public String welcome(@CookieValue("JSESSIONID") String sessionId) {
        return "Welcome! Your session ID is " + sessionId;
    }
    

步骤 4:处理响应

  1. 直接返回对象 (最常见):

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id); // 自动序列化为 JSON
    }
    
    • 对于 @RestController,返回的 User 对象会自动被 HttpMessageConverter (如 Jackson) 序列化为 JSON。
  2. 返回 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 状态码、响应头和响应体。
  3. 返回 String (直接作为响应体):

    @GetMapping("/status")
    public String getStatus() {
        return "OK"; // 返回纯文本 "OK"
    }
    
    • 注意:这会返回 text/plain 类型,除非配置了 HttpMessageConverter

步骤 5:处理异常

  1. 控制器内部异常处理 (@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 来定制错误响应。
  2. 全局异常处理 (@ControllerAdvice):

    • 创建一个用 @ControllerAdvice 注解的类来处理整个应用的异常。
    • 这超出了单个控制器的范围,但通常是处理异常的最佳实践。

三、常见错误

  1. 忘记 @ResponseBody (当使用 @Controller 时):

    • 错误: 使用 @Controller 但方法返回对象(如 User),期望返回 JSON。
    • 结果: Spring 会尝试将返回值(如 "User" 字符串)当作视图名称去查找模板,导致 DispatcherServlet 抛出 NoSuchRequestHandlingMethodException 或视图解析错误。
    • 正确: 使用 @RestController,或在 @Controller 的方法上添加 @ResponseBody
  2. @PathVariable 名称不匹配:

    • 错误: @GetMapping("/{userId}") 方法参数 @PathVariable Long id
    • 结果: Spring 无法将 {userId} 绑定到 id 参数,抛出 MissingPathVariableException
    • 正确: 确保 @PathVariablevalue (或参数名) 与路径变量名一致,或使用 @PathVariable("userId") Long id
  3. @RequestBody@RequestParam 混用不当:

    • 错误: 在同一个 POST 请求中,既想用 @RequestBody 接收 JSON 数据,又想用 @RequestParam 接收查询参数,但方法签名错误。
    • 正确: 可以同时使用,但需注意 @RequestBody 只能有一个,且通常用于接收请求体。查询参数用 @RequestParam
    • 限制: 不能同时有多个 @RequestBody 参数。
  4. @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));
      }
      
  5. @RequestParam 未设置 required = false 导致 400:

    • 错误: @RequestParam String name,但请求中没有 name 参数。
    • 结果: 抛出 MissingServletRequestParameterException,返回 400 Bad Request。
    • 正确: 对于可选参数,设置 @RequestParam(required = false),并考虑提供 defaultValue
  6. @RestController 不能返回视图名称:

    • 错误:@RestController 中返回 "userList" 期望渲染 userList.html 模板。
    • 结果: 返回字符串 "userList" 作为 JSON 响应体。
    • 正确: 如果需要返回视图,使用 @Controller 并返回视图名称。

四、注意事项

  1. 选择 @RestController 还是 @Controller:
    • REST API / 返回数据: 优先使用 @RestController
    • 服务端渲染 (SSR) / 返回 HTML 视图: 使用 @Controller
  2. @RequestMappingproducesconsumes:
    • produces: 指定控制器方法能产生的媒体类型(如 application/json)。客户端可通过 Accept 头匹配。
    • consumes: 指定控制器方法能消费的媒体类型(如 application/json)。客户端请求的 Content-Type 必须匹配。
    • 示例:@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
  3. 线程安全: 控制器实例是单例(Singleton),由 Spring 容器管理。不要在控制器中定义可变的实例变量。方法参数和局部变量是线程安全的。
  4. 业务逻辑分离: 控制器应尽量,只负责请求/响应处理、参数验证和调用 Service 层。复杂的业务逻辑应在 @Service 注解的类中实现。
  5. 异常处理策略: 优先考虑使用 @ControllerAdvice 进行全局异常处理,保持控制器代码简洁。
  6. DTO (Data Transfer Object): 考虑使用专门的 DTO 类来接收请求数据 (@RequestBody) 和返回响应数据,而不是直接暴露领域模型(Entity)。这有助于解耦、安全和灵活性。

五、使用技巧

  1. 使用 @GetMapping, @PostMapping 等代替 @RequestMapping(method = ...): 语义更清晰,代码更简洁。
  2. 利用 @RequestParam 处理分页和排序: 结合 PageableSort 参数(Spring Data JPA 支持)。
    @GetMapping
    public Page<User> getUsers(Pageable pageable) { // /api/users?page=0&size=10&sort=name,asc
        return userService.findAll(pageable);
    }
    
  3. 使用 @MatrixVariable (较少用): 处理 URL 路径中的矩阵参数(;color=red;size=large)。
  4. @RequestAttribute: 访问请求作用域的属性(通常由过滤器或拦截器设置)。
  5. @ModelAttribute: 用于表单提交或在方法参数上预填充对象(在 @Controller 中更常见)。
  6. @CrossOrigin: 在控制器或方法上启用 CORS (跨域资源共享)。
    @RestController
    @CrossOrigin(origins = "https://myfrontend.com") // 允许特定源
    public class ApiController { ... }
    
  7. 使用 ResponseEntity 返回自定义状态码和头:201 Created, 204 No Content, 302 Found (重定向)。

六、最佳实践

  1. 清晰的 URL 设计: 遵循 RESTful 原则,使用名词复数、合理的路径层次、正确的 HTTP 方法。
  2. 使用 @RestController: 对于现代前后端分离应用,@RestController 是标准。
  3. 依赖注入 Service 层: 使用 @Autowired 或构造函数注入将业务逻辑委托给 @Service
    @RestController
    @RequestMapping("/api/users")
    public class UserController {
        private final UserService userService; // 推荐使用构造函数注入
    
        public UserController(UserService userService) {
            this.userService = userService;
        }
        // ... 方法
    }
    
  4. 参数验证:@RequestBody 对象上使用 @Valid 和 Bean Validation 注解(如 @NotNull, @Size, @Email)。
  5. 使用 DTO: 定义 UserRequestDto, UserResponseDto 等,避免直接暴露 Entity。
  6. 全局异常处理: 使用 @ControllerAdvice@ExceptionHandler 统一处理异常,返回结构化的错误响应(如包含错误码、消息、时间戳的 JSON)。
  7. 日志记录: 在关键操作(如创建、更新、删除)时记录日志。
  8. API 文档: 使用 Swagger (Springfox 或 Springdoc-openapi) 为 API 生成文档。
  9. 安全性: 考虑使用 Spring Security 保护端点。

七、性能优化

  1. 避免在控制器中执行耗时操作: 控制器方法应快速响应。耗时的业务逻辑、I/O 操作(数据库、远程调用)应在异步线程或 Service 层处理,必要时考虑异步控制器 (@Async 结合 Callable/DeferredResult/WebAsyncTask)。
  2. 合理使用缓存: 对于频繁读取且不常变的数据,考虑在 Service 层使用 @Cacheable
  3. 序列化/反序列化优化:
    • 确保 HttpMessageConverter (如 Jackson) 配置合理。
    • 避免返回过大的对象图(防止 N+1 查询,使用 DTO 或投影)。
    • 考虑使用 @JsonIgnore, @JsonView 等 Jackson 注解控制序列化内容。
  4. GZIP 压缩: 启用服务器的 GZIP 压缩,减少响应体大小。
    # application.yml
    server:
      compression:
        enabled: true
        mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
    
  5. 连接池和超时: 确保数据库连接池(如 HikariCP)和 HTTP 客户端(如 RestTemplate, WebClient)配置了合理的连接数、超时时间。
  6. 监控: 使用 Spring Boot Actuator 监控应用的健康、指标(如 HTTP 请求计数、处理时间),及时发现性能瓶颈。
  7. 减少不必要的对象创建: 在控制器方法中避免创建大量临时对象。

总结: Spring Boot 控制器是构建 Web 应用和 REST API 的入口。掌握 @RestController、请求映射、参数绑定和响应处理是核心。遵循最佳实践,如使用 DTO、分离关注点、全局异常处理,并注意性能优化,可以构建出高效、健壮、易于维护的 API。