AI摘要

文章提出用Spring全局异常处理替代Controller重复try-catch,统一封装ApiResult与自定义异常体系,@RestControllerAdvice按精确度匹配异常,结合参数校验与自定义注解,使业务代码专注逻辑、简洁健壮。

(一) 为什么要全局异常处理?

原始代码的痛点:

@RestController
public class UserController {
    
    @PostMapping("/users")
    public ApiResult createUser(@RequestBody @Valid UserDTO userDTO) {
        try {
            // 参数校验可能失败
            if (userService.existsByUsername(userDTO.getUsername())) {
                return ApiResult.error("用户名已存在");
            }
            // 业务操作可能失败
            userService.createUser(userDTO);
            return ApiResult.success();
        } catch (IllegalArgumentException e) {
            log.error("参数错误", e);
            return ApiResult.error("请求参数错误");
        } catch (DataAccessException e) {
            log.error("数据库错误", e);
            return ApiResult.error("系统繁忙,请稍后重试");
        } catch (Exception e) {
            log.error("未知错误", e);
            return ApiResult.error("系统异常");
        }
    }
    
    // 每个方法都要重复这个try-catch模板...
}

问题分析:

  1. ​代码重复:​​ 每个Controller方法都有相似的异常处理逻辑
  2. ​关注点混杂:​​ 业务逻辑和异常处理代码交织在一起
  3. ​维护困难:​​ 修改异常处理策略需要在每个方法中修改
  4. ​容易遗漏:​​ 新开发的功能可能忘记添加异常处理

(二) 全局异常处理器的完整实现

1. 统一的响应体封装:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResult<T> {
    private Integer code;
    private String message;
    private T data;
    private Long timestamp = System.currentTimeMillis();
    
    public static <T> ApiResult<T> success(T data) {
        return new ApiResult<>(200, "成功", data, System.currentTimeMillis());
    }
    
    public static ApiResult<?> error(Integer code, String message) {
        return new ApiResult<>(code, message, null, System.currentTimeMillis());
    }
    
    public static ApiResult<?> error(String message) {
        return error(500, message);
    }
}

2. 自定义异常体系:

// 基础业务异常
public class BaseException extends RuntimeException {
    private final Integer code;
    
    public BaseException(Integer code, String message) {
        super(message);
        this.code = code;
    }
    
    public BaseException(String message) {
        this(400, message);
    }
}

// 具体的业务异常
public class BusinessException extends BaseException {
    public BusinessException(String message) {
        super(400, message);
    }
    
    public BusinessException(Integer code, String message) {
        super(code, message);
    }
}

public class UnauthorizedException extends BaseException {
    public UnauthorizedException(String message) {
        super(401, message);
    }
}

public class ForbiddenException extends BaseException {
    public ForbiddenException(String message) {
        super(403, message);
    }
}

3. 全局异常处理器核心实现:

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    /**
     * 处理业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public ApiResult<?> handleBusinessException(BusinessException e) {
        log.warn("业务异常: code={}, message={}", e.getCode(), e.getMessage());
        return ApiResult.error(e.getCode(), e.getMessage());
    }
    
    /**
     * 处理参数校验异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResult<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getAllErrors().stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.joining("; "));
        log.warn("参数校验失败: {}", message);
        return ApiResult.error(400, message);
    }
    
    /**
     * 处理缺少请求参数异常
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public ApiResult<?> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        String message = String.format("缺少必要参数: %s", e.getParameterName());
        log.warn(message);
        return ApiResult.error(400, message);
    }
    
    /**
     * 处理权限相关异常
     */
    @ExceptionHandler({UnauthorizedException.class, AccessDeniedException.class})
    public ApiResult<?> handleAccessDeniedException(Exception e) {
        log.warn("权限异常: {}", e.getMessage());
        return ApiResult.error(403, "权限不足");
    }
    
    /**
     * 处理数据操作异常
     */
    @ExceptionHandler(DataAccessException.class)
    public ApiResult<?> handleDataAccessException(DataAccessException e) {
        log.error("数据操作异常", e);
        return ApiResult.error(500, "数据操作失败,请稍后重试");
    }
    
    /**
     * 处理所有未识别的异常
     */
    @ExceptionHandler(Exception.class)
    public ApiResult<?> handleException(Exception e) {
        log.error("系统异常: ", e);
        // 生产环境不要返回详细错误信息
        String message = "生产环境".equals(env) ? "系统繁忙,请稍后重试" : e.getMessage();
        return ApiResult.error(500, message);
    }
}

(三) 高级特性:异常处理器的执行顺序

Spring会按照@ExceptionHandler方法的精确度匹配异常:

  1. 先匹配最具体的异常类型(如BusinessException
  2. 再匹配父类异常(如RuntimeException
  3. 最后匹配Exception.class
// 更精确的异常处理优先
@ExceptionHandler(FileNotFoundException.class)  // 优先匹配
public ApiResult<?> handleFileNotFound(FileNotFoundException e) {
    return ApiResult.error("文件不存在");
}

@ExceptionHandler(IOException.class)  // 其次匹配
public ApiResult<?> handleIO(IOException e) {
    return ApiResult.error("IO异常");
}

(四) 与参数校验框架的深度集成

1. 自定义验证注解:

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
    String message() default "手机号格式不正确";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class PhoneValidator implements ConstraintValidator<Phone, String> {
    private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
    
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) return true; // 使用@NotNull处理空值
        return PHONE_PATTERN.matcher(value).matches();
    }
}

2. 在DTO中使用:

@Data
public class UserDTO {
    @NotBlank(message = "用户名不能为空")
    @Size(min = 2, max = 20, message = "用户名长度2-20位")
    private String username;
    
    @Phone(message = "手机号格式不正确")
    private String phone;
}

(五) 最终效果:简洁的Controller

@RestController
@Validated
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @PostMapping("/users")
    public ApiResult<UserVO> createUser(@RequestBody @Valid UserDTO userDTO) {
        // 专注业务逻辑,异常交给全局处理器
        UserVO user = userService.createUser(userDTO);
        return ApiResult.success(user);
    }
    
    @GetMapping("/users/{id}")
    public ApiResult<UserVO> getUser(@PathVariable @Min(1) Long id) {
        UserVO user = userService.getUserById(id);
        return ApiResult.success(user);
    }
}

​总结:​​ 全局异常处理是Spring Boot项目的基础设施,良好的异常处理架构能显著提升代码质量和开发效率。通过分层级的异常体系和统一的处理逻辑,让业务代码更加纯粹和健壮。

版权声明 ▶ 本网站名称:黄磊的博客
▶ 本文标题:全局异常处理的艺术:从基础配置到高级定制
▶ 本文链接:https://www.huangleicole.com/backend-related/33.html
▶ 转载本站文章需要遵守:商业转载请联系站长,非商业转载请注明出处!!

如果觉得我的文章对你有用,请随意赞赏