Spring Boot实现全局异常处理
在项目开发中出现异常时很平常不过的事情,我们处理异常也有很多种方式,可能如下:
public int test(int a ,int b){
int c=0;
try{
c=a/b;
}catch (Exception ex){
ex.printStackTrace();
}
return c;
}
如果我们这样处理异常,代码中就会出现特别多的异常处理模块,这样代码就会变得可读性非常差,而且业务模块逻辑会夹杂特别多的非业务逻辑。但是在项目开发的过程中我们应该将主要精力放在业务模块,除了必要的异常处理模块最好不要再包含其他无关紧要的代码。那么我们如何处理项目中无处不在的异常呢?这就引出了我们要介绍的全局异常处理方法,主要有两种种方式:
- HandlerExceptionResolver。
- @RestControllerAdvice+@ExceptionHandler
今天我们主要介绍一下@RestControllerAdvice+@ExceptionHandler模式处理全局异常。
全局异常处理
首先我们先介绍一下@RestControllerAdvice和@ExceptionHandler
- @RestControllerAdvice注解:他是一个比较特殊的@Component,用于定义全局异常处理类作用在所有的@RestController类型的接口上。(和@ControllerAdvice注解作用类似)
- @ExceptionHandler注解:用于声明处理异常的方法
配置全局异常
@RestControllerAdvice+@ExceptionHandler只要设计得当,就不需要在Controller层使用trg-catch了!下面我们先写介绍一个Controller层全局异常处理类。
package com.alex.advice;
import com.alex.util.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
/**
* @program: springboot-exception
* @description: 全局异常处理类
* @author: <a href="https://sunalex.cn">Alex</a>
* @create: 2023-08-04 09:19
**/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler(value = Exception.class)
public Result<String> exception(HttpServletRequest request, Exception e){
Result<String> result = new Result<>();
result.setCode(-1);
result.setMessage("系统错误");
result.setData(e.getMessage() + " " + request.getRequestURL());
log.error("系统错误,错误类型:{}",e.getMessage());
log.error("系统错误,错误地址:{}",request.getRequestURL());
return result;
}
}
至此 , 一个简单的全局异常处理解决方式就完成了,这只是一个简单的异常处理方式,远不能达到完整项目中全局异常处理的方案。
注 : Result
是自定义的全局统一返回结果集
全局异常处理的完善
项目中业务繁杂 , 可以通过自定义的异常知道哪一个模块发生了异常 , 并且不同的业务模块也有不同的异常处理方式,这也方便我们做扩展
package com.alex.exception;
import lombok.Data;
/**
* @program: springboot-exception
* @description: 自定义异常类
* @author: <a href="https://sunalex.cn">Alex</a>
* @create: 2023-08-04 09:46
**/
@Data
public class BaseException extends RuntimeException {
private String code;
private String msg;
public BaseException() {
super();
}
public BaseException(String msg) {
super(msg);
this.msg = msg;
}
public BaseException(String code, String msg) {
super(msg);
this.code = code;
this.msg = msg;
}
public BaseException(String msg, Throwable e) {
super(msg, e);
this.msg = msg;
}
public BaseException(String code, String msg, Throwable e) {
super(msg, e);
this.code = code;
this.msg = msg;
}
public BaseException(Throwable e) {
super(e);
}
}
加入自定义异常处理
package com.alex.advice;
import com.alex.exception.BaseException;
import com.alex.util.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
/**
* @program: springboot-exception
* @description: 全局异常处理类
* @author: <a href="https://sunalex.cn">Alex</a>
* @create: 2023-08-04 09:19
**/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdvice {
/**
* 处理所有不可知的异常
* @param request 请求
* @param e 异常
* @return Result
*/
@ExceptionHandler(value = Exception.class)
public Result<String> exception(HttpServletRequest request, Exception e){
Result<String> result = new Result<>();
result.setCode(-1);
result.setMessage("系统错误");
result.setData(e.getMessage() + " " + request.getRequestURL());
log.error("系统错误,错误类型:{}",e.getMessage());
log.error("系统错误,错误地址:{}",request.getRequestURL());
return result;
}
/**
* 自定义异常处理
* @param request 请求
* @param e 异常
* @return Result
*/
@ExceptionHandler(value = BaseException.class)
public Result<String> baseException(HttpServletRequest request, BaseException e){
if (e.getCode() == null) {
return Result.error(e.getMessage() + " " + request.getRequestURL());
}
return Result.error("错误编码为" + e.getCode());
}
}
处理 Controller 数据绑定、数据校验的异常
在用户登录Model字段上注解数据校验规则。
package com.alex.entity;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* @program: springboot-exception
* @description: 用户登陆实体类
* @author: <a href="https://sunalex.cn">Alex</a>
* @create: 2023-08-04 10:32
**/
@Data
public class UserLogin {
@NotEmpty(message = "用户名不能为空")
private String username;
@NotNull(message = "密码不能为空")
private String password;
}
SpringBoot中可以使用@Validated + @RequestBody注解方式实现数据绑定和数据校验。例如登录方式为:
package com.alex.controller;
import com.alex.entity.UserLogin;
import com.alex.util.Result;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* @program: springboot-exception
* @description: 用户控制器
* @author: <a href="https://sunalex.cn">Alex</a>
* @create: 2023-08-04 10:33
**/
@RestController
public class UserController {
@PostMapping("/login")
public Result<?> login(@RequestBody @Validated UserLogin user){
// 这里仅作模拟 测试Validated功能
if ("123456".equals(user.getPassword()) && "admin".equals(user.getUsername())){
return Result.ok("登录成功");
}
return Result.error("登录失败,用户名或密码错误");
}
}
如果数据校验不对数据抛出的异常为MethodArgumentNotValidException,所以我们可以在全局异常处理类中添加对MethodArgumentNotValidException异常的处理声明,就可以实现全局处理数据校验和绑定的异常了,实现如下:
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Result<String> methodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e){
log.error("参数校验异常:{}" , e.getMessage());
BindingResult bindingResult = e.getBindingResult();
String message = null;
if (bindingResult.hasErrors()){
FieldError fieldError = bindingResult.getFieldError();
if (fieldError != null){
message = fieldError.getField() + fieldError.getDefaultMessage();
}
}
return Result.error(message + " " + request.getRequestURL());
}
通过上面介绍的未知异常、数据校验和自定义全局异常所有的Controller层的异常处理方式全部都集中到了GlobalExceptionHandler类中,那么我们在Controller类中就不再需要收到记录错误了。
总结
今天主要讲解了@RestControllerAdvice+@ExceptionHandler进行统一的在Controller层上的全局异常处理。详细代码可在Gitee了解