错误处理

未匹配的标注

在 Spring Boot 中,错误处理可以通过多种方式实现,从全局异常处理到自定义错误响应,以下是完整的错误处理方案:


1. 全局异常处理(推荐)
使用 @ControllerAdvice + @ExceptionHandler 统一捕获异常,返回结构化错误信息。

(1) 定义全局异常处理器

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    // 处理自定义业务异常
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
        ErrorResponse error = new ErrorResponse(
            ex.getErrorCode(),
            ex.getMessage(),
            System.currentTimeMillis()
        );
        return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
    }

    // 处理所有未捕获的异常
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex) {
        ErrorResponse error = new ErrorResponse(
            "SERVER_ERROR",
            "Internal server error",
            System.currentTimeMillis()
        );
        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

// 自定义错误响应体
class ErrorResponse {
    private String code;
    private String message;
    private long timestamp;
    // 构造方法/getters/setters
}

(2) 抛出自定义异常

// 自定义业务异常
public class BusinessException extends RuntimeException {
    private String errorCode;

    public BusinessException(String errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }
    // getter
}

// 在Controller中使用
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
    if (id == 0) {
        throw new BusinessException("INVALID_ID", "ID cannot be zero");
    }
    return userService.findById(id);
}

2. HTTP 状态码控制
通过 ResponseEntity 或注解直接指定状态码:

(1) 使用 ResponseEntity

@GetMapping("/resource/{id}")
public ResponseEntity<?> getResource(@PathVariable Long id) {
    return resourceRepository.findById(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
}

(2) 使用 @ResponseStatus

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

3. 自定义错误页面(适用于传统Web应用)
(1) 静态错误页面
src/main/resources/static/error/ 下添加:
404.html(Not Found)

500.html(Server Error)

(2) 动态模板错误页(Thymeleaf/FreeMarker)

@Controller
public class CustomErrorController implements ErrorController {

    @RequestMapping("/error")
    public String handleError(HttpServletRequest request) {
        Integer status = (Integer) request.getAttribute("javax.servlet.error.status_code");
        if (status == 404) {
            return "error-404";
        }
        return "error-default";
    }
}

4. 验证错误处理(@Valid)
自动处理 @Valid 参数校验失败的情况:

@PostMapping("/users")
public ResponseEntity<?> createUser(@Valid @RequestBody User user) {
    // 正常逻辑
}

// 在GlobalExceptionHandler中添加
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {
    List<String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(FieldError::getDefaultMessage)
            .collect(Collectors.toList());

    ErrorResponse error = new ErrorResponse(
        "VALIDATION_FAILED",
        String.join("; ", errors),
        System.currentTimeMillis()
    );
    return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}

5. 异常日志记录
结合 @Slf4j 记录异常:

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex) {
        log.error("Unhandled exception: ", ex); // 记录完整堆栈
        ErrorResponse error = new ErrorResponse(/* ... */);
        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

6. 特定Controller的异常处理

@RestController
public class UserController {

    @ExceptionHandler(UserNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ErrorResponse handleUserNotFound(UserNotFoundException ex) {
        return new ErrorResponse(/* ... */);
    }
}

7. 禁用默认的Whitelabel错误页
application.properties 中:

server.error.whitelabel.enabled=false

最佳实践总结

  1. 分层处理:
    • 业务异常:自定义 BusinessException

    • 系统异常:全局捕获 Exception

    • 校验异常:自动处理 MethodArgumentNotValidException

  1. 统一响应格式:

    {
      "code": "INVALID_ID",
      "message": "ID cannot be zero",
      "timestamp": 1630000000000,
      "path": "/api/users/0"
    }
  2. 生产环境安全:
    • 避免暴露堆栈信息(可通过 server.error.include-stacktrace=never 关闭)

    • 敏感错误信息过滤

通过以上方式,Spring Boot 的错误处理可以做到与 Laravel 的 Handler.php 类似的集中管理效果。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
唐章明
讨论数量: 0
发起讨论 只看当前版本


暂无话题~