Java Spring Boot 自定义异常与全局异常处理
我们在对比 过滤器与拦截器 一文中,知道请求过来,各种拦截处理的顺序:
- 1.过滤器
- 2.拦截器
- 3.controllerAdvice
- 4.AOP
- 5.controller
- 6.AOP
- 7.controllerAdvice
- 8.拦截器
- 9.过滤器
今天我们学习的 自定义异常与异常处理 这块内容,恰好就是 ControllerAdvice/RestControllerAdvice
部分了。
在日常开发中,对于异常的处理我们要么是主要 try...catch...
或者是 throw new xxxException(msg)
几种方式,比如我们主要捕捉异常可以捕捉文件读写时可能存在的 IOException
等,或者是 throw Exception("这是某种异常"),但在 Spring Boot web 开发中,我们还是希望能和业务尽量结合起来,一起优雅通过统一的响应结构输出。
在捕捉异常的时候,我们往往希望 最好是具体的某种异常,再是一般的某种异常,所以这里自然就会涉及到 异常处理的顺序问题,后面我们在应用中也会提到。
接下来的学习中,主要分通用异常处理、自定义异常处理,其中内容含统一响应部分,可看上篇文档,传送门:Java Spring Boot 规范统一响应体结构。
通用异常处理
package com.example.springbootexceptiondemo.params; import lombok.extern.slf4j.Slf4j; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** * @ExceptionHandler(value = MyException.class) -- 注解类型 * @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) -- 错误码 * @ResponseBody -- 返回json * @ControllerAdvice -- 顾名思义,这是一个增强的 Controller。使用这个 Controller ,可以实现三个方面的功能: * 1.全局异常处理 * 2.全局数据绑定 * 3.全局数据预处理 */ @Slf4j @ControllerAdvice // @RestControllerAdvice = @ControllerAdvice + @ResponseBody @ResponseBody public class GlobalExceptionAdvice { /** * 通用异常 */ @ExceptionHandler(value = Exception.class) public RespInfo exception(Exception ex) { log.error("服务器异常: ", ex); return RespInfo.fail(RespCodeEnums.INNER_SEVER_ERROR.getCode(), ex.getMessage()); } }
如果我们要用到异常处理拦截,在 Spring Boot 中,只需要加上 @ControllerAdvice
注解,或者 @RestControllerAdvice
注解,二者的区别就是是否通过 Json 格式返回,或者说 @RestControllerAdvice = @ControllerAdvice + @ResponseBody
,然后在我们处理的方法中,指定通过哪种 异常类 来匹配,即 @ExceptionHandler注解,通过 value 的值指定匹配的异常类,这里我们通过 Exception类,大部分的异常类通过继承自该类。
如上面代码,一个通用的异常类就实现了。
自定义异常处理
自定义异常,首先要明确,我们这个异常的功能,这里为了演示方便,只是简单继承自 Exception类
,实际用的时候,请结合自己的项目。
定义自己的异常类
package com.example.springbootexceptiondemo.exception; public class MyException extends RuntimeException{ // 使用时传入错误信息 public MyException(String msg) { super(msg); } }
实际使用时,我们只需要通过传入 错误msg
来构造实例。
在异常处理中添加
package com.example.springbootexceptiondemo.params; import com.example.springbootexceptiondemo.exception.MyException; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @Slf4j @RestControllerAdvice public class MyExceptionAdvice { /** * 自定义异常 * @param ex * @return */ @ExceptionHandler(value = MyException.class) public RespInfo bizExceptionHandler(MyException ex) { log.error("自定义业务异常: ", ex); return RespInfo.fail(RespCodeEnums.BIZ_EXCEPTION.getCode(), ex.getMessage()); } }
注意我们此处用的注解是 @RestControllerAdvice
,我们在 @ExceptionHandler
指定相应的异常类是自定义的异常类,如果应用中哪里触发了该异常,应该就要匹配到这个异常处理的。
当然很多情况下,Java 自带的异常类已经可以满足一定的需求,比如我们有这样的业务场景,对于上传的请求参数,如果在校验validate参数时发生异常,在异常处理中,我们就可以捕捉到,进而返回响应,如下。
Java自带的异常类
package com.example.springbootexceptiondemo.params; import lombok.extern.slf4j.Slf4j; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; 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; import java.util.Objects; @Slf4j @ControllerAdvice @Order(Ordered.LOWEST_PRECEDENCE - 2) // 异常处理器顺序,越小越优先,-2保证比全局更优先 public class ArgumentsValidateAdvice { @ResponseBody @ExceptionHandler(value = MethodArgumentNotValidException.class) public RespInfo methodArgsNotValidExceptionHandler(MethodArgumentNotValidException ex) { log.error("参数异常,msg ->", ex); return RespInfo.fail(RespCodeEnums.PARAMS_NOT_VALID_ERROR.getCode(), Objects.requireNonNull(ex.getBindingResult().getFieldError().getDefaultMessage())); } }
这里其实就是用到参数校验不合规异常,相应的我们的 User bean 如下:
package com.example.springbootexceptiondemo.model; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class User { @NotBlank(message = "密码不能为空") @Size(min = 6, max = 20, message = "密码长度在 min ~ max 之间") private String password; @NotNull(message = "id不能为空") @NotBlank(message = "id不能为空") private int id; @NotBlank(message = "姓名不能为空") @Size(min = 2, max = 8, message = "username 长度在 min ~ max 之间") private String username; private int age; }
测试异常处理
结合上面的异常处理,我们编写个 controller
,一是测试通用异常,二是测试自定义的异常。
package com.example.springbootexceptiondemo.controller; import com.example.springbootexceptiondemo.exception.MyException; import com.example.springbootexceptiondemo.model.User; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/user") public class UserLoginController { @GetMapping("/test") public User testLogin() throws MyException { throw new MyException("测试自定义异常"); // 主动抛出异常 } @PostMapping("/add") public User addUser(@RequestBody @Validated User user) { return user; } @GetMapping("/haha") public Object haha() { int i = 1 / 0; return "haha"; } }
测试情况:
通用异常处理
自定义异常处理
Java自带异常类-参数校验不合规
注:在多个异常处理中,我们可以通过 @Order
注解 来限定异常的处理优先级:
Ordered.HIGHEST_PRECEDENCE
,级别最高,最优先处理,实际就是int.MIN
的值Ordered.LOWEST_PRECEDENCE
,级别最低,最后处理,实际就是int.MAX
的值
实际在用的时候如下:
@Slf4j @ControllerAdvice @Order(Ordered.HIGHEST_PRECEDENCE) public class ArgumentsValidateAdvice { @ResponseBody @ExceptionHandler(value = MethodArgumentNotValidException.class) public RespInfo methodArgsNotValidExceptionHandler(MethodArgumentNotValidException ex) { log.error("参数异常,msg ->", ex); return RespInfo.fail(RespCodeEnums.PARAMS_NOT_VALID_ERROR.getCode(), Objects.requireNonNull(ex.getBindingResult().getFieldError().getDefaultMessage())); } }
我们在用的时候,要比 Exception类
更优先,只需要调整这个 value 即可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
2021-12-14 单链表及双向链表的代码实现-golang