Springboot封装全局异常处理及返回结果

534 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情

前后端分离项目中,前端通常会使用统一封装的请求组件来做请求,同时在这个组件内进行统一的请求前、响应后处理,简化前端请求逻辑.

一般情况下,对于后端所有请求都要求有统一的返回格式,方便前端处理.

统一返回结果

借助springmvc的一个注解和一个增强类

  1. @RestControllerAdvice
  2. ResponseBodyAdvice

@RestControllerAdvice

看下结构:

本质上其实是@ControllerAdvice与@ResponseBody的结合体,顾名思义就是针对与返回json数据的.

ControllerAdvice其实就是springmvc提供的针对controller的增强类,使用此注解的类将作用于所有的Controller上,即所有的有RequestMapping注解的方法.

ResponseBodyAdvice

看下结构

提供了两个方法:

  1. supports
    1. 从名字就可以看出来,这个方法就是用来判断过来的请求是否要走这个增强. 简单的说就是返回true执行beforeBodyWrite方法,返回false不执行
  1. beforeBodyWrite
    1. 在这个方法内部进行返回内容的封装等操作.

简单原理概述:

  1. 启动后,扫描带有@ControllerAdvice注解的bean,放入RequestResponseBodyAdviceChain中
  2. 在请求到达后和请求结束后转换json数据时,选择合适的advice进行增强

我们主要使用beforeBodyWrite方法,即请求结束后增强响应

使用

package com.zy.common.advice;

import com.zy.common.model.response.ResponseResult;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * @Author: Zy
 * @Date: 2023/1/3 9:25
 */
@RestControllerAdvice
@Component
public class ResponseResultAdvice implements ResponseBodyAdvice<Object> {

    @Value("${advice.result:true}")
    Boolean resultAdviceEnabled;

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        return ResponseResult.success(body);
    }
}
  1. 使用@RestControllerAdvice增强
  2. 实现ResponseBodyAdvice接口,标识此类是一个advice增强类,同时@ResponseBodyAdvice中有@Comment注解,可以注册进入Spring
  3. supports方法中直接返回true,表示所有请求均被增强
  4. beforeBodyWrite方法中对响应内容进行包装,包装的方法自定义,返回统一格式结果即可.

ResponseResult

上文中对响应内容进行包装的方法:

package com.zy.common.model.response;

import lombok.Data;

/**
 * @Author: Zy
 * @Date: 2022/12/9 11:03
 * 统一返回结果包装类
 */
@Data
public class ResponseResult {
    private String code;
    private String msg;
    private Object data;

    public ResponseResult(ResponseCode code, String msg) {
        this.code = code.getCode();
        this.msg = msg;
    }

    public ResponseResult(ResponseCode code, String msg, Object data) {
        this.code = code.getCode();
        this.msg = msg;
        this.data = data;
    }


    public static ResponseResult success() {
        return new ResponseResult(ResponseCode.SUCCESS, "success");
    }

    public static ResponseResult success(String msg) {
        return new ResponseResult(ResponseCode.SUCCESS, msg);
    }

    public static ResponseResult success(String msg, Object data) {
        return new ResponseResult(ResponseCode.SUCCESS, msg, data);
    }

    public static ResponseResult success(Object data) {
        return new ResponseResult(ResponseCode.SUCCESS, "success", data);
    }

    public static ResponseResult error() {
        return new ResponseResult(ResponseCode.ERROR, "error");
    }

    public static ResponseResult error(String msg) {
        return new ResponseResult(ResponseCode.ERROR, msg);
    }

    public static ResponseResult error(Object data) {
        return new ResponseResult(ResponseCode.ERROR, "error", data);
    }

    public static ResponseResult error(String msg, Object data) {
        return new ResponseResult(ResponseCode.ERROR, msg, data);
    }
}

可以自定义格式,再对外暴露几个简单方法即可.

统一异常处理

统一异常初始同样基于@RestControllerAdvice,区别是不用实现ResponseBodyAdvice接口,而是改用@ExceptionHandler注解.

@ExceptionHandler

与@RestControllerAdvice配套使用,只有一个属性value,用于表示用此注解修饰的方法用来捕获什么类型的异常

使用

package com.zy.common.advice;

import com.zy.common.model.response.ResponseResult;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.lang.annotation.Annotation;

/**
 * @Author: Zy
 * @Date: 2023/1/3 11:18
 */
@RestControllerAdvice
public class ExceptionHandlerAdvice {

    @ExceptionHandler(Exception.class)
    public ResponseResult handleException(Exception e){
        e.printStackTrace();
        return ResponseResult.error(e.getMessage());
    }



}

简单的直接捕获Exception,可以分为多种异常,或者捕获自定义异常,进行特殊操作.

冲突

统一异常处理与统一返回结果存在冲突,因为所有请求都会经过统一返回结果处理,如果出现了异常,请求就会被增强两次,导致响应体被包装两次.

解决办法:

在统一返回结果的supports中增加过滤,如果是已经被包装过的结果就不再进行二次包装:

package com.zy.common.advice;

import com.zy.common.model.response.ResponseResult;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * @Author: Zy
 * @Date: 2023/1/3 9:25
 */
@RestControllerAdvice
@Component
public class ResponseResultAdvice implements ResponseBodyAdvice<Object> {

    @Value("${advice.result:true}")
    Boolean resultAdviceEnabled;

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 如果增加类未启用 或者 返回的类型已经是结果类了 就不再进行封装
        return resultAdviceEnabled && !(returnType.getParameterType() == ResponseResult.class);
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        return ResponseResult.success(body);
    }
}