@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常
前言
对于与数据库相关的 Spring项目,我们通常会把 事务 配置在 Service层,当数据库操作失败时让 Service 层抛出运行时异常,控制层进行try catch处理,Spring 事物管理器就会进行回滚。
如此一来,我们的 Controller 层就不得不进行 try-catch Service 层的异常,否则会返回一些不友好的错误信息到客户端。但是,Controller 层每个方法体都写一些模板化的 try-catch 的代码,很难看也难维护,特别是还需要对 Service 层的不同异常进行不同处理的时候。
全局异常处理(搭配 @ExceptionHandler)
顾名思义,@ControllerAdvice 就是 @Controller 的增强版。@ControllerAdvice 主要用来处理全局数据,一般搭配 @ExceptionHandler 使用。下面分别进行介绍。
0、优缺点
优点:将 Controller 层的异常和数据校验的异常进行统一处理,减少模板代码,减少编码量,提升扩展性和可维护性。
缺点:只能处理 Controller 层未捕获(往外抛)的异常,对于 Interceptor(拦截器)层的异常,Spring 框架层的异常,就无能为力了。
1、基本用法
1)@ControllerAdvice 最常见的使用场景就是全局异常处理。假设我们项目中有个文件上传功能,并且对文件上传的大小限制进行了配置。
2)如果用户上传的文件超过了限制大小,就会抛出异常,此时可以通过 @ControllerAdvice 结合 @ExceptionHandler 定义全局异常捕获机制,具体代码如下:
代码说明:
GlobalExceptionHandler 类上面添加了 @ControllerAdvice 注解。当系统启动时,该类就会被扫描到 Spring 容器中。
handleException方法上面添加了 @ExceptionHandler 注解,其中定义的 Exception.class(也可以是其他Exception的子类及Exception的继承自定义类,如NullPointerException.class) 表明该方法用来处理 Exception(NullPointerException)类型的异常。如果想让该方法处理所有类型的异常,只需要将 NullPointerException 改成 Exception 即可。
异常处理方法的参数可以有异常实例、HttpServletResponse 以及 HttpServletRequest、Model 等。异常处理方法的返回值可以是一段 JSON、一个 ModelAndView、一个逻辑视图名等。
使用上面的注解配置一个类
/**
* Created by kinginblue on 2017/4/10.
* @ControllerAdvice + @ExceptionHandler 实现全局的 Controller 层的异常处理
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 处理所有不可知的异常
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
AppResponse handleException(Exception e){
LOGGER.error(e.getMessage(), e);
AppResponse response = new AppResponse();
response.setFail("操作失败,关注公众号Java精选!");
return response;
}
/**
* 处理所有业务异常
* @param e
* @return
*/
@ExceptionHandler(BusinessException.class)
@ResponseBody
AppResponse handleBusinessException(BusinessException e){
LOGGER.error(e.getMessage(), e);
AppResponse response = new AppResponse();
response.setFail(e.getMessage());
return response;
}
}
上面这个异常信息提示已知业务异常和未知捕获的异常,BusinessException是自己定义的异常类
public class BusinessException extends RuntimeException {
public BusinessException(String message){
super(message);
}
}
写一个工具类,将这个异常信息转接下,抛出来
public static void isTrue(boolean expression, String error){
if(!expression) {
throw new BusinessException(error);
}
}
这个方法在下面用到了,将写一些你知道异常,进行判断,将异常抛出来处理。
这个可以采用spring自带的返回体ResponseEntity,自己声明定义具体的返回对象信息,例如这样
@ExceptionHandler(JyspException.class)
public ResponseEntity<ExceptionResult> handleException(JyspException e) {
return ResponseEntity.status(e.getExceptionEnum().getCode())
.body(new ExceptionResult(e.getExceptionEnum()));
}
自定义的异常
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class JyspException extends RuntimeException{
private ExceptionEnum exceptionEnum;
}
枚举你的异常信息类
@Getter
@NoArgsConstructor
@AllArgsConstructor
public enum ExceptionEnum {
/**
* 用户名或密码错误
*/
INVALID_USERNAME_PASSWORD(400, "用户名或密码错误!"),
/**
* 用户名保存失败
*/
SAVE_USER_ERROR(400, "用户名保存失败!"),
/**
* 角色保存失败
*/
CREAT_ROLE_ERROR(500,"角色保存失败");
private int code;
private String msg;
}
上面这个采用枚举将已知的异常几种管理
其中@ExceptionHandler(Exception.class)返回的异常,也可以自定义的异常,我的采用枚举来处理,也可以在代码中处理,异常信息是怎么来的,是你写代码遇到到的异常,你throws 方式抛出来,然后采用自己定的异常信息,将它抛到控制层,通过全局异常处理类处理,友好的抛到前端,例如通过代码处理异常抛出
@Service
public class DogService {
@Transactional
public Dog update(Dog dog){
// some database options
// 模拟狗狗新名字与其他狗狗的名字冲突
BSUtil.isTrue(false, "狗狗名字已经被使用了...");
// update database dog info
return dog;
}
}
这个异常是你能预料到的,但是有的异常你没办法预料到,或者遗漏了,那么将采用下面的方法友好提示前端
@ExceptionHandler(Exception.class)
ResponseEntity<ExceptionResult> handleException(Exception e) {
log.error(e.getMessage(), e);
ExceptionResult response = new ExceptionResult();
response.setStatus(500);
response.setMsg("操作失败!");
return ResponseEntity.ok(response);
}
代码说明
Logger 进行所有的异常日志记录。
@ExceptionHandler(BusinessException.class) 声明了对 BusinessException 业务异常的处理,并获取该业务异常中的错误提示,构造后返回给客户端。
@ExceptionHandler(Exception.class) 声明了对 Exception 异常的处理,起到兜底作用,不管 Controller 层执行的代码出现了什么未能考虑到的异常,都返回统一的错误提示给客户端。
备注:以上 GlobalExceptionHandler 只是返回 Json 给客户端,更大的发挥空间需要按需求情况来做。
参考:https://blog.csdn.net/asd051377305/article/details/104773800
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
https://blog.csdn.net/qq_41134142/article/details/110400896
最近有很多人问,有没有读者交流群!加入方式很简单,公众号Java精选,回复“加群”,即可入群!
(微信小程序):3000+道面试题,包含Java基础、并发、JVM、线程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架构设计等,在线随时刷题!
特别推荐:专注分享最前沿的技术与资讯,为弯道超车做好准备及各种开源项目与高效率软件的公众号,「大咖笔记」,专注挖掘好东西,非常值得大家关注。点击下方公众号卡片关注。