SpringBoot统一接口返回和全局异常处理

现在大多数web项目基本都是前后端分离模式,这种模式会涉及到一个前后端对接问题,所以一套完善且规范的接口是非常有必要的,不仅能够提高对接效率,也可以让我的代码看起来更加简洁优雅。本文将解决如何在基于SpringBoot前后端分离开发模式下返回统一的标准格式以及处理全局异常。

一、为什么要返回统一格式

如果SpringBoot不使用统一返回格式,默认会有如下三种返回情况。

1.1 返回字符串

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

调用接口返回结果:

Hello

1.2 返回实体类对象

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

调用接口返回结果:

{
  "name": "Hello",
  "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 定义返回格式

一个标准的返回格式至少包含3部分:

code: 状态码
message: 接口调用的提示信息
data: 返回数据

2.2 定义返回对象

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);
    }
}

2.3 定义状态码

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;
    }
}

2.4 统一返回格式

上面定义了数据返回格式和状态码,下面在接口中使用。

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

返回结果如下,可以看到返回结果与在Result中定义的参数类型相同。

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

通过在Controller层调用 ResultData.success()对返回结果进行包装后返回给前端,虽然能够满足日常需求,但是当有大量的接口时,每一个接口中都使用Result.success()来包装返回信息就会增加很多重复代码。

三、高级封装

下面将进行二次封装,只需要借助SpringBoot提供的ResponseBodyAdvice即可。

3.1 ResponseBodyAdvice类介绍

ResponseBodyAdvice: 该接口是SpringMVC 4.1提供的,它允许在 执行
@ResponseBody后自定义返回数据,用来封装统一数据格式返回;拦截Controller方法的返回值,统一处理返回值/响应体,一般用来统一返回格式,加解密,签名等

先来看下 ResponseBodyAdvice的源码:

public interface ResponseBodyAdvice<T> {
		/**
		* 是否支持advice功能
		* true 支持,false 不支持
		*/
    boolean supports(MethodParameter var1, Class<? extends HttpMessageConverter<?>> var2);

	  /**
		* 对返回的数据进行处理
		*/
    @Nullable
    T beforeBodyWrite(@Nullable T var1, MethodParameter var2, MediaType var3, Class<? extends HttpMessageConverter<?>> var4, ServerHttpRequest var5, ServerHttpResponse var6);
}

@RestControllerAdvice: 该注解是Controller的增强版,可以全局捕获抛出的异常,全局数据绑定,全局数据预处理。

3.2 用法说明

新建ResponseAdvice类;
该类用于统一封装controller中接口的返回结果。
实现ResponseBodyAdvice接口,实现supports、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) {
        //处理字符串类型数据,如果Controller返回String的话,SpringBoot是直接返回.
        if(o instanceof String){
            try {
                return objectMapper.writeValueAsString(Result.success(o));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return Result.success(o);
    }
}

测试后发现和直接使用Result返回的结果是一致的,这样就再也不需要在Controller层通过 ResultData.success()来进行转换了,直接返回原始数据格式,SpringBoot会自动帮我们进行封装。

但是这里还存在一个问题,在ResponseAdvice我们全部使用了Result.success(o)来处理结果,对于error类型的结果未做处理。那么当发生异常情况时会发生什么情况?测试结果如下:

{
    "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,不过这种方式会导致大量代码重复,维护困难等问题,这里不用手写try…catch,由全局异常处理器统一捕获、
对于自定义异常,只能通过全局异常处理器来处理,使用全局异常处理器最大的便利就是程序员在写代码时不再需要手写 try…catch了。

@GetMapping("error1")
public void empty(){
	throw  new RuntimeException("自定义异常");
}

当我们引入Validator参数校验器的时候,参数校验不通过会抛出异常,此时是无法用 try…catch捕获的,只能使用全局异常处理器。


3.4 实现全局异常处理

使用的全局异常处理也是比较简单的。首先新增一个类,增加@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.5 统一返回结果处理类最终版

解决上述问题非常简单,只要在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);
    }
}

至此,SpringBoot统一接口返回和全局异常处理就封装好了,可直接引用到项目中。


感谢大家的耐心阅读,如有建议请私信或评论留言

  • 11
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring Boot 中,我们可以使用统一返回处理来规范 API 接口返回格式,避免重复的代码和降低维护成本。 一般来说,我们可以定义一个 Result 类来封装接口返回的数据,包括状态码、提示信息和返回数据等。同时,我们还可以定义一个异常处理类来捕获全局异常,并将异常信息封装到 Result 类中返回给前端。 以下是一个示例代码: ```java @Data @NoArgsConstructor @AllArgsConstructor public class Result<T> { private Integer code; private String msg; private T data; public static <T> Result<T> success(T data) { return new Result<>(200, "success", data); } public static Result<?> error(Integer code, String msg) { return new Result<>(code, msg, null); } } @ControllerAdvice @ResponseBody public class GlobalExceptionHandler { @ExceptionHandler(value = Exception.class) public Result<?> handleException(Exception e) { log.error("未知异常:{}", e.getMessage()); return Result.error(500, "服务器内部错误"); } @ExceptionHandler(value = BusinessException.class) public Result<?> handleBusinessException(BusinessException e) { log.error("业务异常:{}", e.getMessage()); return Result.error(e.getCode(), e.getMessage()); } } ``` 在上面的代码中,我们定义了一个 Result 类来封装接口返回的数据。其中,success 方法表示成功时返回的结果,error 方法表示失败时返回的结果。 同时,我们还定义了一个全局异常处理器 GlobalExceptionHandler,用来捕获全局异常。在 handleException 和 handleBusinessException 方法中,我们分别处理了未知异常和业务异常,并将异常信息封装到 Result 类中返回给前端。 通过使用统一返回处理,我们可以大大简化接口返回的代码,提高代码的可维护性和可读性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值