前置条件
已经初始化好一个spring boot项目且版本为3X,项目可正常启动。
作者版本为3.2.2
初始化教程:新版idea(2023)创建spring boot3项目
目的
Spring Boot应用程序开发中,会遇到各种异常有可预知的也有不可预知的,我们很少会每个过程做单独异常处理,通常会将各种类型的异常处理过程解耦出来,保证业务逻辑单一、相关异常处理单一。
通常将异常进行处理,封装一下对应错误信息返回友好信息。避免把异常直接给前端、用户。
反例:异常直接返回(不友好)
正例:处理后返回提示信息(友好)
主要步骤
定义自定义异常类
下面我们定义几个自定义异常
1.账号不存在异常
import com.mijiu.commom.enumerate.ResultEnum; import lombok.Getter; /** * 账户不存在异常 * * @author mijiupro */ @Getter public class AccountNotFoundException extends RuntimeException { private final ResultEnum resultEnum; public AccountNotFoundException(ResultEnum resultEnum) { this.resultEnum = resultEnum; } }
2.密码错误异常
import com.mijiu.commom.enumerate.ResultEnum; import lombok.Getter; /** * 密码错误异常 * * @author mijiupro */ @Getter public class PasswordErrorException extends RuntimeException { private final ResultEnum resultEnum; public PasswordErrorException(ResultEnum resultEnum) { this.resultEnum = resultEnum; } }
3. 令牌过期异常
import com.mijiu.commom.enumerate.ResultEnum; import lombok.Getter; /** * 令牌过期异常 * * @author mijiupro */ @Getter public class TokenOverdueException extends RuntimeException { private final ResultEnum resultEnum; public TokenOverdueException(ResultEnum resultEnum) { this.resultEnum = resultEnum; } }
对于各种异常的错误码以及提示内容通常使用枚举类如下:
import lombok.Getter; /** * @author mijiupro */ @Getter public enum ResultEnum { /* 成功状态码 */ SUCCESS(1, "操作成功!"), /* 错误状态码 */ FAIL(0, "操作失败!"), /* 参数错误:10001-19999 */ PARAM_IS_INVALID(10001, "参数无效"), PARAM_IS_BLANK(10002, "参数为空"), PARAM_TYPE_BIND_ERROR(10003, "参数格式错误"), PARAM_NOT_COMPLETE(10004, "参数缺失"), /* 用户错误:20001-29999*/ USER_NOT_LOGGED_IN(20001, "用户未登录,请先登录"), USER_LOGIN_ERROR(20002, "账号不存在或密码错误"), USER_ACCOUNT_FORBIDDEN(20003, "账号已被禁用"), USER_NOT_EXIST(20004, "用户不存在"), USER_HAS_EXISTED(20005, "用户已存在"), /* 系统错误:40001-49999 */ FILE_MAX_SIZE_OVERFLOW(40003, "上传尺寸过大"), FILE_ACCEPT_NOT_SUPPORT(40004, "上传文件格式不支持"), /* 数据错误:50001-599999 */ RESULT_DATA_NONE(50001, "数据未找到"), DATA_IS_WRONG(50002, "数据有误"), DATA_ALREADY_EXISTED(50003, "数据已存在"), AUTH_CODE_ERROR(50004, "验证码错误"), /* 权限错误:70001-79999 */ PERMISSION_UNAUTHENTICATED(70001, "此操作需要登陆系统!"), PERMISSION_UNAUTHORIZED(70002, "权限不足,无权操作!"), PERMISSION_EXPIRE(70003, "登录状态过期!"), PERMISSION_TOKEN_EXPIRED(70004, "token已过期"), PERMISSION_LIMIT(70005, "访问次数受限制"), PERMISSION_TOKEN_INVALID(70006, "无效token"), PERMISSION_SIGNATURE_ERROR(70007, "签名失败"); // 状态码 int code; // 提示信息 String message; ResultEnum(int code, String message) { this.code = code; this.message = message; } public int code() { return code; } public String message() { return message; } public void setCode(int code) { this.code = code; } public void setMessage(String message) { this.message = message; } }
创建全局异常处理器
需要创建一个全局异常处理器类,用于捕获和处理所有的异常。可以使用@ControllerAdvice 或者@RestControllerAdvice注解将该类标记为全局异常处理器,并使用@ExceptionHandler 注解定义具体的异常处理方法。例如,创建一个名为 GlobalExceptionHandler
的全局异常处理器类:
import com.mijiu.commom.exception.AccountNotFoundException; import com.mijiu.commom.exception.PasswordErrorException; import com.mijiu.commom.exception.TokenOverdueException; import com.mijiu.commom.result.Result; import com.mijiu.commom.enumerate.ResultEnum; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; /** * @author mijiupro */ @RestControllerAdvice(basePackages = "com.mijiu.controller") @ResponseBody @Slf4j public class GlobalExceptionHandler { // 账号不存在异常 @ExceptionHandler(AccountNotFoundException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public Result<String> handleAccountNotFoundException(AccountNotFoundException ex) { return Result.error(ex.getResultEnum()); } // 密码错误异常 @ExceptionHandler(PasswordErrorException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public Result<String> handlePasswordErrorException(PasswordErrorException ex) { return Result.error(ex.getResultEnum()); } // 登录状态过期异常 @ExceptionHandler(TokenOverdueException.class) @ResponseStatus(HttpStatus.UNAUTHORIZED) public Result<?> handleTokenOverdueException(TokenOverdueException ex){ return Result.error(ex.getResultEnum()); } /** * * 通用异常处理 */ @ExceptionHandler(Exception.class) public Result<String> exceptionHandler(Exception ex) { log.error(ex.getMessage()); return Result.error(ResultEnum.FAIL); } }
对于不可预知的异常通常直接捕获所有异常父类Exception异常即可
这段代码会触发ArithmeticException ,类似这种的不可预知异常会有很多,它们都继承自Exception所以全局拦截Exception进行处理即可。
对于异常的处理返回的信息通常用统一格式封装:
import com.mijiu.commom.enumerate.ResultEnum; import lombok.Data; /** * @author mijiupro */ @Data public class Result<T> { // 操作代码 Integer code; // 提示信息 String message; // 结果数据 T data; public Result(ResultEnum resultCode) { this.code = resultCode.code(); this.message = resultCode.message(); } public Result(ResultEnum resultCode, T data) { this.code = resultCode.code(); this.message = resultCode.message(); this.data = data; } public Result(String message) { this.message = message; } //成功返回封装-无数据 public static Result<String> success() { return new Result<String>(ResultEnum.SUCCESS); } //成功返回封装-带数据 public static <T> Result<T> success(T data) { return new Result<T>(ResultEnum.SUCCESS, data); } //失败返回封装-使用默认提示信息 public static Result<String> error() { return new Result<String>(ResultEnum.FAIL); } //失败返回封装-使用返回结果枚举提示信息 public static Result<String> error(ResultEnum resultCode) { return new Result<String>(resultCode); } //失败返回封装-使用自定义提示信息 public static Result<String> error(String message) { return new Result<String>(message); } }
手动抛出自定义异常
在业务代码中抛出对应业务自定义异常:
有参构造传递错误枚举信息(状态码+错误内容),全局异常捕获并从自定义异常类拿到相关返回信息统一封装返回。
像这样直接抛出即可