统一异常处理概述

spring mvc中如何做统一异常处理

先加载的@ControllerAdvice类里如果存在@ExceptionHandler(xxException.class)是需要捕获的异常或其父类,则将使用先加载的类中的异常处理方式。如果没有,则看后面的@ControllerAdvice类里是否有。
可以使用@Order来决定加载优先级,网上也有说法可以使用@Primary,暂未自测,个人觉得应该也是可行的。

如果有多个统一异常处理怎么办

  1. 使用basePackageClasses属性,将想要优先加载的包写在前面;
  2. 使用@Order注解来决定顺序
  3. 使用excludeFilters属性排除不想要加载的类,该属性使用方式多样,可自行搜索查看。(例:@ComponentScan(excludeFilters = @ComponentScan.Filter( type = FilterType.ASSIGNABLE_TYPE, classes = xx.class))

项目中的统一异常处理

  1. package com.yonyoucloud.manufacturing.web;
  2. import com.yonyou.ucf.mdd.ext.util.ResultMessage;
  3. import org.imeta.biz.base.BizException;
  4. import org.springframework.http.HttpStatus;
  5. import org.springframework.http.MediaType;
  6. import org.springframework.http.ResponseEntity;
  7. import org.springframework.web.bind.annotation.*;
  8. import org.springframework.web.servlet.NoHandlerFoundException;
  9. import javax.servlet.http.HttpServletRequest;
  10. @ControllerAdvice
  11. public class ErrorHandler {
  12. @ExceptionHandler(BizException.class)
  13. @ResponseBody
  14. ResponseEntity<?> handleBizException(HttpServletRequest request, Throwable ex) {
  15. //HttpStatus status = getStatus(request);
  16. return errResponse(ResultMessage.error(ex.getMessage()), HttpStatus.OK);
  17. }
  18. @ExceptionHandler(NoHandlerFoundException.class)
  19. @ResponseBody
  20. ResponseEntity<?> handleNotFoundException(HttpServletRequest request, Throwable ex) {
  21. //HttpStatus status = getStatus(request);
  22. return errResponse(ResultMessage.error(String.format("接口无效:%s", request.getRequestURI())), HttpStatus.OK);
  23. }
  24. @ExceptionHandler(Throwable.class)
  25. @ResponseBody
  26. ResponseEntity<?> handleCommonException(HttpServletRequest request, Throwable ex) {
  27. HttpStatus status = getStatus(request);
  28. return errResponse(ResultMessage.error(ex.getMessage()), status);
  29. }
  30. private HttpStatus getStatus(HttpServletRequest request) {
  31. Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
  32. if (statusCode == null) {
  33. return HttpStatus.INTERNAL_SERVER_ERROR;
  34. }
  35. return HttpStatus.valueOf(statusCode);
  36. }
  37. private ResponseEntity<?> errResponse(String message, HttpStatus status) {
  38. return ResponseEntity.status(status).contentType(MediaType.APPLICATION_JSON_UTF8)
  39. .body(message);
  40. }
  41. }

平台的统一异常处理

mdd框架中也有类似的处理

  1. //
  2. // Source code recreated from a .class file by IntelliJ IDEA
  3. // (powered by Fernflower decompiler)
  4. //
  5. package com.yonyou.iuap.ucf.core.exception;
  6. import com.yonyou.iuap.ucf.common.exception.BusinessException;
  7. import com.yonyou.iuap.ucf.common.exception.CommonExceptionConstants;
  8. import com.yonyou.iuap.ucf.common.rest.CommonResponse;
  9. import org.apache.commons.lang3.exception.ExceptionUtils;
  10. import org.slf4j.Logger;
  11. import org.slf4j.LoggerFactory;
  12. import org.slf4j.MDC;
  13. import org.springframework.beans.factory.annotation.Value;
  14. import org.springframework.web.bind.annotation.ControllerAdvice;
  15. import org.springframework.web.bind.annotation.ExceptionHandler;
  16. import org.springframework.web.bind.annotation.ResponseBody;
  17. @ControllerAdvice
  18. public class CommonExceptionHandler {
  19. private static final Logger log = LoggerFactory.getLogger(CommonExceptionHandler.class);
  20. @Value("${ucf.enableExcepthonHandler:true}")
  21. private String enableExcepthonHandler;
  22. public CommonExceptionHandler() {
  23. }
  24. @ExceptionHandler({Throwable.class})
  25. public CommonResponse handleThrowable(final Throwable e) throws Throwable {
  26. if (this.enableExcepthonHandler != null && !"true".equals(this.enableExcepthonHandler)) {
  27. throw e;
  28. } else {
  29. return this.handleBusinessException(new BusinessException(CommonExceptionConstants.UNKNOWN_EXCEPTION, e));
  30. }
  31. }
  32. @ExceptionHandler({BusinessException.class})
  33. @ResponseBody
  34. public CommonResponse handleBusinessException(final BusinessException excetpion) {
  35. try {
  36. MDC.put("code", String.valueOf(excetpion.getErrorCode()));
  37. if (excetpion.getCause() != null) {
  38. log.error("出现异常啦", excetpion);
  39. } else {
  40. String[] stackFrames = ExceptionUtils.getStackFrames(excetpion);
  41. String latestUsableStack = "";
  42. if (stackFrames != null && stackFrames.length > 1) {
  43. latestUsableStack = stackFrames[1];
  44. }
  45. log.error("出现异常啦:" + excetpion.getMessage() + ",异常编码:" + excetpion.getErrorCode() + ",异常发生地:" + latestUsableStack);
  46. }
  47. } finally {
  48. MDC.remove("code");
  49. }
  50. return CommonResponse.ofFail(excetpion);
  51. }
  52. }

这样,咱们的项目运行起来就会有两个@ControllerAdvice的异常处理器,那到底会用哪个呢?

  1. 没有打@Order注解
  2. 因为没有打@Order注解,所以就取决于scan包的顺序了。在config项目的applicationContext中,看到了这样的配置

    1. <context:component-scan base-package="com.yonyoucloud,com.yonyou">
    2. <context:exclude-filter type="assignable" expression="org.imeta.orm.base.BizObject"/>
    3. <context:exclude-filter type="regex" expression="com.yonyou.ucf.mdd.ext.controller.FileUpload"/>
    4. <context:exclude-filter type="regex"
    5. expression="com.yonyou.ucf.mdd.ext.portal.controller.PortalLayoutController"/>
    6. <context:exclude-filter type="regex" expression="com.yonyoucloud.uretail.*"/>
    7. <context:exclude-filter type="regex" expression="com.yonyou.ucf.mdd.core.file.oss.*"/>
    8. <context:exclude-filter type="regex" expression="com.yonyou.ucf.mdd.poi.*"/>
    9. <context:exclude-filter type="regex" expression="com.yonyou.ucf.mdd.conf.*" />
    10. <context:exclude-filter type="regex" expression="com.yonyou.ucf.mdd.dao.*" />
    11. <context:exclude-filter type="regex" expression="com.yonyou.ucf.mdd.rule.context.*" />
    12. <context:exclude-filter type="regex" expression="com.yonyou.ucf.mdd.uimeta.context.*" />
    13. </context:component-scan>

    咱们几个项目的表现不同,就是因为base-package的配置顺序不同导致的。导致会使用不同的全局异常处理器

参考文档

springboot多个@ControllerAdvice全局异常处理