我们在Spring Boot2.x-07Spring Boot2.1.2整合Mybatis这边文章的基础上来实现下Spring Boot使用@ControllerAdvice和@ExceptionHandler实现自定义全局异常。
首先需要明确的是:@ControllerAdvice 顾名思义主要处理的就是 controller 层的异常信息,没有进入 controller 层的异常@ControllerAdvice 是无法处理的。 如果需要处理这种错误可以继承BasicErrorController,可参考 https://segmentfault.com/a/1190000008443705
现在前后端分离的趋势,前端通过ajax调用Restful接口,约定前后端的接口规范,后台只需要按照约定格式返回JSON给前端即可,越来越少的项目会在Controller层糅合ModelAndView的信息了。
假定我们这里的项目是前后端分离,我们来探讨下基于此种场景的全局异常处理(因此全局异常处理类我们使用了@RestControllerAdvice)
为什么需要全局异常呢?
server.error.path
(application.properties中可以配置,默认为”/error”)的控制器方法中进行处理,详见BasicErrorController源码 ,提示不友好比如我们之前写的o2o的项目
Controller层充满了大量的try-catch【不推荐使用try-catch,增大了代码量,当异常过多对应的catch也就越多,不方便维护和扩展】,而且也只是简单粗暴的返回Map,通过@ResponseBody转换为JSON返回给前台,非常不优雅。
约定好返回格式+使用全局异常后,Controller层就清爽了很多,无需try-catch,并且还能避免因为异常被try-catch捕获导致@Transactional注解失效。
我们先看下如果没有全局异常,并且也没有对异常进行捕获,直接使用Spring Boot默认的异常显示会怎样呢?
先把个字段名故意写错来看下,
Controller层的代码如下:
启动Spring Boot工程,访问下Controller层暴露的接口
http://localhost:8080/artisans
经典的Whitelabel Error Page
如上图,可以看到是非常的不友好,那这里我们来使用全局异常来改造下吧。
项目中
package com.artisan.exception;
import lombok.Getter;
/**
* 需要继承RuntimeException。
* 另外Spring 对于 RuntimeException类型的 异常才会进行事务回滚
* @author yangshangwei
*
*/
public class MyCustomException extends RuntimeException {
private static final long serialVersionUID = 8863339790253662109L;
@Getter
private int code ;
@Getter
private String message;
public MyCustomException() {
super();
}
public MyCustomException(int code , String message) {
this.message = message;
this.code = code;
}
}
统一返回的异常信息的格式
package com.artisan.exception;
import lombok.Getter;
/**
* 统一返回的异常信息的格式
*
* @author yangshangwei
*
*/
public class ExceptionResponseEntity {
@Getter
private int code;
@Getter
private String message;
public ExceptionResponseEntity() {
}
public ExceptionResponseEntity(int code, String message) {
this.code = code;
this.message = message;
}
}
说明见代码注释
package com.artisan.exception;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
/**
*
* @ControllerAdvice 捕获 Controller 层抛出的异常,如果添加 @ResponseBody 返回信息则为JSON 格式。
@RestControllerAdvice 相当于 @ControllerAdvice 与 @ResponseBody 的结合体。
@ExceptionHandler 统一处理一种类的异常,减少代码重复率,降低复杂度。
因为我们这里全部异常信息都约定返回json,所以直接使用 @RestControllerAdvice 代替 @ControllerAdvice ,这样在方法上就可以不需要添加 @ResponseBody了
步骤:
1.创建一个 GlobalExceptionHandler 类,并添加上 @RestControllerAdvice 注解就可以实现异常通知类的定义了
2.定义的方法中添加上 @ExceptionHandler 即可实现Controller层的异常捕捉
*
*/
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler{
/**
* 如果需要捕获多个异常 定义如下:@ExceptionHandler({})
*
* @param request
* @param e
* @param response
* @return
*/
// 捕获多个异常的写法
@ExceptionHandler({MyCustomException.class,MyCustomException.class})
public ExceptionResponseEntity customExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
MyCustomException exception = (MyCustomException) e;
logger.info("MyCustomException");
return new ExceptionResponseEntity(exception.getCode(), exception.getMessage());
}
/**
* 捕获 RuntimeException 异常
* 如果在一个 exceptionHandler 通过 if (e instanceof xxxException) 太麻烦,
* 可以写多个方法标注@ExceptionHandler处理不同的异常
*
* @param request request
* @param e exception
* @param response response
* @return 响应结果
*/
@ExceptionHandler(RuntimeException.class)
public ExceptionResponseEntity runtimeExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
RuntimeException exception = (RuntimeException) e;
logger.info("RuntimeException");
return new ExceptionResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR.value(), exception.getMessage());
}
}
通过 @ControllerAdvice(或者@RestControllerAdvice) 和 @ExceptionHandler 实现了对全局异常的捕获。
这里仅定义了2个异常,一个是自定义的MyCustomException,另外一个是RuntimeException,按需增加自定义的异常类即可。
因为我们把数据库字段写错了,所以这个方法肯定是抛出异常的,假定dao层和service层都未对异常进行处理,那么Controller层抛出的异常返回给前端是什么样的呢?
启动Spring Boot工程,
http://localhost:8080/artisans
结合控制台输出的日志
可知,GlobalExceptionHandler#runtimeExceptionHandler
捕获了该异常,而不是我们文章开始的那个经典的Whitelabel Error Page页面了
那我们刚才自定义的那个异常怎么来捕获呢? 其实很简单,只要throw即可
为了演示用法,我们修改下Controller层的方法 如下
访问 http://localhost:8080/artisans
结合后台日志可知 GlobalExceptionHandler#customExceptionHandler
捕获了该异常
这里是使用@ControllerAdvice和@ExceptionHandler来实现全局的异常处理,其他方式比如使用AOP的方式也是可行的。 还有一种基于Spring Boot本身的全局异常统一处理,主要是实现ErrorController接口或者继承AbstractErrorController抽象类或者继承BasicErrorController类。
具体可参考这位大神的博客
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有