全局异常处理
当定义了JSR303校验器后,校验不通过都会产生一个BindException,输出错误信息。若要对异常处理,我们可以定义一个全局异常处理的拦截器。
JSR303校验可以参考:
https://blog.csdn.net/wolfishness/article/details/101167488.
好处:
-
1.可以实现对项目中所有产生的异常进行拦截,在同一个类中实现统一处理。避免异常漏处理的情况。
-
2.项目中遇到异常,一般Try catch,这就不可避免在业务代码中加入这部分代码,显得复杂冗余。
一、统一异常处理
package com.bosssoft.bes.base.resolver;
import com.bosssoft.bes.base.coredata.vo.CommonResponse;
import com.bosssoft.bes.base.enums.SystemExceptionEnum;
import com.bosssoft.bes.base.exception.GlobalException;
import com.bosssoft.bes.base.utils.ResultUtils;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
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 org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* 统一异常全局异常处理类
* @ClassName: GlobalExceptionResolver
* @Description: TODO
* @Author: lujinshan
* @Date: 2019/8/10 17:47
* @Version: 1.0.0
*/
@Slf4j
@ControllerAdvice(annotations = {RestController.class})
public class GlobalExceptionResolver {
private final static Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionResolver.class);
@ExceptionHandler({Exception.class})
@ResponseBody
public CommonResponse handleException(Exception e) {
//判断是否是系统自定义异常
if (e instanceof GlobalException){
return ResultUtils.error(((GlobalException) e).getCode(), e.getMessage());
}
//判断是否是参数异常,并且没有使用@RequestBody
if (e instanceof BindException) {
//此处的BindException为Spring框架抛出的Validation异常
BindException bindException = (BindException) e;
//抛出的异常可能不止一个
List<ObjectError> errors = bindException.getAllErrors();
//获取第一个异常
ObjectError error = errors.get(0);
//获取异常信息
String errorMsg = error.getDefaultMessage();
return ResultUtils.error(20001, errorMsg);
}
//判断是否是参数异常,并且使用@RequestBody
if (e instanceof MethodArgumentNotValidException) {
LOGGER.info(((MethodArgumentNotValidException) e).getBindingResult().getAllErrors().toString());
MethodArgumentNotValidException methodArgumentNotValidException = (MethodArgumentNotValidException) e;
//抛出的异常可能不止一个
List<ObjectError> errorList = methodArgumentNotValidException.getBindingResult().getAllErrors();
List<String> result = new ArrayList<>();
for (ObjectError objectError : errorList) {
result.add(objectError.getDefaultMessage());
}
return ResultUtils.error(20000, result.toString());
}
LOGGER.error("【系统异常】{}", e);
return ResultUtils.error(SystemExceptionEnum.SYSTEM_ERROR.getCode(), SystemExceptionEnum.SYSTEM_ERROR.getMessage());
}
}
主要用到的是:@ControllerAdvice + @ExceptionHandler (Spring 框架)
@ControllerAdvice是一个@Component,用于定义@ExceptionHandler,@InitBinder和@ModelAttribute方法,适用于所有使用@RequestMapping方法。
意思就是该注解会对所有@RequestMapping方法进行检查,拦截。并进行异常处理。
拦截的范围 可以选定拦截异常范围,最终输出,以Result(CodeMsg)的形式输出。将异常信息放入 msg中。
MethodArgumentNotValidException 和 BindException 都是为了处理JSR303异常
于是我们的业务异常和运行时的其他异常可以在这个类中一并处理。
目的:
-
1.当参数校验不通过的时候,输出也是Result(CodeMsg),传给前端用于前端显示获取处理**
-
2.当Service 出现业务逻辑错误的时候,这个时候我们可以直接抛出异常,让拦截器来捕捉,捕捉之后,就不需要冗余的代码来return 一个不符合业务逻辑的返回值来作为输出。如下:
二、自定义异常:
package com.bosssoft.bes.base.exception;
/**
* 统一异常基类
* @ClassName: GlobalException
* @Description: TODO
* @Author: lujinshan
* @Date: 2019/8/10 23:28
* @Version: 1.0.0
*/
public class GlobalException extends RuntimeException {
private static final long serialVersionUID = -6025952423835016528L;
/**
* 异常码
*/
private Integer code;
/**
* 统一异常
* @param e
*/
public GlobalException(GlobalException e){
super(e.getMessage());
this.code = e.getCode();
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
三、统一异常注解
package com.bosssoft.bes.base.annotation;
import java.lang.annotation.*;
/**
* 统一异常注解
* @author lujinshan
* @date Created on 2019.08.12
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GlobalExceptionLog {
/**
* 设置默认值
*/
String value() default "";
}
四、统一异常AOP切面
package com.bosssoft.bes.base.aop;
import com.bosssoft.bes.base.resolver.GlobalExceptionResolver;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 全局异常切面
* @ClassName: ParamCheckAop
* @Description: TODO
* @Author: lujinshan
* @Date: 2019/8/12 13:40
* @Version: 1.0.0
*/
@Component
@Aspect
public class GlobalExceptionAspect {
private final static Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionAspect.class);
@Autowired
private GlobalExceptionResolver globalExceptionResolver;
@Pointcut(value = "@annotation(com.bosssoft.bes.base.annotation.GlobalExceptionLog)")
public void logPointCut() {
}
@Before("logPointCut()")
public void doBefore(JoinPoint joinPoint) {
}
@AfterThrowing(value = "logPointCut()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, RuntimeException e) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//创建时间
LOGGER.error("dateTime={}" + dateFormat.format(new Date()));
//URL
LOGGER.error("url={}", request.getRequestURL());
//method
LOGGER.error("method={}", request.getMethod());
//ip
LOGGER.error("ip={}", request.getRemoteAddr());
//class_method
LOGGER.error("class_method={}", joinPoint.getSignature().getDeclaringTypeName() + "," + joinPoint.getSignature().getName());
//args[]
LOGGER.error("args={}", joinPoint.getArgs());
//异常信息
LOGGER.error("errMessage={}" + e);
//输出异常堆栈信息
e.printStackTrace();
}
}
五、异常消息封装
package com.bosssoft.bes.base.utils;
import com.alibaba.fastjson.JSONObject;
import com.bosssoft.bes.base.coredata.vo.CommonResponse;
import com.bosssoft.bes.base.coredata.vo.ResponseHead;
/**
* 异常信息封装
* @ClassName: ResultUtils
* @Description: TODO
* @Author: lujinshan
* @Date: 2019/8/12 14:57
* @Version: 1.0.0
*/
public class ResultUtils {
/**
* 封装成功信息
* @param object
* @return
*/
public static CommonResponse success(Object object){
CommonResponse commonResponse = new CommonResponse();
ResponseHead head = new ResponseHead();
//成功
head.setCode("0");
head.setMessage("success");
commonResponse.setResponseHead(head);
commonResponse.setBody(JSONObject.parseObject(object.toString()));
return commonResponse;
}
/**
* 封装失败信息
* @param code
* @param message
* @return
*/
public static CommonResponse error(Integer code , String message){
CommonResponse commonResponse = new CommonResponse();
ResponseHead head = new ResponseHead();
//填入异常码和异常信息
head.setCode(code.toString());
head.setMessage(message);
commonResponse.setResponseHead(head);
return commonResponse;
}
}
注:CommonResponse是自己编写的输出数据格式