千家信息网

SpringBoot中统一接口返回与全局异常的处理

发表于:2024-11-30 作者:千家信息网编辑
千家信息网最后更新 2024年11月30日,本篇内容介绍了"SpringBoot中统一接口返回与全局异常的处理"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能
千家信息网最后更新 2024年11月30日SpringBoot中统一接口返回与全局异常的处理

本篇内容介绍了"SpringBoot中统一接口返回与全局异常的处理"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

目录
  • 背景

  • 统一接口返回

    • 定义API返回码枚举类

    • 定义正常响应的API统一返回体

    • 定义异常响应的API统一返回体

    • 编写包装返回结果的自定义注解

    • 定义返回结果拦截器

    • WebMvc配置类拦截器注册者添加返回结果拦截器

    • 编写响应体处理器

    • 接口调用

    • 测试结果

  • 全局异常处理

    • 编写自定义异常基类

    • 编写自定义业务异常类

    • 定义全局异常处理类

    • 接口调用

    • 测试结果


背景

在分布式、微服务盛行的今天,绝大部分项目都采用的微服务框架,前后端分离方式。前端和后端进行交互,前端按照约定请求URL路径,并传入相关参数,后端服务器接收请求,进行业务处理,返回数据给前端。维护一套完善且规范的接口是非常有必要的, 这样不仅能够提高对接效率,也可以让我的代码看起来更加简洁优雅。

使用统一返回结果时,还有一种情况,就是程序的报错是由于运行时异常导致的结果,有些异常是我们在业务中抛出的,有些是无法提前预知。

因此,我们需要定义一个统一的全局异常,在Controller捕获所有异常,并且做适当处理,并作为一种结果返回。

统一接口返回

定义API返回码枚举类

public enum ResultCode {    /* 成功状态码 */    SUCCESS(200, "成功"),        /* 错误状态码 */    NOT_FOUND(404, "请求的资源不存在"),    INTERNAL_ERROR(500, "服务器内部错误"),    PARAMETER_EXCEPTION(501, "请求参数校验异常"),        /* 业务状态码 */    USER_NOT_EXIST_ERROR(10001, "用户不存在"),    ;    private Integer code;    private String message;    public Integer code() {        return this.code;    }    public String message() {        return this.message;    }    ResultCode(Integer code, String message) {        this.code = code;        this.message = message;    }}

定义正常响应的API统一返回体

@Datapublic class Result implements Serializable {private Integer code;    private String message;    private boolean success = true;    private T data;    @JsonIgnore    private ResultCode resultCode;    private Result() {    }    public void setResultCode(ResultCode resultCode) {        this.resultCode = resultCode;        this.code = resultCode.code();        this.message = resultCode.message();    }    public Result(ResultCode resultCode, T data) {        this.code = resultCode.code();        this.message = resultCode.message();        this.data = data;    }    public static  Result success() {        Result result = new Result<>();        result.setResultCode(ResultCode.SUCCESS);        return result;    }    public static  Result success(T data) {        Result result = new Result<>();        result.setResultCode(ResultCode.SUCCESS);        result.setData(data);            return result;    }}

定义异常响应的API统一返回体

@Datapublic class ErrorResult implements Serializable {    private Integer code;    private String message;    private boolean success = false;    @JsonIgnore    private ResultCode resultCode;    public static ErrorResult error() {        ErrorResult result = new ErrorResult();        result.setResultCode(ResultCode.INTERNAL_ERROR);        return result;    }    public static ErrorResult error(String message) {        ErrorResult result = new ErrorResult();        result.setCode(ResultCode.INTERNAL_ERROR.code());        result.setMessage(message);        return result;    }    public static ErrorResult error(Integer code, String message) {        ErrorResult result = new ErrorResult();        result.setCode(code);        result.setMessage(message);        return result;    }        public static ErrorResult error(ResultCode resultCode, String message) {        ErrorResult result = new ErrorResult();        result.setResultCode(resultCode);        result.setMessage(message)        return result;    }}

编写包装返回结果的自定义注解

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.METHOD})  //作用于方法和类(接口)上@Documentedpublic @interface ResponseResult {}

定义返回结果拦截器

@Componentpublic class ResponseResultInterceptor implements HandlerInterceptor {    /* 使用统一返回体的标识 */    private static final String RESPONSE_RESULT_ANNOTATION = "RESPONSE-RESULT-ANNOTATION";    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {        // 正在处理请求的方法bean        if (handler instanceof HandlerMethod) {            final HandlerMethod handlerMethod = (HandlerMethod) handler;            // 获取当前类            final Class clazz = handlerMethod.getBeanType();            // 获取当前方法            final Method method = handlerMethod.getMethod();            // 判断是否在类对象上加了注解            if (clazz.isAnnotationPresent(ResponseResult.class)) {                // 设置该请求返回体,需要包装,往下传递,在ResponseBodyAdvice接口进行判断                request.setAttribute(RESPONSE_RESULT_ANNOTATION, clazz.getAnnotation(ResponseResult.class));            }            // 判断是否在方法上加了注解            else if (method.isAnnotationPresent(ResponseResult.class)) {                // 设置该请求返回体,需要包装,往下传递,在ResponseBodyAdvice接口进行判断                request.setAttribute(RESPONSE_RESULT_ANNOTATION, method.getAnnotation(ResponseResult.class));            }        }        return true;    }}

WebMvc配置类拦截器注册者添加返回结果拦截器

@Configurationpublic class WebMvcConfig implements WebMvcConfigurer {    /**     * 添加自定义拦截器     */    @Override    public void addInterceptors(InterceptorRegistry registry) {        registry.addInterceptor(new ResponseResultInterceptor()).addPathPatterns("/**");    }}

编写响应体处理器

/** * 统一处理响应体,用Result.success静态方法包装, * 在API接口使用时就可以直接返回原始类型 */@RestControllerAdvicepublic class ResponseResultHandler implements ResponseBodyAdvice {    /* 使用统一返回体的标识 */    private static final String RESPONSE_RESULT_ANNOTATION = "RESPONSE-RESULT-ANNOTATION";    @Override    public boolean supports(MethodParameter methodParameter, Class> aClass) {        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();        HttpServletRequest request = Objects.requireNonNull(sra).getRequest();        ResponseResult responseResult = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANNOTATION);        // 判断返回体是否需要处理        return responseResult != null;    }    @Override    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {        // 异常响应体则直接返回code+message的消息体        if (body instanceof ErrorResult) {            return body;        }        // 正常响应体则返回Result包装的code+message+data的消息体        return Result.success(body);    }}

接口调用

@Api("用户管理")@RestController@RequestMapping("user")@ResponseResult  // 作用于类上,对所有接口有效public class UserController {    @Autowired    private UserService userService;    @ResponseResult  // 作用于方法上    @ApiOperation("根据ID查询用户")    @GetMapping("one")    public User selectOne(Long id) {        // 由于在ResponseResultHandler中已经统一将返回数据用Result.success包装了,        // 直接返回原始类型即可,代码更简洁        return this.userService.queryById(id);    }    @ResponseResult    @ApiOperation("查询所有用户")    @GetMapping("all")    public List selectAll(Page page) {        // 由于在ResponseResultHandler中已经统一将返回数据用Result.success包装了,        // 直接返回原始类型即可,代码更简洁        return this.userService.queryAllByLimit(page);    }}

测试结果

全局异常处理

编写自定义异常基类

@Datapublic class BaseException extends RuntimeException {    private static final int BASE_EXCEPTION_CODE = ResultCode.INTERNAL_ERROR.code();    private static final String BASE_EXCEPTION_MESSAGE = ResultCode.INTERNAL_ERROR.message();    private Integer code;    private String message;    public BaseException() {        super(BASE_EXCEPTION_MESSAGE);        this.code = BASE_EXCEPTION_CODE;        this.message = BASE_EXCEPTION_MESSAGE;    }    public BaseException(String message) {        super(message);        this.code = BASE_EXCEPTION_CODE;        this.message = message;    }    public BaseException(ResultCode resultCode) {        super(resultCode.message());        this.code = resultCode.code();        this.message = resultCode.message();    }    public BaseException(Throwable cause) {        super(cause);        this.code = BASE_EXCEPTION_CODE;        this.message = BASE_EXCEPTION_MESSAGE;    }    public BaseException(String message, Throwable cause) {        super(message, cause);        this.code = BASE_EXCEPTION_CODE;        this.message = message;    }    public BaseException(Integer code, String message) {        super(message);        this.code = code;        this.message = message;    }    public BaseException(Integer code, String message, Throwable cause) {        super(message, cause);        this.code = code;        this.message = message;    }}

编写自定义业务异常类

public class BizException extends BaseException {    public BizException(ResultCode resultCode) {        super(resultCode);    }}

定义全局异常处理类

通过@ExceptionHandler注解来统一处理某一类异常

@Slf4j@RestControllerAdvicepublic class GlobalExceptionHandler {    /**     * 统一处理自定义基础异常     */    @ExceptionHandler(BaseException.class)    public ErrorResult baseException(BaseException e) {        if (StringUtils.isEmpty(e.getCode())) {            return ErrorResult.error(e.getMessage());        }        return ErrorResult.error(e.getCode(), e.getMessage());    }    /**     * 统一处理自定义业务异常     */    @ExceptionHandler(BizException.class)    public ErrorResult bizException(BizException e) {        if (StringUtils.isEmpty(e.getCode())) {            return ErrorResult.error(e.getMessage());        }        return ErrorResult.error(e.getCode(), e.getMessage());    }    /**     * 统一处理非自定义异常外的所有异常     */    @ExceptionHandler(Exception.class)    public ErrorResult handleException(Exception e) {        log.error(e.getMessage(), e);        return ErrorResult.error(e.getMessage());    }        /**     * 兼容Validation校验框架:忽略参数异常处理器     */    @ExceptionHandler(MissingServletRequestParameterException.class)    public ApiResult parameterMissingExceptionHandler(MissingServletRequestParameterException e) {        log.error(e.getMessage(), e);        return ErrorResult.error(PARAMETER_EXCEPTION, "请求参数 " + e.getParameterName() + " 不能为空");    }        /**     * 兼容Validation校验框架:缺少请求体异常处理器     */    @ExceptionHandler(HttpMessageNotReadableException.class)    public ErrorResult parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {        log.error(e.getMessage(), e);        return ErrorResult.error(PARAMETER_EXCEPTION, "参数体不能为空");    }    /**     * 兼容Validation校验框架:参数效验异常处理器     */    @ExceptionHandler(MethodArgumentNotValidException.class)    public ErrorResult parameterExceptionHandler(MethodArgumentNotValidException e) {        log.error(e.getMessage(), e);        // 获取异常信息        BindingResult exceptions = e.getBindingResult();        // 判断异常中是否有错误信息,如果存在就使用异常中的消息,否则使用默认消息        if (exceptions.hasErrors()) {            List errors = exceptions.getAllErrors();            if (!errors.isEmpty()) {                // 这里列出了全部错误参数,按正常逻辑,只需要第一条错误即可                FieldError fieldError = (FieldError) errors.get(0);                return ErrorResult.error(PARAMETER_EXCEPTION, fieldError.getDefaultMessage());            }        }        return ErrorResult.error(PARAMETER_EXCEPTION, "请求参数校验异常");    }}

接口调用

@ResponseResult    @GetMapping    public User update() {        // 非自定义的运行时异常        long id = 10 / 0;        return userService.queryById(id);    }    @ResponseResult    @PostMapping    public User insert() {        // 抛出自定义的基础异常        throw new BaseException();    }    @ResponseResult    @DeleteMapping    public boolean delete() {        // 抛出自定义的业务异常        throw new BizException(USER_NOT_EXIST_ERROR);    }

测试结果

"SpringBoot中统一接口返回与全局异常的处理"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!

0