当我们重新设计企业平台时,我们了解到,使客户不满意的主要因素不是性能或用户界面,而是未修复的故障破坏了流程。 在 Spring Boot 或其他框架中的类似模式中,确保系统在压力下以预期的方式行为,这种纪律类似于更大的领导原则:在发生之前计划失败。 @ControllerAdvice Why This Matters 当您在 REST API 中构建 ,你会很快面对这个问题: Spring Boot “ ” How do I handle errors neatly without writing repetitive try-catch blocks everywhere? 想象有 每个人都可以失败一个 错误的输入或缺失的资源。 而不是返回混乱的堆栈痕迹,你想要 . 50+ endpoints 零指示例外 consistent, meaningful, and client-friendly error responses 这就是哪里 进入 - 它将所有错误处理集中在一个地方。 让我们看看 以实际世界的例子,一步一步地构建这个。 @ControllerAdvice how Use Case — User Management REST API 我们将创建一个 这支持: simple User API Fetching users Creating users Simulating failures for testing Our goal → build a that returns clean JSON responses. unified global error-handling layer Step 1: Project Setup Dependencies (Maven): <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Validation --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> This brings in web + validation support. Then create your main app: @SpringBootApplication public class ExceptionHandlerDemoApplication { public static void main(String[] args) { SpringApplication.run(ExceptionHandlerDemoApplication.class, args); } } Step 2: Define the Entity — User.java @Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; @NotBlank(message = "Name cannot be blank") private String name; @Min(value = 18, message = "Age must be at least 18") private int age; } ◎為什麼: ** 我们添加简单的验证,这样 Spring 就可以在输入无效时自动触发方法参数不有效的例外。 Step 3: Custom Exception for Missing Data public class ResourceNotFoundException extends RuntimeException { public ResourceNotFoundException(String message) { super(message); } } public class InvalidRequestException extends RuntimeException { public InvalidRequestException(String message) { super(message); } } ◎為什麼: ** 清晰地表示“未找到用户”的情况,而不是通用例外。 Step 4: Build the Controller @RestController @RequestMapping("/api/users") public class UserController { // Simple GET that throws ResourceNotFoundException for id > 100 @GetMapping("/{id}") public String getUser(@PathVariable("id") @Min(1) Integer id) { if (id > 100) { throw new ResourceNotFoundException("User with id " + id + " not found"); } return "User-" + id; } // Create user example to demonstrate validation public static record CreateUserRequest( @NotBlank(message = "name is required") String name, @Min(value = 18, message = "age must be >= 18") int age) {} @PostMapping @ResponseStatus(HttpStatus.CREATED) public String createUser(@RequestBody @Valid CreateUserRequest body) { if ("bad".equalsIgnoreCase(body.name())) { throw new InvalidRequestException("Name 'bad' is not allowed"); } return "created:" + body.name(); } // Endpoint to force a server error for demo @GetMapping("/boom") public void boom() { throw new IllegalStateException("simulated server error"); } } ◎為什麼: **我们创建了现实的场景 - 未找到,验证错误和运行时错误 - 我们的全球处理器将管理。 Step 5: Create a Standard Error Model @Data @AllArgsConstructor @NoArgsConstructor public class ErrorResponse { private OffsetDateTime timestamp; private int status; private String error; private String message; private String path; private List<FieldError> fieldErrors;} ◎為什麼: **所有API都应该在相同的结构中返回错误 - 这可以改善生产中的监控和调试。 Step 6: Implement @ControllerAdvice (Global Handler) @ControllerAdvice public class GlobalExceptionHandler { private final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); // Handle custom validation exceptions @ExceptionHandler(InvalidRequestException.class) public ResponseEntity<ErrorResponse> handleInvalidRequest(InvalidRequestException ex, HttpServletRequest req) { log.debug("InvalidRequestException: {}", ex.getMessage()); ErrorResponse body = new ErrorResponse(OffsetDateTime.now(), HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase(), ex.getMessage(), req.getRequestURI()); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(body); } // Resource not found -> 404 @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex, HttpServletRequest req) { log.debug("ResourceNotFoundException: {}", ex.getMessage()); ErrorResponse body = new ErrorResponse(OffsetDateTime.now(), HttpStatus.NOT_FOUND.value(), HttpStatus.NOT_FOUND.getReasonPhrase(), ex.getMessage(), req.getRequestURI()); return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body); } // Validation errors from @Valid on request bodies @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex, HttpServletRequest req) { log.debug("Validation failed: {}", ex.getMessage()); List<ErrorResponse.FieldError> fieldErrors = ex.getBindingResult().getFieldErrors().stream() .map(fe -> new ErrorResponse.FieldError(fe.getField(), fe.getRejectedValue(), fe.getDefaultMessage())) .collect(Collectors.toList()); ErrorResponse body = new ErrorResponse(OffsetDateTime.now(), HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase(), "Validation failed", req.getRequestURI()); body.setFieldErrors(fieldErrors); return ResponseEntity.badRequest().body(body); } // Type mismatch for method args (?id=abc) @ExceptionHandler(MethodArgumentTypeMismatchException.class) public ResponseEntity<ErrorResponse> handleTypeMismatch(MethodArgumentTypeMismatchException ex, HttpServletRequest req) { log.debug("Type mismatch: {}", ex.getMessage()); ErrorResponse body = new ErrorResponse(OffsetDateTime.now(), HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase(), ex.getMessage(), req.getRequestURI()); return ResponseEntity.badRequest().body(body); } // No handler found (404 for unmatched endpoints) @ExceptionHandler(NoHandlerFoundException.class) public ResponseEntity<ErrorResponse> handleNoHandler(NoHandlerFoundException ex, HttpServletRequest req) { log.debug("NoHandlerFound: {} {}", ex.getHttpMethod(), ex.getRequestURL()); ErrorResponse body = new ErrorResponse(OffsetDateTime.now(), HttpStatus.NOT_FOUND.value(), HttpStatus.NOT_FOUND.getReasonPhrase(), "Endpoint not found", req.getRequestURL().toString()); return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body); } // Generic fallback @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleAll(Exception ex, HttpServletRequest req) { log.error("Unhandled exception: ", ex); ErrorResponse body = new ErrorResponse(OffsetDateTime.now(), HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), "An internal error occurred", req.getRequestURI()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(body); } } Why: @ControllerAdvice → 在所有控制器中使其全球化。 @ExceptionHandler → 捕捉特定例外类型。 buildResponse() 帮助器将代码保持干净和干净。 Step 7: Test Scenarios GET /api/users/1 答案代码: 200 - 成功 」 」 GET /api/users/999 答案代码: 404 资源未找到 」 」 Validation Error POST / API / 用户 {"name":"", "age": 15} Response: 」 」 Unexpected Error GET /api/用户 / 蓬勃发展 」 」 Why This Approach Works in Real Projects Problem Solution Too many try-catch blocks Centralized handling with @ControllerAdvice Inconsistent responses Unified ErrorResponse structure Hard to debug Standardized messages with timestamps and paths Client confusion Clear, meaningful messages for each failure type 太多的尝试捕捉块 集中处理 @ControllerAdvice 不一致的回应 统一的错误响应结构 难度 Debug 标准化消息与时间标签和路径 客户混淆 每个失败类型的清晰而有意义的信息 Real-World Usage Scenarios 银行API:确保验证错误(如无效帐号)不会导致服务崩溃。 电子商务:优雅地处理产品未找到或付款错误。 数据微服务:当输入数据未能验证时返回结构化消息。 API Gateway:跨多个微服务的一致响应。 Final Thoughts 通过结合 @ControllerAdvice、 @ExceptionHandler 和一个简单的错误响应模型,您可以获得: 清洁代码 持久的 API 体验 在生产中更容易调试 它是 Spring Boot 开发中最简单但最强大的设计模式之一。