统一全局异常处理


统⼀全局异常处理

1.设计⼀个优秀的异常处理机制

⼀、异常处理的乱象例举

乱象⼀:捕获异常后只输出到控制台

前端 js-ajax 代码

$.ajax({
  type: "GET",
  url: "/user/add",
  dataType: "json",
  success: function (data) {
    alert("添加成功");
  },
});

后端业务代码

try {
    // do something
} catch (XyyyyException e) {
    e.printStackTrace();
}

问题:

  1. 后端直接将异常捕获,⽽且只做了⽇志打印。⽤户体验⾮常差,⼀旦后台出错,⽤户没有任何感知, ⻚⾯⽆状态。
  2. 后端只给出前端异常结果,没有给出异常的原因的描述。⽤户不知道是⾃⼰操作输⼊错误,还是系统 bug。⽤户⽆法判断⾃⼰需要等⼀下再操作?还是继续下⼀步?
  3. 如果没有⼈去经常关注服务端⽇志,不会有⼈发现系统出现异常。

乱象⼆:混乱的返回⽅式

前端代码

$.ajax({
    type: "GET",
    url: "/goods/add",
    dataType: "json",
    success: function(data) {
        if (data.flag) {
            alert("添加成功");
       } else {
            alert(data.message);
       }
   },
    error: function(data){
        alert("添加失败");
   }
});

后端代码

@RequestMapping("/goods/add")
@ResponseBody
public Map add(Goods goods) {
    Map map = new HashMap();
    try {
        // do something
        map.put(flag, true);
    } catch (Exception e) {
        e.printStackTrace();
        map.put("flag", false);
        map.put("message", e.getMessage());
    }
    reutrn map;
}

问题:

  1. 每个⼈返回的数据有每个⼈⾃⼰的规范,你叫 flag 他叫 isOK,你的成功 code 是 0,它的成功 code 是 0000。这样导致后端书写了⼤量的异常返回逻辑代码,前端也随之每⼀个请求⼀套异常处理逻辑。 很多重复代码。
  2. 如果是前端后端⼀个⼈开发还勉强能⽤,如果前后端分离,这就是系统灾难。

⼆、该如何设计异常处理

⾯向相关⽅友好

  1. 后端开发⼈员职责单⼀,只需要将异常捕获并转换为⾃定义异常⼀直对外抛出。不需要去想⻚⾯跳转 404,以及异常响应的数据结构的设计。
  2. ⾯向前端⼈员友好,后端返回给前端的数据应该有统⼀的数据结构,统⼀的规范。不能⼀个⼈⼀个响 应的数据结构。⽽在此过程中不需要后端开发⼈员做更多的⼯作,交给全局异常处理器去处理“异 常”到“响应数据结构”的转换。
  3. ⾯向⽤户友好,⽤户能够清楚的知道异常产⽣的原因。这就要求⾃定义异常,全局统⼀处理,ajax 接 ⼝请求响应统⼀的异常数据结构,⻚⾯模板请求统⼀跳转到 404 ⻚⾯。
  4. ⾯向运维友好,将异常信息合理规范的持久化,以⽇志的形式存储起来,以便查询。

为什么要将系统运⾏时异常捕获,转换为⾃定义异常抛出?

因为⽤户不认识 ConnectionTimeOutException 类似这种异常是什么东⻄,但是转换为⾃定义异常就要求 程序员对运⾏时异常进⾏⼀个翻译,⽐如:⾃定义异常⾥⾯应该有 message 字段,后端程序员应该明确 的在 message 字段⾥⾯⽤⾯向⽤户的友好语⾔,说明服务端发⽣了什么。

三、开发规范

  1. Controller、Service、DAO 层拦截异常转换为⾃定义异常,不允许将异常私⾃截留。必须对外抛出。
  2. 统⼀数据响应代码,使⽤ http 状态码,不要⾃定义。⾃定义不⽅便记忆,HTTP 状态码程序员都知道。但是太多了程序员也记不住,在项⽬组规定范围内使⽤⼏个就可以。⽐如:200 请求成功,400 ⽤户输⼊错误导致的异常,500 系统内部异常,999 未知异常。
  3. ⾃定义异常⾥⾯有 message 属性,⽤对⽤户友好的语⾔描述异常的发⽣情况,并赋值给 message。
  4. 不允许对⽗类 Exception 统⼀ catch,要分⼩类 catch,这样能够清楚地将异常转换为⾃定义异常传递给前端。

2.⾃定义异常和相关数据结构

⼀、该如何设计数据结构

  1. CustomException ⾃定义异常。核⼼要素包含异常错误编码(400,500)、异常错误信息 message。
  2. ExceptionTypeEnum 枚举异常分类,将异常分类固化下来,防⽌开发⼈员思维发散。
  3. AjaxResponse ⽤于响应 HTTP 请求的统⼀数据结构。

⼆、枚举异常的类型

为了防⽌开发⼈员⼤脑发散,每个开发⼈员都不断的发明⾃⼰的异常类型,我们需要规定好异常的类型 (枚举)。⽐如:系统异常、⽤户(输⼊)操作导致的异常、其他异常等。

package top.syhan.boot.exception.enums;
/**
* @description: 异常类型枚举
* @author: syhan
* @date: 2022-04-11
**/
public enum CustomExceptionType {
    /**
     * 客户端异常
     */
    USER_INPUT_ERROR(400, "您输⼊的数据错误或您没有权限访问资源!"),

    /**
     * 服务器异常
     */
    SYSTEM_ERROR(500, "系统出现异常,请您稍后再试或联系管理员!"),

    /**
     * 未知异常
     */
    OTHER_ERROR(999, "系统出现未知异常,请联系管理员!");

    CustomExceptionType(int code, String desc) {
      	this.code = code;
      	this.desc = desc;
    }

    /**
     * 异常类型状态码
     */
    private final int code;

    /**
     * 异常类型中⽂描述
     */
    private final String desc;

    public String getDesc() {
		return desc;
    }

    public int getCode() {
      	return code;
    }
}
  • 最好不要超过 5 个,否则开发⼈员将会记不住,也不愿意去记。
  • 这⾥的 code 表示异常类型的唯⼀编码,为了⽅便⼤家记忆,就使⽤ Http 状态码 400、500。
  • 这⾥的 desc 是通⽤的异常描述,在创建⾃定义异常的时候,为了给⽤户更友好的回复,通常异常信 息描述应该更具体更友好。

三、⾃定义异常

  • ⾃定义异常有两个核⼼内容,⼀个是 code。使⽤ CustomExceptionType 来限定范围。
  • 另外⼀个是 message,这个 message 信息是要最后返回给前端的,所以需要⽤友好的提示来表达异常发⽣的原因或内容
package top.syhan.boot.exception.exception;
import top.syhan.boot.exception.enums.CustomExceptionType;
/**
* @description: ⾃定义异常
* @author: syhan
* @date: 2022-04-11
**/
public class CustomException extends RuntimeException {
    /**
     * 异常错误编码
     */
    private int code;

    /**
     * 异常信息
     */
    private String message;

    private CustomException() {
    }

    public CustomException(CustomExceptionType customExceptionType) {
        this.code = customExceptionType.getCode();
        this.message = customExceptionType.getDesc();
    }

    public CustomException(CustomExceptionType customExceptionType, String message) {
        this.code = customExceptionType.getCode();
        this.message = message;
    }

    public int getCode() {
        return code;
    }

	@Override
    public String getMessage() {
        return message;
    }
}

四、请求接⼝统⼀响应数据结构

为了解决不同的开发⼈员使⽤不同的结构来响应给前端,导致规范不统⼀,开发混乱的问题。我们使⽤ 如下代码定义统⼀数据响应结构。

  • isok 表示该请求是否处理成功(即是否发⽣异常)。true 表示请求处理成功,false 表示处理失败。
  • code 对响应结果进⼀步细化,200 表示请求成功,400 表示⽤户操作导致的异常,500 表示系统异 常,999 表示其他异常。与 CustomExceptionType 枚举⼀致。
  • message:友好的提示信息,或者请求结果提示信息。如果请求成功这个信息通常没什么⽤,如果 请求失败,该信息需要展示给⽤户。
  • data:通常⽤于查询数据请求,成功之后将查询数据响应给前端。
package top.syhan.boot.exception.utils;
import top.syhan.boot.exception.enums.CustomExceptionType;
import top.syhan.boot.exception.exception.CustomException;
import lombok.Data;
/**
* @description: 请求接⼝统⼀响应数据结构
* @author: syhan
* @date: 2022-04-11
**/
@Data
public class AjaxResponse {
    /**
     * 请求响应状态码
     */
    private int code;

    /**
     * 请求结果描述信息
     */
    private String message;

    /**
     * 请求结果数据(通常⽤于查询操作)
     */
    private Object data;

    private AjaxResponse() {
    }

    /**
     * 请求出现异常时的响应数据封装
     *
     * @param e e
     * @return AjaxResponse
     */
    public static AjaxResponse error(CustomException e) {
        AjaxResponse resultBean = new AjaxResponse();
        resultBean.setCode(e.getCode());
        resultBean.setMessage(e.getMessage());
        return resultBean;
    }

    /**
     * 请求出现异常时的响应数据封装
       * @param customExceptionType customExceptionType
     * @param errorMessage       errorMessage
     * @return AjaxResponse
     */
    public static AjaxResponse error(CustomExceptionType customExceptionType, String errorMessage) {
        AjaxResponse resultBean = new AjaxResponse();
        resultBean.setCode(customExceptionType.getCode());
        resultBean.setMessage(errorMessage);
        return resultBean;
    }

    /**
     * 请求成功的响应,不带查询数据(⽤于删除、修改、新增接⼝)
     *
     * @return AjaxResponse
     */
    public static AjaxResponse success() {
        AjaxResponse ajaxResponse = new AjaxResponse();
        ajaxResponse.setCode(200);
        ajaxResponse.setMessage("请求响应成功!");
        return ajaxResponse;
    }

    /**
     * 请求成功的响应,带有查询数据(⽤于数据查询接⼝)
     *
     * @param obj obj
     * @return AjaxResponse
     */
    public static AjaxResponse success(Object obj) {
        AjaxResponse ajaxResponse = new AjaxResponse();
        ajaxResponse.setCode(200);
        ajaxResponse.setMessage("请求响应成功!");
        ajaxResponse.setData(obj);
        return ajaxResponse;
    }

    /**
     * 请求成功的响应,带有查询数据(⽤于数据查询接⼝)
     *
     * @param obj     obj
     * @param message message
     * @return AjaxResponse
     */
    public static AjaxResponse success(Object obj, String message) {
        AjaxResponse ajaxResponse = new AjaxResponse();
        ajaxResponse.setCode(200);
        ajaxResponse.setMessage(message);
        ajaxResponse.setData(obj);
        return ajaxResponse;
   }
}

对于不同的场景,提供了四种构建 AjaxResponse 的⽅法。

  • 当请求成功的情况下,可以使⽤ AjaxResponse.success()构建返回结果给前端。
  • 当查询请求等需要返回业务数据,请求成功的情况下,可以使⽤ AjaxResponse.success(data)构建 返回结果给前端。携带结果数据。
  • 当请求处理过程中发⽣异常,需要将异常转换为 CustomException ,然后在控制层使⽤ AjaxResponse error(CustomException)构建返回结果给前端。
  • 在某些情况下,没有任何异常产⽣,我们判断某些条件也认为请求失败。这种使⽤ AjaxResponse error(customExceptionType,errorMessage)构建响应结果。

五、使⽤示例如下:

例如:更新操作,Controller ⽆需返回额外的数据

return AjaxResponse.success();

查询接⼝,Controller 需返回结果数据(data 可以是任何类型数据)

return AjaxResponse.success(data);

3.通⽤全局异常处理逻辑

⼀、通⽤异常处理逻辑

程序员的异常处理逻辑要⼗分的单⼀:⽆论在 Controller 层、Service 层还是什么其他位置,程序员只负 责⼀件事:那就是捕获异常,并将异常转换为⾃定义异常。使⽤⽤户友好的信息去填充。

CustomException 的 message,并将 CustomException 抛出去。

package top.syhan.boot.exception.service;
import top.syhan.boot.exception.consts.MsgConsts;
import top.syhan.boot.exception.enums.CustomExceptionType;
import top.syhan.boot.exception.exception.CustomException;
import org.springframework.stereotype.Service;
/**
* @description: 通⽤异常处理逻辑
* @author: syhan
* @date: 2022-04-11
**/
@Service
public class ExceptionService {
    /**
     * 服务层,模拟系统异常
     */
    public void systemBizError() {
        try {
            Class.forName("com.mysql.jdbc.cj.Driver");
       } catch (ClassNotFoundException e) {
            throw new CustomException(
                    CustomExceptionType.SYSTEM_ERROR,
"在XXX业务,myBiz()⽅法内,出现ClassNotFoundException,请
将该信息告知管理员");
       }
   }
    /**
     * 服务层,模拟⽤户输⼊数据导致的校验异常
     *
     * @param input ⽤户输⼊
     */
    public void userBizError(int input) {
        //模拟业务校验失败逻辑
        if (input < 0) {
            throw new CustomException(CustomExceptionType.USER_INPUT_ERROR, MsgConsts.INPUT_ERROR);
       }
   }
}

⼆、全局异常处理器

通过团队内的编码规范的要求,我们已经知道了:不允许程序员截留处理 Exception,必须把异常转换为 ⾃定义异常 CustomException 全都抛出去。那么程序员把异常跑出去之后由谁来处理?那就是 ControllerAdvice。 ControllerAdvice 注解的作⽤就是监听所有的 Controller,⼀旦 Controller 抛出 CustomException,就会 在@ExceptionHandler(CustomException.class)注解的⽅法⾥⾯对该异常进⾏处理。处理⽅法很简单就是使⽤ AjaxResponse.error(e)包装为通⽤的接⼝数据结构返回给前端

package top.syhan.boot.exception.handler;
import top.syhan.boot.exception.enums.CustomExceptionType;
import top.syhan.boot.exception.exception.CustomException;
import top.syhan.boot.exception.utils.AjaxResponse;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @description: 全局异常处理器
* @author: syhan
* @date: 2022-04-11
**/
@ControllerAdvice
public class WebExceptionHandler {
    /**
     * 处理程序员主动转换的⾃定义异常
     *
     * @param e 异常
     * @return AjaxResponse
     */
    @ExceptionHandler(CustomException.class)
    @ResponseBody
    public AjaxResponse customerException(CustomException e) {
        if (e.getCode() == CustomExceptionType.SYSTEM_ERROR.getCode()) {
            //400异常不需要持久化,将异常信息以友好的⽅式告知⽤户就可以
            //将500异常信息持久化处理,⽅便运维⼈员处理
       }
        return AjaxResponse.error(e);
    }

    /**
     * 处理程序员在程序中未能捕获(遗漏的)异常
     *
     * @param e 异常
     * @return AjaxResponse
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public AjaxResponse exception(Exception e) {
	  //TODO 将异常信息持久化处理,⽅便运维⼈员处理
        return AjaxResponse.error(new CustomException(CustomExceptionType.OTHER_ERROR));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public AjaxResponse handleBindException(MethodArgumentNotValidException ex) {
        FieldError fieldError = ex.getBindingResult().getFieldError();
        assert fieldError != null;
        return AjaxResponse.error(new CustomException(CustomExceptionType.USER_INPUT_ERROR, fieldError.getDefaultMessage()));
    }

    @ExceptionHandler(BindException.class)
    @ResponseBody
    public AjaxResponse handleBindException(BindException ex) {
        FieldError fieldError = ex.getBindingResult().getFieldError();
        assert fieldError != null;
        return AjaxResponse.error(new CustomException(CustomExceptionType.USER_INPUT_ERROR, fieldError.getDefaultMessage()));
    }

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseBody
    public AjaxResponse handleIllegalArgumentException(IllegalArgumentException e) {
        return AjaxResponse.error(new CustomException(CustomExceptionType.USER_INPUT_ERROR, e.getMessage()));
    }
}

三、测试⼀下

随便找⼀个 API,注⼊ ExceptionService 访问测试⼀下

四、业务状态与 HTTP 协议状态⼀致

不知道⼤家有没有注意到⼀个问题(看上图)?这个问题就是我们的 AjaxResponse 的 code 是 400,但是真正的 HTTP 协议状态码是 200。

  • AjaxResponse 的 code 是 400 代表的是业务状态,也就是说⽤户的请求业务失败了
  • 但是 HTTP 请求是成功的,也就是说数据是正常返回的。

在很多的公司开发 RESTful 服务时,要求 HTTP 状态码能够体现业务的最终执⾏状态,所以说:我们有必要让业务状态与 HTTP 协议 Response 状态码⼀致。

@Component
@ControllerAdvice
public class GlobalResponseAdvice implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        //return returnType.hasMethodAnnotation(ResponseBody.class);
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        //如果响应结果是JSON数据类型
        if(selectedContentType.equalsTypeAndSubtype(
                MediaType.APPLICATION_JSON)){
                //为HTTP响应结果设置状态码,状态码就是AjaxResponse的code,⼆者达到统⼀
                response.setStatusCode(
                        HttpStatus.valueOf(((AjaxResponse) body).getCode())
               );
                return body;
        }
        return body;
    }
}
  • 实现 ResponseBodyAdvice 接⼝的作⽤是:在将数据返回给⽤户之前,做最后⼀步的处理。也就是说,ResponseBodyAdvice 的处理过程在全局异常处理的后⾯。

五、进⼀步优化

我们已经知道了,ResponseBodyAdvice 接⼝的作⽤是:在将数据返回给⽤户之前,做最后⼀步的处理。将上⽂的 GlobalResponseAdvice 中 beforeBodyWrite ⽅法代码优化如下。

  • 如果 Controller 或全局异常处理响应的结果 body 是 AjaxResponse,就直接 return 给前端。
  • 如果 Controller 或全局异常处理响应的结果 body 不是 AjaxResponse,就将 body 封装为 AjaxResponse 之后再 return 给前端。

我们之前的代码是这样写的,⽐如:某个 controller ⽅法返回值

return AjaxResponse.success(objList);

现在就可以这样写了,因为在 GlobalResponseAdvice ⾥⾯会统⼀再封装为 AjaxResponse。

return objList;

最终代码如下:

package top.syhan.boot.exception.advice;
import top.syhan.boot.exception.utils.AjaxResponse;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* @description: 全局业务状态通知
* @author: syhan
* @date: 2022-04-11
**/
@Component
@ControllerAdvice
public class GlobalResponseAdvice implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse)
    {
        //如果响应结果是JSON数据类型
        if (mediaType.equalsTypeAndSubtype(
                MediaType.APPLICATION_JSON)) {
            if (body instanceof AjaxResponse ajaxResponse) {
                //999 不是标准的HTTP状态码,特殊处理
                if (ajaxResponse.getCode() != 999) {
                    serverHttpResponse.setStatusCode(HttpStatus.valueOf(
                            ajaxResponse.getCode()
                   ));
                    }
                return body;
            } else {
                serverHttpResponse.setStatusCode(HttpStatus.OK);
                return AjaxResponse.success(body);
            }
        }
        return body;
    }
}

4.服务端数据校验异常处理逻辑

⼀、异常校验的规范及常⽤注解

在 Web 开发时,对于请求参数,⼀般上都需要进⾏参数合法性校验的,原先的写法是⼀个个字段⼀个个 去判断,这种⽅式太不通⽤了,Java 的 JSR 303: Bean Validation 规范就是解决这个问题的。 JSR 303 只是个规范,并没有具体的实现,⽬前通常是⽤ hibernate-validator 进⾏统⼀参数校验。

JSR303 定义的校验类型

Constraint 详细信息
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是⼀个数字,其值必须⼤于等于指定的最⼩值
@Max(value) 被注释的元素必须是⼀个数字,其值必须⼩于等于指定的最⼤值
@DecimalMin(value) 被注释的元素必须是⼀个数字,其值必须⼤于等于指定的最⼩值
@DecimalMax(value) 被注释的元素必须是⼀个数字,其值必须⼩于等于指定的最⼤值
@Size(max, min) 被注释的元素的⼤⼩必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是⼀个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是⼀个过去的⽇期
@Future 被注释的元素必须是⼀个将来的⽇期
@Pattern(value) 被注释的元素必须符合指定的正则表达式

Hibernate Validator 附加的 constraint

Constraint 详细信息
@Email 被注释的元素必须是电⼦邮箱地址
@Length 被注释的字符串的⼤⼩必须在指定的范围内
@NotEmpty 被注释的字符串的必须⾮空
@Range 被注释的元素必须在合适的范围内

⽤法:把以上注解加在 ArticleVO 的属性字段上,然后在参数校验的⽅法上加@Valid 注解 如:

当⽤户输⼊参数不符合注解给出的校验规则的时候,会抛出 BindException 或 MethodArgumentNotValidException。

⼆、Assert 断⾔与 IllegalArgumentException

之前给⼤家讲通⽤异常处理的时候,⽤户输⼊异常判断是这样处理的。这种⽅法也是可以⽤的,但是我 们学了这么多的知识,可以优化⼀下。

//服务层,模拟⽤户输⼊数据导致的校验异常
public void userBizError(int input) {
    if(input < 0){ //模拟业务校验失败逻辑
        throw new CustomException(
                CustomExceptionType.USER_INPUT_ERROR,
                "您输⼊的数据不符合业务逻辑,请确认后重新输⼊!");
    }

    //…… 其他的业务
}

更好的写法是下⾯这样的,使⽤ org.springframework.util.Assert 断⾔ input >= 0,如果不满⾜条件就抛 出 IllegalArgumentException,参数不合法的异常。

//服务层,模拟⽤户输⼊数据导致的校验异常
public void userBizError(int input) {
    Assert.isTrue(input >= 0,"您输⼊的数据不符合业务逻辑,请确认后重新输⼊!");
    //…… 其他的业务
}

org.springframework.util.Assert 断⾔提供了⼤量的断⾔⽅法,针对各种数据类型进⾏数据合法性校验, 使⽤它我们编写代码更⽅便。

三、友好的数据校验异常处理(⽤户输⼊异常的全局处理)

我们已知当数据校验失败的时候,会抛出异常 BindException 或 MethodArgumentNotValidException。 所以我们对这两种异常做全局处理,防⽌程序员重复编码带来困扰。

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public AjaxResponse handleBindException(MethodArgumentNotValidException ex) {
    FieldError fieldError = ex.getBindingResult().getFieldError();
    return AjaxResponse.error(new CustomException(CustomExceptionType.USER_INPUT_ERROR, fieldError.getDefaultMessage()));
}

@ExceptionHandler(BindException.class)
@ResponseBody
public AjaxResponse handleBindException(BindException ex) {
    FieldError fieldError = ex.getBindingResult().getFieldError();
    return AjaxResponse.error(new CustomException(CustomExceptionType.USER_INPUT_ERROR, fieldError.getDefaultMessage()));
}

我们已知使⽤ org.springframework.util.Assert 断⾔,如果不满⾜条件就抛出 IllegalArgumentException。可以使⽤下⾯的全局异常处理函数。

@ExceptionHandler(IllegalArgumentException.class)
@ResponseBody
public AjaxResponse
handleIllegalArgumentException(IllegalArgumentException e) {
    return AjaxResponse.error(new CustomException(CustomExceptionType.USER_INPUT_ERROR, e.getMessage())
    );
}

文章作者: Syhan
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Syhan !
评论
  目录