前景描述: 最近在做项目时发现后台程序的异常抛到前端页面上报出一大段sql异常,因此考虑需要对异常进行全局统一处理,并将日志进行分类入库以及记录接口请求的日志信息等,记录日志信息在之前的文章已经有记录了这里不再重复有需要的请移步到Spring Boot 使用AOP切面实现后台日志管理模块。
因为项目是基于Springboot做的前后端分离的项目,需要结合项目本身的一些特殊需求做些许改造。
在网络上讲述的大致这两种方案:
1、基于@ControllerAdvice注解的Controller层的全局异常统一处理(最常用,使用较多)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.isoftstone.common.utils.web.JsonResult;
import com.isoftstone.common.utils.web.StatusUtil;
/**
* 全局异常处理类
* @ClassName::ControllerExceptionHandler
* @author leon
* @createDate 2018年9月20日 下午3:39:19
* @version v1.0
* @classRemarks TODO
*/
@ControllerAdvice
public class ControllerExceptionHandler {
private static Logger logger = LoggerFactory.getLogger(ControllerExceptionHandler.class);
@ExceptionHandler(Exception.class)
@ResponseBody
public JsonResult handleException(Exception e){
//异常日志入库
logger.info("=================exception===============");
e.printStackTrace();
return new JsonResult(StatusUtil.ERROR,"系统正忙,请稍后在试......");
}
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public JsonResult handleException(RuntimeException e){
//异常日志入库
logger.info("=================runtime exception===============");
e.printStackTrace();
return new JsonResult(StatusUtil.ERROR,"小服正在努力尝试重连,请稍后在试......");
}
@ExceptionHandler(SaveRuntimeException.class)
@ResponseBody
public JsonResult handleException(SaveRuntimeException e){
//异常日志入库
logger.info("=================Save exception===============");
e.printStackTrace();
return new JsonResult(StatusUtil.ERROR,"新增数据失败,请稍后在试......");
}
@ExceptionHandler(UpdateRuntimeException.class)
@ResponseBody
public JsonResult handleException(UpdateRuntimeException e){
//异常日志入库
logger.info("=================Update exception===============");
e.printStackTrace();
return new JsonResult(StatusUtil.ERROR,"更新数据失败,请稍后在试......");
}
}
/**
* 数据新增时异常
* @author Leon
*
*/
public class SaveRuntimeException extends RuntimeException {
private static final long serialVersionUID = 2323446669041126322L;
public SaveRuntimeException() {
super();
}
public SaveRuntimeException(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public SaveRuntimeException(String message, Throwable cause) {
super(message, cause);
}
public SaveRuntimeException(String message) {
super(message);
}
public SaveRuntimeException(Throwable cause) {
super(cause);
}
}
/**
* 数据更新时的异常
* @author Loen
*
*/
public class UpdateRuntimeException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 18794756827824687L;
public UpdateRuntimeException() {
super();
}
public UpdateRuntimeException(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public UpdateRuntimeException(String message, Throwable cause) {
super(message, cause);
}
public UpdateRuntimeException(String message) {
super(message);
}
public UpdateRuntimeException(Throwable cause) {
super(cause);
}
}
需注意的是 SaveRuntimeException 和 UpdateRuntimeException是我自定义的异常处理类,基于RuntimeException处理的,
这个代码示例写的非常浅显易懂,但是需要注意的是:基于@ControllerAdvice注解的全局异常统一处理只能针对于Controller层的异常,意思是只能捕获到Controller层的异常,在service层或者其他层面的异常不能捕获,当然也可以通过自定义来实现,那我们就来说说第二种方式.
2、基于Springboot自身的全局异常统一处理,主要是实现ErrorController接口或者继承AbstractErrorController抽象类或者继承BasicErrorController类, 以下是网上一位博主给出的示例代码
@Controller
@RequestMapping(value = "error")
@EnableConfigurationProperties({ServerProperties.class})
public class ExceptionController implements ErrorController {
private ErrorAttributes errorAttributes;
@Autowired
private ServerProperties serverProperties;
/**
* 初始化ExceptionController
* @param errorAttributes
*/
@Autowired
public ExceptionController(ErrorAttributes errorAttributes) {
Assert.notNull(errorAttributes, "ErrorAttributes must not be null");
this.errorAttributes = errorAttributes;
}
/**
* 定义404的ModelAndView
* @param request
* @param response
* @return
*/
@RequestMapping(produces = "text/html",value = "404")
public ModelAndView errorHtml404(HttpServletRequest request,
HttpServletResponse response) {
response.setStatus(getStatus(request).value());
Map model = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.TEXT_HTML));
return new ModelAndView("error/404", model);
}
/**
* 定义404的JSON数据
* @param request
* @return
*/
@RequestMapping(value = "404")
@ResponseBody
public ResponseEntity
该示例写的也是非常简单明了的,但是结合本身项目的实际需求,做相应的改造:
1、因为项目是前后端分离的,所以Controller层不会有ModelAndView返回类型,需要返回自身的APIResponse返回类型
2、项目需要统计全部的异常,而不只是404或者500的异常
3、捕获到异常之后需要做特殊化的业务处理
所以基于以上几方面对示例代码做了改造,具体改造代码如下:
/**
* @Author: leon
* @Description: Springboot全局异常统一处理
* @Date: 2018/9/20
* @Time: 16:41
*/
@RestController
@EnableConfigurationProperties({ServerProperties.class})
public class ExceptionController implements ErrorController {
private ErrorAttributes errorAttributes;
@Autowired
private ServerProperties serverProperties;
/**
* 初始化ExceptionController
* @param errorAttributes
*/
@Autowired
public ExceptionController(ErrorAttributes errorAttributes) {
Assert.notNull(errorAttributes, "ErrorAttributes must not be null");
this.errorAttributes = errorAttributes;
}
@RequestMapping(value = "/error")
@ResponseBody
public APIResponse error(HttpServletRequest request) {
Map body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new APIResponse(APIResponse.FAIL,null,body.get("message").toString());
}
/**
* Determine if the stacktrace attribute should be included.
* @param request the source request
* @param produces the media type produced (or {@code MediaType.ALL})
* @return if the stacktrace attribute should be included
*/
protected boolean isIncludeStackTrace(HttpServletRequest request,
MediaType produces) {
ErrorProperties.IncludeStacktrace include = this.serverProperties.getError().getIncludeStacktrace();
if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
return true;
}
if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) {
return getTraceParameter(request);
}
return false;
}
/**
* 获取错误的信息
* @param request
* @param includeStackTrace
* @return
*/
private Map getErrorAttributes(HttpServletRequest request,
boolean includeStackTrace) {
RequestAttributes requestAttributes = new ServletRequestAttributes(request);
return this.errorAttributes.getErrorAttributes(requestAttributes,
includeStackTrace);
}
/**
* 是否包含trace
* @param request
* @return
*/
private boolean getTraceParameter(HttpServletRequest request) {
String parameter = request.getParameter("trace");
if (parameter == null) {
return false;
}
return !"false".equals(parameter.toLowerCase());
}
/**
* 获取错误编码
* @param request
* @return
*/
private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request
.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
try {
return HttpStatus.valueOf(statusCode);
}
catch (Exception ex) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
}
/**
* 实现错误路径,暂时无用
* @return
*/
@Override
public String getErrorPath() {
return "";
}
}
经过测试,可以捕获到所有层面上的异常,当前前提仍然是没有对异常进行catch处理,否则这里也是捕获不到
以上为网络上常用的两种全局异常统一处理方案,经过实际测试发现都可以实现满足要求。
其实基于AOP也可以实现异常的全局处理,自己相应的做了测试发现也满足要求,相应的代码如下:
@Component
@Aspect
public class ExceptionAspectController {
public static final Logger logger = LoggerFactory.getLogger(ExceptionAspectController.class);
@Pointcut("execution(* com.test.test.*.*(..))")//此处基于自身项目的路径做具体的设置
public void pointCut(){}
@Around("pointCut()")
public Object handleControllerMethod(ProceedingJoinPoint pjp) {
Stopwatch stopwatch = Stopwatch.createStarted();
APIResponse> apiResponse;
try {
logger.info("执行Controller开始: " + pjp.getSignature() + " 参数:" + Lists.newArrayList(pjp.getArgs()).toString());
apiResponse = (APIResponse>) pjp.proceed(pjp.getArgs());
logger.info("执行Controller结束: " + pjp.getSignature() + ", 返回值:" + apiResponse.toString());
logger.info("耗时:" + stopwatch.stop().elapsed(TimeUnit.MILLISECONDS) + "(毫秒).");
} catch (Throwable throwable) {
apiResponse = handlerException(pjp, throwable);
}
return apiResponse;
}
private APIResponse> handlerException(ProceedingJoinPoint pjp, Throwable e) {
APIResponse> apiResponse = null;
if(e.getClass().isAssignableFrom(MessageCenterException.class) ){
MessageCenterException messageCenterException = (MessageCenterException)e;
logger.error("RuntimeException{方法:" + pjp.getSignature() + ", 参数:" + pjp.getArgs() + ",异常:" + messageCenterException.getException().getMessage() + "}", e);
apiResponse = messageCenterException.getApiResponse();
} else if (e instanceof RuntimeException) {
logger.error("RuntimeException{方法:" + pjp.getSignature() + ", 参数:" + pjp.getArgs() + ",异常:" + e.getMessage() + "}", e);
apiResponse = new APIResponse(APIResponse.FAIL,null,e.getMessage());
} else {
logger.error("异常{方法:" + pjp.getSignature() + ", 参数:" + pjp.getArgs() + ",异常:" + e.getMessage() + "}", e);
apiResponse = new APIResponse(APIResponse.FAIL,null,e.getMessage());
}
return apiResponse;
}
}
经过测试,在执行切点中配置的路径中的方法有异常时,可以被这里捕获到。