统一异常处理概述
spring mvc中如何做统一异常处理
先加载的@ControllerAdvice
类里如果存在@ExceptionHandler(xxException.class)
是需要捕获的异常或其父类,则将使用先加载的类中的异常处理方式。如果没有,则看后面的@ControllerAdvice
类里是否有。
可以使用@Order
来决定加载优先级,网上也有说法可以使用@Primary
,暂未自测,个人觉得应该也是可行的。
如果有多个统一异常处理怎么办
- 使用
basePackageClasses
属性,将想要优先加载的包写在前面; - 使用@Order注解来决定顺序
- 使用
excludeFilters
属性排除不想要加载的类,该属性使用方式多样,可自行搜索查看。(例:@ComponentScan(excludeFilters = @ComponentScan.Filter( type = FilterType.ASSIGNABLE_TYPE, classes = xx.class))
)
项目中的统一异常处理
package com.yonyoucloud.manufacturing.web;
import com.yonyou.ucf.mdd.ext.util.ResultMessage;
import org.imeta.biz.base.BizException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.NoHandlerFoundException;
import javax.servlet.http.HttpServletRequest;
@ControllerAdvice
public class ErrorHandler {
@ExceptionHandler(BizException.class)
@ResponseBody
ResponseEntity<?> handleBizException(HttpServletRequest request, Throwable ex) {
//HttpStatus status = getStatus(request);
return errResponse(ResultMessage.error(ex.getMessage()), HttpStatus.OK);
}
@ExceptionHandler(NoHandlerFoundException.class)
@ResponseBody
ResponseEntity<?> handleNotFoundException(HttpServletRequest request, Throwable ex) {
//HttpStatus status = getStatus(request);
return errResponse(ResultMessage.error(String.format("接口无效:%s", request.getRequestURI())), HttpStatus.OK);
}
@ExceptionHandler(Throwable.class)
@ResponseBody
ResponseEntity<?> handleCommonException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
return errResponse(ResultMessage.error(ex.getMessage()), status);
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
return HttpStatus.valueOf(statusCode);
}
private ResponseEntity<?> errResponse(String message, HttpStatus status) {
return ResponseEntity.status(status).contentType(MediaType.APPLICATION_JSON_UTF8)
.body(message);
}
}
平台的统一异常处理
mdd框架中也有类似的处理
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.yonyou.iuap.ucf.core.exception;
import com.yonyou.iuap.ucf.common.exception.BusinessException;
import com.yonyou.iuap.ucf.common.exception.CommonExceptionConstants;
import com.yonyou.iuap.ucf.common.rest.CommonResponse;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
public class CommonExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(CommonExceptionHandler.class);
@Value("${ucf.enableExcepthonHandler:true}")
private String enableExcepthonHandler;
public CommonExceptionHandler() {
}
@ExceptionHandler({Throwable.class})
public CommonResponse handleThrowable(final Throwable e) throws Throwable {
if (this.enableExcepthonHandler != null && !"true".equals(this.enableExcepthonHandler)) {
throw e;
} else {
return this.handleBusinessException(new BusinessException(CommonExceptionConstants.UNKNOWN_EXCEPTION, e));
}
}
@ExceptionHandler({BusinessException.class})
@ResponseBody
public CommonResponse handleBusinessException(final BusinessException excetpion) {
try {
MDC.put("code", String.valueOf(excetpion.getErrorCode()));
if (excetpion.getCause() != null) {
log.error("出现异常啦", excetpion);
} else {
String[] stackFrames = ExceptionUtils.getStackFrames(excetpion);
String latestUsableStack = "";
if (stackFrames != null && stackFrames.length > 1) {
latestUsableStack = stackFrames[1];
}
log.error("出现异常啦:" + excetpion.getMessage() + ",异常编码:" + excetpion.getErrorCode() + ",异常发生地:" + latestUsableStack);
}
} finally {
MDC.remove("code");
}
return CommonResponse.ofFail(excetpion);
}
}
这样,咱们的项目运行起来就会有两个@ControllerAdvice的异常处理器,那到底会用哪个呢?
- 没有打@Order注解
因为没有打@Order注解,所以就取决于scan包的顺序了。在config项目的applicationContext中,看到了这样的配置
<context:component-scan base-package="com.yonyoucloud,com.yonyou">
<context:exclude-filter type="assignable" expression="org.imeta.orm.base.BizObject"/>
<context:exclude-filter type="regex" expression="com.yonyou.ucf.mdd.ext.controller.FileUpload"/>
<context:exclude-filter type="regex"
expression="com.yonyou.ucf.mdd.ext.portal.controller.PortalLayoutController"/>
<context:exclude-filter type="regex" expression="com.yonyoucloud.uretail.*"/>
<context:exclude-filter type="regex" expression="com.yonyou.ucf.mdd.core.file.oss.*"/>
<context:exclude-filter type="regex" expression="com.yonyou.ucf.mdd.poi.*"/>
<context:exclude-filter type="regex" expression="com.yonyou.ucf.mdd.conf.*" />
<context:exclude-filter type="regex" expression="com.yonyou.ucf.mdd.dao.*" />
<context:exclude-filter type="regex" expression="com.yonyou.ucf.mdd.rule.context.*" />
<context:exclude-filter type="regex" expression="com.yonyou.ucf.mdd.uimeta.context.*" />
</context:component-scan>
咱们几个项目的表现不同,就是因为base-package的配置顺序不同导致的。导致会使用不同的全局异常处理器