SpringBoot中如何统一接口返回与全局异常处理详解

作者:l拉不拉米 时间:2021-08-11 02:22:35 

目录
  • 背景

  • 统一接口返回

    • 定义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统一返回体


    @Data
    public class Result<T> 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 <T> Result<T> success() {
           Result<T> result = new Result<>();
           result.setResultCode(ResultCode.SUCCESS);
           return result;
       }

       public static <T> Result<T> success(T data) {
           Result<T> result = new Result<>();
           result.setResultCode(ResultCode.SUCCESS);
           result.setData(data);
               return result;
       }
    }

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


    @Data
    public 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})  //作用于方法和类(接口)上
    @Documented
    public @interface ResponseResult {
    }

    定义返回结果 *


    @Component
    public 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配置类 * 注册者添加返回结果 *


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

    编写响应体处理器


    /**
    * 统一处理响应体,用Result.success静态方法包装,
    * 在API接口使用时就可以直接返回原始类型
    */
    @RestControllerAdvice
    public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
       /* 使用统一返回体的标识 */
       private static final String RESPONSE_RESULT_ANNOTATION = "RESPONSE-RESULT-ANNOTATION";

       @Override
       public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> 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<? extends HttpMessageConverter<?>> 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<User> selectAll(Page page) {
           // 由于在ResponseResultHandler中已经统一将返回数据用Result.success包装了,
           // 直接返回原始类型即可,代码更简洁
           return this.userService.queryAllByLimit(page);
       }
    }

    测试结果

    SpringBoot中如何统一接口返回与全局异常处理详解

    SpringBoot中如何统一接口返回与全局异常处理详解

    全局异常处理

    编写自定义异常基类


    @Data
    public 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
    @RestControllerAdvice
    public 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<String> 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<ObjectError> 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中如何统一接口返回与全局异常处理详解

    SpringBoot中如何统一接口返回与全局异常处理详解

    SpringBoot中如何统一接口返回与全局异常处理详解

    来源:https://juejin.cn/post/7002926832160866318

    标签:springboot,接口,返回
    0
    投稿

    猜你喜欢

  • 基于java Files类和Paths类的用法(详解)

    2021-08-11 11:22:55
  • Springboot Session共享实现原理及代码实例

    2022-12-16 03:29:52
  • Java下载远程服务器文件到本地(基于http协议和ssh2协议)

    2022-08-29 12:23:18
  • Netty如何设置为Https访问

    2021-12-06 02:00:40
  • Java中instanceof关键字的用法总结

    2022-12-11 15:52:27
  • C# 游戏外挂实现核心代码

    2021-12-28 14:47:40
  • Java JDBC连接数据库常见操作总结

    2021-12-01 23:01:40
  • Java 网络爬虫基础知识入门解析

    2022-05-16 02:39:06
  • Java实现简单邮件发送功能

    2023-08-15 00:53:23
  • linphone-sdk-android版本号生成解析

    2022-10-18 14:43:18
  • Java中的MapStruct用法详解

    2022-10-05 12:18:13
  • Java AWT中常用的三种布局管理器详解

    2023-02-11 20:55:25
  • Android 调用系统照相机拍照和录像

    2023-10-30 05:40:35
  • JavaFx 实现按钮防抖功能

    2023-03-20 07:34:48
  • 基于java实现斗地主代码实例解析

    2023-09-07 00:31:15
  • Android编程实现状态保存的方法分析

    2023-07-17 12:54:45
  • java 引用传递的三种类型小结

    2023-09-03 03:59:47
  • Winform项目中TextBox控件DataBindings属性

    2023-03-29 15:25:15
  • C++实现LeetCode(9.验证回文数字)

    2023-06-21 00:20:38
  • 用C#来解析PDF文件

    2023-11-02 08:07:07
  • asp之家 软件编程 m.aspxhome.com