利用HandlerExceptionResolver接口实现全局异常捕获
场景:当还没有进入我们业务接口就发生了异常,比如参数解析异常,拦截器处理发生异常等,此时自定义异常由于还没有走接口所以不会走自定义异常。
步骤
第一步
定义mvc全局异常处理类 AbstractWebExceptionHandler 类 implements HandlerExceptionResolver
package com.xihongshi.common.handler;
import com.alibaba.fastjson.JSON;
import com.xihongshi.common.constants.Constant;
import com.xihongshi.common.constants.LogConstant;
import com.xihongshi.common.exception.InvalidSessionException;
import com.xihongshi.utils.HttpResponseUtil;
import com.xihongshi.utils.HttpServletUtil;
import com.xihongshi.utils.IpUtil;
import com.xihongshi.utils.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.slf4j.MDC;
import org.springframework.core.MethodParameter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.Map;
/**
* @Description: mvc全局异常处理
* @author: AndrewLee
* @date: 2021-03-28
*/
@Slf4j
public abstract class AbstractWebExceptionHandler implements HandlerExceptionResolver {
/**
* 缓存的请求body
*/
protected static final String CACHED_REQUESTBODY = "cached_requestBody";
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
ModelAndView mv = new ModelAndView();
// 业务逻辑异常
Object resultModel = doResolveException(request, response, handler, e);
logBadRequest(e, request, handler, resultModel);
writeErrorResponse(request, response, mv, handler, resultModel);
clearMdc();
clearThreadLocal();
return mv;
}
protected abstract Object doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e);
/**
* 记录 Request 异常日志(方便排查问题)
*
* @param e
* @param resultModel
*/
protected void logBadRequest(Throwable e, HttpServletRequest request, Object handler, Object resultModel) {
try {
String requestBody = getRequestBody(request);
Map<String, String> requestParams = HttpServletUtil.getRequestParams(request);
Map<String, String> headerMap = HttpServletUtil.getHeaderMap(request);
String responseStr = toString(resultModel);
String clientIp = IpUtil.getClientIp(request);
String exceptionName = e.getClass().getSimpleName();
String exceptionCauseName = (e.getCause() == null ? null : e.getCause().getClass().getSimpleName());
String exceptionMessage = e.getMessage();
long costTime = getCostTime(request);
String errorMessage = "logBadRequest -> Exception=%s - %s - %s -> clientIp=%s ,url=%s ,method=%s ,contentType=%s -> requestParams=%s -> requestBody=%s -> costTime=%s ,responseStr=%s -> header=%s";
errorMessage = String.format(errorMessage
, new Object[]{exceptionName, exceptionCauseName, exceptionMessage,
clientIp, request.getRequestURL(), request.getMethod(), request.getContentType(), requestParams.toString(), requestBody, costTime, responseStr, headerMap});
if (isIgnoreException(e)) {
//可以忽略的异常(不打印堆栈信息)
log.error(errorMessage);
} else {
log.error(errorMessage, e);
}
} catch (Exception ex) {
log.warn("[全局异常处理] logBadRequest error: " + ex.getMessage(), ex);
}
}
/**
* @param request
* @return
*/
private long getCostTime(HttpServletRequest request) {
long costTime = 0;
Long requestStartTime = (Long) request.getAttribute(LogConstant.REQUEST_START_TIME_ATTR);
if (requestStartTime != null) {
costTime = System.currentTimeMillis() - requestStartTime;
}
return costTime;
}
protected String toString(Object resultModel) {
if (null == resultModel) {
return null;
}
String str = null;
if (resultModel instanceof Serializable) {
str = JSON.toJSONString(resultModel);
} else {
str = resultModel.toString();
}
return str;
}
/**
* 输出异常响应
*
* @param request
* @param response
* @param mv
* @param resultModel
*/
protected void writeErrorResponse(HttpServletRequest request, HttpServletResponse response
, ModelAndView mv, Object handler, Object resultModel) {
if (null == resultModel) {
return;
}
if (!isApiEntityReturnType(handler)) {
log.warn("[警告]当前接口[{}]返回类型{}非接口统一响应格式,将不返回统一的异常响应!", request.getRequestURL(), getMethodReturnType(handler));
return;
}
String responseJson = JSON.toJSONString(resultModel);
try {
String requestUrl = request.getRequestURL().toString();
String requestBody = getRequestBody(request);
String requestIp = IpUtil.getClientIp(request);
log.info("writeErrorResponse -> clientIp={} ,url={} ,param={} -> respStr={}", requestIp, requestUrl, requestBody, responseJson);
} catch (Exception ex) {
log.warn("writeErrorResponse error: " + ex.getMessage(), ex);
}
HttpResponseUtil.renderJson(response, responseJson);
}
/**
* 可以忽略的异常(不打印堆栈信息)
*
* @param e
* @return
*/
private boolean isIgnoreException(Throwable e) {
if (e instanceof InvalidSessionException) {
return true;
}
return false;
}
protected boolean isApiEntityReturnType(Object handler) {
return isResponseEntityReturnType(handler);
}
protected boolean isResponseEntityReturnType(Object handler) {
Class<?> parameterType = getMethodReturnType(handler);
return null != parameterType && ResponseResult.class.isAssignableFrom(parameterType);
}
private Class getMethodReturnType(Object handler) {
if (null != handler && handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
MethodParameter returnType = handlerMethod.getReturnType();
Class<?> parameterType = returnType.getParameterType();
return parameterType;
}
return null;
}
/**
* 清理日志上下文
*/
private void clearMdc() {
try {
MDC.clear();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 清理线程本地变量
*/
protected void clearThreadLocal() {
}
private String getRequestBody(HttpServletRequest request) throws IOException {
String requestBody = (String) request.getAttribute(CACHED_REQUESTBODY);
if (null == requestBody) {
try {
requestBody = IOUtils.toString(request.getInputStream(), Charset.forName(Constant.UTF8));
request.setAttribute(CACHED_REQUESTBODY, requestBody);
} catch (Exception e) {
log.warn("getRequestBody error: " + e.getMessage(), e);
}
}
return requestBody;
}
}
第二步
API 全局异常处理类ApiExceptionHandler,extends AbstractWebExceptionHandler,重写方法,处理异常信息
package com.xihongshi.common.handler;
import com.xihongshi.common.exception.BizException;
import com.xihongshi.utils.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Description: API 全局异常处理
* @author: AndrewLee
* @date: 2021-03-28
*/
@Slf4j
public class ApiExceptionHandler extends AbstractWebExceptionHandler {
public ApiExceptionHandler() {
log.info("[Api全局异常拦截处理器]初始化{}", this);
}
@Override
protected Object doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
if (!isResponseEntityReturnType(handler)) {
return null;
}
String errorCode = "-1";
String errorMsg = "系统繁忙,请稍后再试";
if (e instanceof BizException) {
BizException ex = (BizException) e;
errorCode = ex.getCode();
errorMsg = ex.getMessage();
}
// 参数校验异常
else if (e instanceof BindException) {
BindingResult bindingResult = ((BindException) e).getBindingResult();
if (bindingResult.hasErrors()) {
errorMsg = (bindingResult.getAllErrors().get(0).getDefaultMessage());
}
} else if (e instanceof MethodArgumentNotValidException) {
BindingResult bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
if (bindingResult.hasErrors()) {
errorMsg = bindingResult.getAllErrors().get(0).getDefaultMessage();
}
}
ResponseResult responseResult = new ResponseResult();
responseResult.setCode(errorCode);
responseResult.setMsg(errorMsg);
return responseResult;
}
}