这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战
前言
当前端向后端请求数据的时候,正常情况下,接口会返回我们理想的格式.但是在后端程序出现问题的情况下,返回的默认的数据不一定是我们想要的类型,所有当程序出现异常时,我们需要有一个统一的全局异常处理器,将异常数据处理成,我们需要的格式,并返回前台
需要处理异常的位置
(一) 通过controller抛出的异常
该异常是业务运行时导致的异常,在controller发生,或者能向上抛出到controller的异常,这种异常通过自定义异常类并注解@ControllerAdvice
或者@RestControllerAdvice
即可达到目的:
@RestControllerAdvice
//@ControllerAdvice
@Slf4j
public class AllExceptionHandler{
@ExceptionHandler(value =CustomException.class)
public R customExceptionHandle(Exception e){
//这个地方应该去创建多个Exception类然后分类去捕获
log.error("自定义异常: {}",e);
return R.fail(e.getMessage());
}
@ExceptionHandler(value = org.springframework.validation.BindException.class)
public R bindExceptionHandle(BindException ex){
StringBuffer defaultMessage = new StringBuffer();
List<ObjectError> list = ex.getBindingResult().getAllErrors();
if(list!=null && list.size()>0){
try {
int i=0;
for (ObjectError error : list) {
if(i>0){
defaultMessage.append(",");
}
defaultMessage.append(error.getDefaultMessage());
i++;
}
} catch (Exception e) {
e.printStackTrace();
}
}
String s = defaultMessage.toString();
log.warn("用户数据参数异常: {}",s);
return R.fail(s);
}
@ExceptionHandler(value = Exception.class)
public R exceptionHandle(Exception e){
log.error("未知的异常{},{}",e.getMessage(),e);
return R.fail("系统异常",e.getMessage());
}
public AllExceptionHandler() {
super();
}
使用注意
-
@RestControllerAdvice
注解,可以用于定义@ExceptionHandler
、@InitBinder
、@ModelAttribute
,并应用到所有@RequestMapping
中 -
@RestControllerAdvice
继承了@ControllerAdvice
功能的子类,将返回信息写入到Response的Body中(因为也注解了@ResponseBody
),因此如果是json交互的前后单分离项目直接用@RestControllerAdvice
的更好. -
@RestControllerAdvice
和@ControllerAdvice
注解包含一些属性:属性 解释 @ControllerAdvice("org.zdc.controllers") 捕获指定包中的控制器的异常 @ControllerAdvice(annotations = RestController.class) 捕获带有注解@RestController的控制器的异常 @ControllerAdvice(assignableTypes = {AbstractController.class}) 捕获有带有指定签名的控制器异常 -
如果不生效,则需要注意观察是否被filter,Aspect等拦截,或者异常不是在扫描的包下抛出的.
-
可以定义多个
@ExceptionHandler
, 当异常出现的时候,程序会从上到下依次进行匹配,匹配成功之后就会终止继续匹配,因此最后一个@ExceptionHandler
应该定义为Exception.class
-
@ExceptionHandler
定义的原则: 除了最后一个Exception.class
外, 其他的异常则需要按需实现.比如:CustomException.class
是业务异常,返回的错误信息,只包含e.getMessage()
即可,而@Validated
参数验证异常BindException.class
,则需要解析异常愿意,好让用户明白具体是哪个参数异常导致的
(二) 拦截404或者服务器错误等未进入controller的异常
当请求了一个未定义的请求或解析请求报错的时候,程序会抛出错误,并把错误转发到/error
接口上,然后该返回错误的数据,但是返回的数据格式可能不是我们想要的,所以需要实现一个默认的controller并实现该/error
方法.
@RestController
@Slf4j
public class OtherExceptionHandler implements ErrorController {
@Override
public String getErrorPath() {
return "/error";
}
@RequestMapping("/error")
public R otherError(HttpServletRequest request, HttpServletResponse response) {
//将所有的错误请求都处理成200 具体根据实际项目配置
response.setStatus(200);
Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
String errorMsg = "出现错误";
if (statusCode == null) {
errorMsg = "服务器内部错误";
}
//转发过来的自定义CustomException异常
if(statusCode==5001 && request.getAttribute("javax.servlet.error.exception") instanceof CustomException){
log.warn("Filter异常:{}",((Exception)request.getAttribute("javax.servlet.error.exception")));
return R.fail(((Exception)request.getAttribute("javax.servlet.error.exception")).getMessage());
}
try {
return R.fail(errorMsg, statusCode, HttpStatus.valueOf(statusCode));
} catch (Exception ex) {
errorMsg = "服务器内部错误:" + statusCode;
}
log.error("服务器错误,错误码:{}",statusCode);
return R.fail(errorMsg);
}
}
看到这里你应该看到了转发过来的自定义CustomException异常
,实际上我们可以手动将异常转发到这里(比如自定义filter中), 使用方法请文章全局搜索手动转发到/error接口
(三) filter等自定义代码中异常
在自定义的filter中,业务判断不符的时候需要中断操作抛出异常、 在自定义的Aspect中,代理方法报错需要抛出异常,等等,一次我们需要注意处理这里的异常
filter中使用
@Component
public class ZdcFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if(false){
//中断 抛出异常
//第一种 手动转发到/error接口
request.setAttribute("javax.servlet.error.status_code",5001);
request.setAttribute("javax.servlet.error.status_msg","通过转发/error设置返回数据");
request.getRequestDispatcher("/error").forward(request, response);
return;
//第二种 抛出异常,自动转到/error接口,http状态码500
throw new CustomException("通过MyFilter,抛异常返回值");
//第三种 直接写到response中返回
response.setContentType("application/json; charset=utf-8");
response.getWriter().print("通过MyFilter直接接调用response返回数据");
response.getWriter().flush();
response.getWriter().close();
return;
}else{
//正常通过
chain.doFilter(request,response);
}
}
}
当然在这里我们基本上都时使用第三种方式直接通过response写回去.
作者:ZOUZDC
链接:https://juejin.cn/post/7028963866063306760
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。