springboot controllerAdvice使用
[text]在spring中使用全局异常捕获来处理全局异常是一种很常见的操作
1 创建 GlobalExceptionHandler.java
package com.wycms.framework.web.exception;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import com.wycms.common.enums.ExceptionEnum;
import org.apache.shiro.authz.AuthorizationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import com.wycms.common.core.domain.AjaxResult;
import com.wycms.common.exception.BusinessException;
import com.wycms.common.exception.DemoModeException;
import com.wycms.common.utils.ServletUtils;
import com.wycms.common.utils.security.PermissionUtils;
/**
* 全局异常处理器
*
* @author wycms
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 权限校验失败 如果请求为ajax返回json,普通请求跳转页面
*/
@ExceptionHandler(AuthorizationException.class)
public Object handleAuthorizationException(HttpServletRequest request, AuthorizationException e) {
log.error(e.getMessage(), e);
if (ServletUtils.isAjaxRequest(request)) {
return AjaxResult.error(PermissionUtils.getMsg(e.getMessage()));
} else {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error/unauth");
return modelAndView;
}
}
/**
* 请求方式不支持
*/
@ExceptionHandler({HttpRequestMethodNotSupportedException.class})
public AjaxResult handleException(HttpRequestMethodNotSupportedException e) {
log.error(e.getMessage(), e);
return AjaxResult.error("不支持' " + e.getMethod() + "'请求");
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public AjaxResult runtimeErrorHandler(RuntimeException e) {
log.error("运行时异常:", e);
String detailMsg = e.getCause() != null ? e.getMessage() : "";
String additionInfo = e.getCause() != null ? e.getCause().getMessage() : "";
detailMsg += " " + additionInfo;
return AjaxResult.error("运行时异常:" + e.getMessage() + " " + detailMsg);
}
/**
* 业务异常
*/
@ExceptionHandler(BusinessException.class)
public Object businessException(HttpServletRequest request, BusinessException e) {
log.error(e.getMessage(), e);
if (ServletUtils.isAjaxRequest(request)) {
return AjaxResult.error(e.getMessage());
} else {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("errorMessage", e.getMessage());
modelAndView.setViewName("error/business");
return modelAndView;
}
}
/**
* 自定义验证异常
*/
@ExceptionHandler(BindException.class)
public AjaxResult validatedBindException(BindException e) {
log.error(e.getMessage(), e);
String message = e.getAllErrors().get(0).getDefaultMessage();
return AjaxResult.error(message);
}
@ExceptionHandler({MethodArgumentNotValidException.class})
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public AjaxResult handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
BindingResult bindingResult = ex.getBindingResult();
StringBuilder sb = new StringBuilder("校验失败:");
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
}
String msg = sb.toString();
return AjaxResult.failure(ExceptionEnum.PARAM_NOT_VALID.getShowMsg());
}
@ExceptionHandler({ConstraintViolationException.class})
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public AjaxResult handleConstraintViolationException(ConstraintViolationException ex) {
return AjaxResult.failure(ExceptionEnum.PARAM_NOT_VALID.getShowMsg());
}
/**
* 演示模式异常
*/
@ExceptionHandler(DemoModeException.class)
public AjaxResult demoModeException(DemoModeException e) {
return AjaxResult.error("演示模式,不允许操作");
}
/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e) {
log.error(e.getMessage(), e);
return AjaxResult.error("服务器错误,请联系管理员");
}
}
自定义的异常BusinessException
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
protected final String message;
public BusinessException(String message) {
this.message = message;
}
public BusinessException(String message, Throwable e) {
super(message, e);
this.message = message;
}
@Override
public String getMessage() {
return message;
}
}
2 使用
public String importUser(List<UserOperateModel> userList, Boolean isUpdateSupport) {
if (StringUtils.isNull(userList) || userList.size() == 0) {
throw new BusinessException("导入用户数据不能为空!");
}
int successNum = 0;
int failureNum = 0;
return "";
}
3. 常见问题,无法进入到我们的自定义Exception,而被全局捕获
1. 全局Aop中有try catch,但是catch中没有做相应的throw的类型转换
@Aspect
@Component
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
// 配置织入点
@Pointcut("@annotation(com.wycms.common.annotation.Log)")
public void logPointCut() {
}
/**
* 处理时执行,记录入参
* @param joinPoint
* @return
* @throws Exception
*/
@Around("logPointCut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Exception {
//R.error(1000,"接口调用失败!") ;
Object result = AjaxResult.success();
try {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
Map<String, String[]> parameterMap = request.getParameterMap();
StringBuffer sb = new StringBuffer();
//String requestId = IdUtil.simpleUUID();
//sb.append("\n【request_id】:").append(requestId);
sb.append("\n【请求 URL】:").append(request.getRequestURL());
sb.append("\n【请求 IP】:").append(getIp(request));
sb.append("\n【请求类名】:").append(joinPoint.getSignature().getDeclaringTypeName());
sb.append("【请求方法名】:").append(joinPoint.getSignature().getName());
sb.append("\n【body】:").append(tryToGetArgsString(1, joinPoint, null));
sb.append("\n【请求参数】:").append(tryToGetArgsString(2, null, parameterMap));
String requestLog = sb.toString();
logger.info(" \n 请求接口时间:" + DateUtils.getNowDate() + ",信息为:{} ", requestLog);
// result的值就是被拦截方法的返回值
long startTime = System.currentTimeMillis();
handleLog(joinPoint,null,sb.toString());
//执行成功后
result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
String resultStr = result == null ? "" : result.toString();
logger.info("接口返回--[{}]", resultStr);
logger.info("接口耗时--[{}]", endTime - startTime + "ms");
handleLog(joinPoint,null,resultStr);
return result;
}
catch (Throwable e) {
logger.error("接口调用错误,错误原因:[{}]", ExceptionUtils.getMessage(e));
//这里处理是具体是抛出了什么类型的异常
if (e instanceof CommonException) {
throw new CommonException(((CommonException) e).getCode(), e.getMessage(), ((CommonException) e).getErrorMsg());
}
if (e instanceof BusinessException) {
throw new BusinessException(e.getMessage());
}
throw new Exception(e);
}
}
<code>
throw new Exception(e); 更正一下这里,其实这里只需要改成
throw e 即可解决类型判断的问题
</code>
```java
catch (Throwable e) {
logger.error("接口调用错误,错误原因:[{}]", ExceptionUtils.getMessage(e));
//这里处理是具体是抛出了什么类型的异常
// 由于最终 throw e,所以这里不再需要判断了
// if (e instanceof CommonException) {
// throw new CommonException(((CommonException) e).getCode(), e.getMessage(), ((CommonException) e).getErrorMsg());
// }
// if (e instanceof BusinessException) {
// throw new BusinessException(e.getMessage());
// }
//这里不要包装
// throw new Exception(e);
// 直接throw原来的出去就可以
throw e
}
```
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
handleLog(joinPoint, null, jsonResult);
}
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "logPointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
handleLog(joinPoint, e, null);
}
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
try {
// 获得注解
Log controllerLog = getAnnotationLog(joinPoint);
if (controllerLog == null) {
return;
}
// 获取当前的用户
SysUser currentUser = ShiroUtils.getSysUser();
// *========数据库日志=========*//
SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = ShiroUtils.getIp();
operLog.setOperIp(ip);
// 返回参数
operLog.setJsonResult(JSON.marshal(jsonResult));
operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
if (currentUser != null) {
operLog.setOperName(currentUser.getLoginName());
if (StringUtils.isNotNull(currentUser.getDept())
&& StringUtils.isNotEmpty(currentUser.getDept().getDeptName())) {
operLog.setDeptName(currentUser.getDept().getDeptName());
}
}
if (e != null) {
operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
operLog.setMethod(className + "." + methodName + "()");
// 设置请求方式
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(controllerLog, operLog);
// 保存数据库
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
} catch (Exception exp) {
// 记录本地异常日志
logger.error("==前置通知异常==");
logger.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param log 日志
* @param operLog 操作日志
* @throws Exception
*/
public void getControllerMethodDescription(Log log, SysOperLog operLog) throws Exception {
// 设置action动作
operLog.setBusinessType(log.businessType().ordinal());
// 设置标题
operLog.setTitle(log.title());
// 设置操作人类别
operLog.setOperatorType(log.operatorType().ordinal());
// 是否需要保存request,参数和值
if (log.isSaveRequestData()) {
// 获取参数的信息,传入到数据库中。
setRequestValue(operLog);
}
}
/**
* 获取请求的参数,放到log中
*
* @param operLog 操作日志
* @throws Exception 异常
*/
private void setRequestValue(SysOperLog operLog) throws Exception {
Map<String, String[]> map = ServletUtils.getRequest().getParameterMap();
String params = JSON.marshal(map);
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
}
/**
* 是否存在注解,如果存在就获取
*/
private Log getAnnotationLog(JoinPoint joinPoint) throws Exception {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(Log.class);
}
return null;
}
private String getIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
/**
* 这里要注意的是,对于有一些回调的逻辑,会出错,所以要catch
*
* @param paramType args为1 parameter为2
* @param point 注入点
* @param map 其它信息
* @return
*/
private String tryToGetArgsString(int paramType, ProceedingJoinPoint point, Map map) {
try {
if (paramType == 1) {
if (point.getArgs() != null && point.getArgs().length > 0) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < point.getArgs().length; i++) {
sb.append(point.getArgs()[i]).append(" \n");
}
return sb.toString();
}
}
if (paramType == 2) {
return com.alibaba.fastjson.JSON.toJSONString(map);
}
return "";
} catch (Exception e) {
return e.toString();
}
}
}
注意看上面的:doAround方法中,如果在抛出异常的地方,被这个AOP拦截了,在catch中又没有对Throwable e的类型进行分别处理,那么其实最终抛出的异常就会变成throw new Exception(e); 这时候就变成了默认的Exception了,全局的异常就自然没办法捕获到自定义抛出的异常类型了