SpringBoot统一接口返回及全局异常处理高级用法

 更新时间:2022年06月23日 09:14:46   作者:花哥编程  
这篇文章主要为大家介绍了SpringBoot统一接口返回及全局异常处理高级用法示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

前言

现在大多数公司项目框架,基本都是属于前后端分离模式,这种模式会涉及到一个前后端对接问题,无论是对前端或者是后台服务,维护一套完善且规范的接口是非常有必要的,这样不仅能够提高对接效率,也可以让我的代码看起来更加简洁优雅。

修改前后最大的区别是我们不用在每个接口单独捕获异常,也不用在每个接口都要组装一遍返回参数,可以参考下面这张对比图:

一、SpringBoot不使用统一返回格式

默认情况下,SpringBoot会有如下三种返回情况。

1.1 使用字符串返回

@GetMapping("/getUserName")
public String getUserName(){
    return "HuaGe";
}

调用接口返回结果:

HuaGe

1.2 使用实体类返回

@GetMapping("/getUserName")
public User getUserName(){
    return new User("HuaGe",18,"男");
}

调用接口返回结果:

{
  "name": "HuaGe",
  "age": "18",
  "性别": "男", 
}

1.3 异常情况下返回

@GetMapping("/getUserName")
public static String getUserName(){
    HashMap hashMap = Maps.newHashMap();
    return hashMap.get(0).toString();
}

模拟一个空指针异常,在不做任何异常处理的情况下,可以看下SpringBoot的默认返回结果:

{
    "timestamp": "2021-08-09T06:56:41.524+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "path": "/sysUser/getUserName"
}

对于上面这几种情况,如果整个项目没有定义统一的返回格式,五个后台开发人员定义五种返回格式,这样不仅代码臃肿,前后端对接效率低,而且还会有一些意向不到的情况发生,比如前端直接显示异常详情等,这给用户的体验是非常差的。

二、基础玩法

项目中最常见到的是封装一个工具类,类中定义需要返回的字段信息,把需要返回前端的接口信息,通过该类进行封装,这样就可以解决返回格式不统一的现象了。

2.1 参数说明

  • code: 状态码,后台可以维护一套统一的状态码;
  • message: 描述信息,接口调用成功/失败的提示信息;
  • data: 返回数据。

2.2 流程说明

  • 新建Result类
public class Result<T> {
    private int code;
    private String message;
    private T data;​
    public Result() {}
    public Result(int code, String message) {
        this.code = code;
        this.message = message;
    }
    /**
     * 成功
     */
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<T>();
        result.setCode(ResultMsgEnum.SUCCESS.getCode());
        result.setMessage(ResultMsgEnum.SUCCESS.getMessage());
        result.setData(data);
        return result;
    }
    /**
     * 失败
     */
    public static <T> Result<T> error(int code, String message) {
        return new Result(code, message);
    }
}

定义返回状态码

public enum ResultMsgEnum {
    SUCCESS(0, "成功"),
    FAIL(-1, "失败"),
    AUTH_ERROR(502, "授权失败!"),
    SERVER_BUSY(503, "服务器正忙,请稍后再试!"),
    DATABASE_OPERATION_FAILED(504, "数据库操作失败");
    private int code;
    private String message;
​
    ResultMsgEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }
    public int getCode() {
        return this.code;
    }
    public String getMessage() {
        return this.message;
    }
}
  • 使用方式

上面两步定义了数据返回格式状态码,接下来就要看下在接口中如何使用了。

@GetMapping("/getUserName")
public Result getUserName(){
    return Result.success("huage");
}

调用结果如下,可以看到是我们在Result中定义的参数类型。

{
    "code": 0,
    "message": "成功",
    "data": "huage"
}

这样写虽然能够满足日常需求,而且我相信很多小伙伴也是这么用的,但是如果我们有大量的接口,然后在每一个接口中都使用Result.success来包装返回信息,会新增很多重复代码,显得不够优雅,甚至都不好意思拿出去显摆。 肯定会有一种方式能够再一次提高代码逼格,实现最优解。

三、进阶用法

基本用法学会后,接下来看点究极版本,主要用到如下两个知识点,用法简单,无论是拿出来教学妹,还是指点小姐姐,都是必备技能。

3.1 类介绍

  • ResponseBodyAdvice: 该接口是SpringMVC 4.1提供的,它允许在 执行 @ResponseBody后自定义返回数据,用来封装统一数据格式返回;
  • @RestControllerAdvice: 该注解是对Controller进行增强的,可以全局捕获抛出的异常。

3.2 用法说明

  • 新建ResponseAdvice类;
  • 实现ResponseBodyAdvice接口,实现supportsbeforeBodyWrite方法;
  • 该类用于统一封装controller中接口的返回结果。
@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
    @Autowired
    private ObjectMapper objectMapper;
​
    /**
     * 是否开启功能 true:是 
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }
​
    /**
     * 处理返回结果
     */
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        //处理字符串类型数据
        if(o instanceof String){
            try {
                return objectMapper.writeValueAsString(Result.success(o));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return Result.success(o);
    }
}
​

我们可以通过getUserName接口测试一下,会发现和直接使用Result返回的结果是一致的。

不过,细心的小伙伴们肯定注意到了,在ResponseAdvice我们全部使用了Result.success(o)来处理结果,对于error类型的结果未做处理。我们来看下,发生异常情况时,返回结果是什么样呢?继续使用上面HashMap空指针异常的代码,测试结果如下:

{
    "code": 0,
    "message": "成功",
    "data": {
        "timestamp": "2021-08-09T09:33:26.805+00:00",
        "status": 405,
        "error": "Method Not Allowed",
        "path": "/sysUser/getUserName"
    }
}

虽然格式上没有毛病,但是在code、data字段的具体数据上是不友好或不正确的。不处理好这些事情,会严重影响自己在前端妹妹心中的高大形象的,这是决不能容忍的。

3.3 全局异常处理器

以前我们遇到异常时,第一时间想到的应该是try..catch..finnal吧,不过这种方式会导致大量代码重复,维护困难,逻辑臃肿等问题,这不是我们想要的结果。

今天我们要用的全局异常处理方式,用起来是比较简单的。首先新增一个类,增加@RestControllerAdvice注解,该注解的作用花哥上面已经介绍过,就不再唠叨了。

@RestControllerAdvice
public class CustomerExceptionHandler {
}

如果我们有想要拦截的异常类型,就新增一个方法,使用@ExceptionHandler注解修饰,注解参数为目标异常类型。

例如:controller中接口发生Exception异常时,就会进入到Execption方法中进行捕获,将杂乱的异常信息,转换成指定格式后交给ResponseAdvice方法进行统一格式封装并返回给前端小伙伴。

@RestControllerAdvice
@Slf4j
public class CustomerExceptionHandler {
    @ExceptionHandler(AuthException.class)
    public String ErrorHandler(AuthorizationException e) {
        log.error("没有通过权限验证!", e);
        return "没有通过权限验证!";
    }
​
    @ExceptionHandler(Exception.class)
    public Result Execption(Exception e) {
        log.error("未知异常!", e);
        return Result.error(ResultMsgEnum.SERVER_BUSY.getCode(),ResultMsgEnum.SERVER_BUSY.getMessage());
    }
}

再次调用接口getUserName查看返回结果,会发现还是有一些问题,因为我们在CustomerExceptionHandler中已经将接口返回结果封装成Result类型,而代码执行到统一结果返回类ResponseAdvice时,又会结果再次封装,就出现了如下问题。

{
    "code": 0,
    "message": "成功",
    "data": {
        "code": 503,
        "message": "服务器正忙,请稍后再试!",
        "data": null
    }
}

3.4 统一返回结果处理类最终版

解决上述问题非常简单,只要在beforeBodyWrite中增加一条判断即可。

@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
    @Autowired
    private ObjectMapper objectMapper;
​
    /**
     * 是否开启功能 true:开启
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }
​
    /**
     * 处理返回结果
     */
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        //处理字符串类型数据
        if(o instanceof String){
            try {
                return objectMapper.writeValueAsString(Result.success(o));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        //返回类型是否已经封装
        if(o instanceof Result){
            return o;
        }
        return Result.success(o);
    }
}

至此,本章的任务就全部讲完,上述代码可以直接引用,不需要其他的配置项,非常推荐引用到自己的项目中。

四、总结

本章讲解的内容不是很多,主要就两个类的配置,即处理统一返回结果的类ResponseAdvice和异常处理类CustomerExceptionHandler。全文围绕RestControllerAdviceResponseBodyAdvice的使用,通过一步步的迭代,最终构建一套通用的代码代码返回格式,更多关于SpringBoot接口返回异常处理的资料请关注脚本之家其它相关文章!

相关文章

  • django 递归查询评论的示例详解

    django 递归查询评论的示例详解

    文章介绍了将表数据转换为树状结构以及如何处理扁平化数据的方法,探讨了在数据管理中的应用和实现技术,感兴趣的朋友一起看看吧
    2025-01-01
  • Spring Boot 2 整合 QuartJob 实现定时器实时管理功能

    Spring Boot 2 整合 QuartJob 实现定时器实时管理功能

    Quartz是一个完全由java编写的开源作业调度框架,形式简易,功能强大。接下来通过本文给大家分享Spring Boot 2 整合 QuartJob 实现定时器实时管理功能,感兴趣的朋友一起看看吧
    2019-11-11
  • idea远程调试spark的步骤讲解

    idea远程调试spark的步骤讲解

    今天小编就为大家分享一篇关于idea远程调试spark的步骤讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • 通过JDK源码分析关闭钩子详解

    通过JDK源码分析关闭钩子详解

    一个简单的关闭钩子,程序被中断或者正常退出时会显示 hook shutdown!非常的优雅,有效,巧妙。那么这篇文章就来给大家介绍关于通过JDK源码分析关闭钩子的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴。
    2017-11-11
  • Java中的递增i++与++i的实现原理详解

    Java中的递增i++与++i的实现原理详解

    这篇文章主要介绍了Java中的i++与++i的实现原理详解,在Java中,i++是一种常见的递增操作符,用于将变量i的值增加1,它是一种简洁且方便的方式来实现循环和计数功能,i++可以用于各种情况,本文来看一下其实现原理,需要的朋友可以参考下
    2023-10-10
  • springboot 整合表达式计算引擎 Aviator 使用示例详解

    springboot 整合表达式计算引擎 Aviator 使用示例详解

    本文详细介绍了Google Aviator 这款高性能、轻量级的 Java 表达式求值引擎,并通过详细的代码操作演示了相关API的使用以及如何在springboot项目中进行集成,感兴趣的朋友一起看看吧
    2024-08-08
  • Java实现实时监控目录下文件变化的方法

    Java实现实时监控目录下文件变化的方法

    今天小编就为大家分享一篇关于Java实现实时监控目录下文件变化的方法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • 详谈Map的key、value值的数据类型不能为基本类型的原因

    详谈Map的key、value值的数据类型不能为基本类型的原因

    这篇文章主要介绍了详谈Map的key、value值的数据类型不能为基本类型的原因,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • SpringBoot使用@ControllerAdvice全局异常处理

    SpringBoot使用@ControllerAdvice全局异常处理

    这篇文章主要介绍了SpringBoot使用@ControllerAdvice全局异常处理,异常处理是非常重要的一部分,它可以帮助我们捕获并处理应用程序中出现的异常情况,提高应用程序的健壮性和可靠性,需要的朋友可以参考下
    2023-07-07
  • 利用session实现简单购物车功能

    利用session实现简单购物车功能

    这篇文章主要为大家详细介绍了利用session实现简单购物车功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02

最新评论