• Spring全局异常处理你可能不知道的那些事儿(细而全)


    异常处理主要分为两大块

    进入Controller层的:

    此部分可以借助Spring提供的全局异常处理机制来处理

    以及进入Controller之前的:

    如Filter中的异常,此部分异常无法到达Controller层,因此Spring提供的全局异常处理机制无法捕获。此部分处理有两种方式:网上大部分处理的方式为,在Filter中抛出异常的地方,重定向到指定的一个Controller层去,这样就可以借助Spring的全局异常处理器(@ControllerAdvice)来进行处理,此处不再赘述。主要谈的是第二种,即实现 ErrorController 接口的异常处理。

    1.全局异常处理

    借助Spring提供的注解 @ControllerAdvice 就能很轻松地实现,此处不多说。直接上现成实例,处理部分自定义异常外,几乎可直接使用

    1. import lombok.extern.slf4j.Slf4j;
    2. import org.apache.shiro.ShiroException;
    3. import org.apache.shiro.authz.AuthorizationException;
    4. import org.apache.shiro.authz.UnauthorizedException;
    5. import org.springframework.http.HttpStatus;
    6. import org.springframework.http.converter.HttpMessageNotReadableException;
    7. import org.springframework.validation.BindException;
    8. import org.springframework.validation.BindingResult;
    9. import org.springframework.validation.FieldError;
    10. import org.springframework.web.HttpRequestMethodNotSupportedException;
    11. import org.springframework.web.bind.MissingServletRequestParameterException;
    12. import org.springframework.web.bind.annotation.*;
    13. import org.springframework.web.method.HandlerMethod;
    14. import org.springframework.web.multipart.support.MissingServletRequestPartException;
    15. import org.springframework.web.servlet.ModelAndView;
    16. import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
    17. import javax.servlet.ServletException;
    18. import javax.servlet.http.HttpServletRequest;
    19. import javax.servlet.http.HttpServletResponse;
    20. import java.io.PrintWriter;
    21. import java.io.StringWriter;
    22. import java.util.*;
    23. /**
    24. * 系统全局异常处理
    25. *
    26. */
    27. @Slf4j
    28. @ControllerAdvice
    29. public class GlobalExceptionHandler {
    30. public static final String DEFAULT_ERROR_CODE = "E";
    31. private static final String DEFAULT_ERROR_MSG = "业务繁忙,请稍后再试";
    32. // 自定义的校验注解
    33. private final static Set CUSTOMER_VALID_ANNOTATION = new HashSet<>();
    34. /**
    35. * 参数校验异常处理
    36. *

    37. * 主要拦截 使用@Valid@Validated注解对参数校验后的字段的统一处理
    38. * 通过{@linkplain BindingResult}拿到错误信息,错了完成统一返回
    39. *
    40. * @param ex
    41. * @return
    42. */
    43. @ExceptionHandler(BindException.class)
    44. @ResponseBody
    45. @ResponseStatus(value = HttpStatus.OK)
    46. public ResultVO handleException(BindException ex) {
    47. log.error("请求参数错误", ex);
    48. BindingResult bindingResult = ex.getBindingResult();
    49. StringBuilder msg = new StringBuilder();
    50. for (FieldError fieldError : bindingResult.getFieldErrors()) {
    51. String annName = fieldError.getCode();
    52. if (!CUSTOMER_VALID_ANNOTATION.contains(annName)) {
    53. msg.append("[").append(fieldError.getField()).append("]");
    54. }
    55. msg.append(fieldError.getDefaultMessage()).append(" ");
    56. }
    57. return ResultVO.failed(ResultCode.P00002, msg.toString());
    58. }
    59. /**
    60. * 处理Servlet异常
    61. *
    62. * @param ex
    63. * @return
    64. */
    65. @ExceptionHandler(ServletException.class)
    66. @ResponseBody
    67. @ResponseStatus(value = HttpStatus.OK)
    68. public ResultVO handleServletException(ServletException ex) {
    69. log.error("请求方式异常", ex);
    70. // 文件为空
    71. if (ex instanceof MissingServletRequestPartException) {
    72. MissingServletRequestPartException e = (MissingServletRequestPartException) ex;
    73. String message = String.format("[%s]参数不能为空", e.getRequestPartName());
    74. return ResultVO.failed(ResultCode.P00000, message);
    75. }
    76. // 请求方式异常
    77. if (ex instanceof HttpRequestMethodNotSupportedException) {
    78. HttpRequestMethodNotSupportedException e = (HttpRequestMethodNotSupportedException) ex;
    79. List supportMethods = Arrays.asList(Optional.ofNullable(e.getSupportedMethods()).orElse(new String[0]));
    80. String message = String.format("不支持[%s]请求方式,仅支持%s", e.getMethod(), supportMethods);
    81. return ResultVO.failed(ResultCode.B00000, message);
    82. }
    83. // 参数错误
    84. if (ex instanceof MissingServletRequestParameterException) {
    85. MissingServletRequestParameterException e = (MissingServletRequestParameterException) ex;
    86. String message = String.format("[%s{%s}]不能为空", e.getParameterName(), e.getParameterType());
    87. return ResultVO.failed(ResultCode.B00000, message);
    88. }
    89. return ResultVO.failed(ResultCode.E, DEFAULT_ERROR_MSG);
    90. }
    91. /**
    92. * shiro异常处理
    93. */
    94. @ExceptionHandler(ShiroException.class)
    95. @ResponseBody
    96. @ResponseStatus(value = HttpStatus.OK)
    97. public ResultVO handleShiroException(ShiroException ex) {
    98. String message;
    99. if (ex instanceof UnauthorizedException) {
    100. message = "无权限操作";
    101. log.error("无权限操作 - {}", message, ex);
    102. } else if (ex instanceof AuthorizationException) {
    103. message = "无权限操作";
    104. log.error("权限认证异常 - {}", message, ex);
    105. } else {
    106. message = ex.getMessage();
    107. log.error("权限认证异常 - {}", message, ex);
    108. }
    109. return ResultVO.failed(ResultCode.U00004, message);
    110. }
    111. /**
    112. * 参数格式异常处理
    113. *
    114. * @param ex
    115. * @return
    116. */
    117. @ExceptionHandler(HttpMessageNotReadableException.class)
    118. @ResponseBody
    119. @ResponseStatus(value = HttpStatus.OK)
    120. public ResultVO handleHttpMessageNotReadableException(HttpMessageNotReadableException ex) {
    121. String message = ex.getMessage();
    122. log.error("参数格式错误:{}", message, ex);
    123. return ResultVO.failed(ResultCode.P00004);
    124. }
    125. /**
    126. * 参数格式异常处理
    127. *
    128. * @param ex
    129. * @return
    130. */
    131. @ExceptionHandler({IllegalStateException.class})
    132. @ResponseBody
    133. @ResponseStatus(value = HttpStatus.OK)
    134. public ResultVO handleIllegalStateException(IllegalStateException ex) {
    135. Throwable cause;
    136. String message = null;
    137. if ((cause = ex.getCause()) != null) {
    138. message = cause.getMessage();
    139. }
    140. message = message == null ? ex.getMessage() : message;
    141. log.error("请求异常:{}", message, ex);
    142. return ResultVO.failed(ResultCode.B00000, message);
    143. }
    144. /**
    145. * 业务异常处理
    146. *
    147. * @param ex
    148. * @return
    149. */
    150. @ExceptionHandler({BusinessException.class, PlatformException.class})
    151. @ResponseBody
    152. @ResponseStatus(value = HttpStatus.OK)
    153. public ResultVO handleHttpMessageNotReadableException(PlatformException ex) {
    154. String message = ex.getMessage();
    155. log.error("参数格式错误:{}", message, ex);
    156. return ResultVO.failed(ex.getResultCode(), message);
    157. }
    158. /**
    159. * 其他全局异常处理
    160. *
    161. * @param request
    162. * @param response
    163. * @param ex
    164. * @param handle
    165. * @return
    166. */
    167. @ExceptionHandler(Throwable.class)
    168. @ResponseStatus(value = HttpStatus.OK)
    169. public ModelAndView handleException(HttpServletRequest request, HttpServletResponse response, Throwable ex, HandlerMethod handle) {
    170. ModelAndView modelAndView;
    171. if (handle.getBean().getClass().isAnnotationPresent(RestController.class) || handle.hasMethodAnnotation(ResponseBody.class)) {
    172. modelAndView = new ModelAndView(new MappingJackson2JsonView());
    173. this.handleSpecialException(ex, handle, modelAndView);
    174. modelAndView.addObject("data", null);
    175. } else {
    176. modelAndView = new ModelAndView();
    177. modelAndView.setViewName("error/error");
    178. this.handleSpecialException(ex, handle, modelAndView);
    179. PrintWriter writer = new PrintWriter(new StringWriter());
    180. ex.printStackTrace(writer);
    181. }
    182. return modelAndView;
    183. }
    184. private void handleSpecialException(Throwable e, HandlerMethod handle, ModelAndView modelAndView) {
    185. log.error("全局异常", e);
    186. modelAndView.addObject("code", DEFAULT_ERROR_CODE);
    187. modelAndView.addObject("message", DEFAULT_ERROR_MSG);
    188. }
    189. }

    2.全局异常无法处理的异常处理(Filter中Controller之前的异常)本案例基本可开箱即用

    此部分为重头戏,因为在网上很少找到资料,或者找到的都是只言片语,要么无法使用,要么不全面。

    SPringBoot官方已提供了一种处理机制,参考 org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration

    ,采用@Bean的方式注入:

    SpringBoot官方实现示例

    package org.springframework.boot.autoconfigure.web.servlet.error;
    
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
    // Load before the main WebMvcAutoConfiguration so that the error View is available
    @AutoConfigureBefore(WebMvcAutoConfiguration.class)
    @EnableConfigurationProperties({ ServerProperties.class, WebMvcProperties.class })
    public class ErrorMvcAutoConfiguration {
    
       private final ServerProperties serverProperties;
    
       public ErrorMvcAutoConfiguration(ServerProperties serverProperties) {
          this.serverProperties = serverProperties;
       }
    
       @Bean
       @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
       public DefaultErrorAttributes errorAttributes() {
          return new DefaultErrorAttributes();
       }
    
       @Bean
       @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
       public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
             ObjectProvider errorViewResolvers) {
          return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
                errorViewResolvers.orderedStream().collect(Collectors.toList()));
       }

      //......

    }

    org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController,为什么采用这种方式,是因为要注入额外的3个属性:

    1. org.springframework.boot.web.servlet.error.ErrorAttributes errorAttributes,
    2. org.springframework.beans.factory.ObjectProvider errorViewResolvers;
    3. org.springframework.boot.autoconfigure.web.ServerProperties serverProperties
    4. //注入serverProperties的目的是为了获得ErrorProperties
    5. org.springframework.boot.autoconfigure.web.ErrorProperties errorProperties = serverProperties.getError()

    这就是为什么不能直接使用 @Conponent 只能的注入了,因为 ErrorProperties ErrorAttributes 无法使用有参构造或@Autowired注入。

    说清楚了。参考官方的注入BasicErrorController的方式,注入我们自定义的Bean

    第一步:定义继承自BasicErrorController的实现处理类(为了复用别人已经实现了的code,也可以实现实现接口 org.springframework.boot.web.servlet.error.ErrorController

    1. import lombok.extern.slf4j.Slf4j;
    2. import org.springframework.boot.autoconfigure.web.ErrorProperties;
    3. import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
    4. import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
    5. import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
    6. import org.springframework.boot.web.error.ErrorAttributeOptions;
    7. import org.springframework.boot.web.servlet.error.ErrorAttributes;
    8. import org.springframework.http.HttpStatus;
    9. import org.springframework.http.MediaType;
    10. import org.springframework.http.ResponseEntity;
    11. import org.springframework.web.bind.annotation.RequestMapping;
    12. import org.springframework.web.context.request.ServletWebRequest;
    13. import org.springframework.web.context.request.WebRequest;
    14. import org.springframework.web.servlet.ModelAndView;
    15. import xin.cosmos.basic.define.ResultVO;
    16. import xin.cosmos.basic.exception.BusinessException;
    17. import xin.cosmos.basic.exception.PlatformException;
    18. import javax.servlet.http.HttpServletRequest;
    19. import javax.servlet.http.HttpServletResponse;
    20. import java.util.HashMap;
    21. import java.util.LinkedHashMap;
    22. import java.util.List;
    23. import java.util.Map;
    24. /**
    25. * 过滤器/servlet异常处理
    26. *

    27. * 主要处理经过过滤器但尚未到达controller的异常
    28. *

    29. * {@linkplain GlobalExceptionHandler} 全局异常处理没法处理过虑器中抛出的异常
    30. * 和执行顺序有关:
    31. *

    32. * filter -> interceptor -> controllerAdvice -> aspect -> controller
    33. *

    34. * 当controller返回异常时,也会按照controller -> aspect -> controllerAdvice -> interceptor -> filter来依次抛出
    35. *

    36. * 注意:此方法不能直接在类上使用{@linkplain org.springframework.stereotype.Controller}
    37. *

    38. * 或 {@linkplain org.springframework.web.bind.annotation.RestController} 标注,
    39. *

    40. * 原因是{@linkplain ErrorProperties}和{@linkplain ErrorAttributes}无法注入。
    41. *

    42. * 采用如同SpringBoot注入{@linkplain BasicErrorController}的注入方式一样,采用{@linkplain org.springframework.context.annotation.Bean}的方式注入。
    43. *

    44. * 可参考{@linkplain ErrorMvcAutoConfiguration}
    45. *

    46. * 该类在{@linkplain xin.cosmos.basic.config.ServletErrorConfiguration}中注入spring容器
    47. */
    48. @Slf4j
    49. @RequestMapping("${server.error.path:${error.path:/error}}")
    50. public class ServletErrorHandler extends BasicErrorController {
    51. private static final String CODE_NAME = "code";
    52. private static final String MSG_NAME = "message";
    53. private static final String DATA_NAME = "data";
    54. public ServletErrorHandler(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List errorViewResolvers) {
    55. super(errorAttributes, errorProperties, errorViewResolvers);
    56. }
    57. @Override
    58. public ResponseEntity> error(HttpServletRequest request) {
    59. HttpStatus status = getStatus(request);
    60. if (status == HttpStatus.NO_CONTENT) {
    61. return new ResponseEntity<>(status);
    62. }
    63. WebRequest webRequest = new ServletWebRequest(request);
    64. final Map body = getErrorAttributes(request, getAllErrorAttributeOptions());
    65. log.error("Request Path: {}, Servlet Error: {}", body.get("path"), body.get("trace"));
    66. // 异常错误处理
    67. ResultVO defaultError = ResultVO.failed((String) body.get("error"));
    68. Map errorMap = new LinkedHashMap<>();
    69. String exception = (String) body.get("exception");
    70. if (PlatformException.class.getTypeName().equals(exception) || BusinessException.class.getTypeName().equals(exception)) {
    71. errorMap.put(CODE_NAME, defaultError.getCode());
    72. errorMap.put(MSG_NAME, body.get("message"));
    73. errorMap.put(DATA_NAME, defaultError.getData());
    74. return new ResponseEntity<>(errorMap, HttpStatus.OK);
    75. }
    76. errorMap.put(CODE_NAME, defaultError.getCode());
    77. errorMap.put(MSG_NAME, defaultError.getMessage());
    78. errorMap.put(DATA_NAME, defaultError.getData());
    79. return new ResponseEntity<>(errorMap, HttpStatus.OK);
    80. }
    81. @Override
    82. public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
    83. HttpStatus status = getStatus(request);
    84. Map model = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML));
    85. Map map = new HashMap<>();
    86. map.put("title", status.getReasonPhrase() + "page");
    87. map.put("code", model.get("status"));
    88. map.put("message", model.get("error"));
    89. response.setStatus(status.value());
    90. log.error("{}", model);
    91. return new ModelAndView("error/error", map, HttpStatus.OK);
    92. }
    93. ErrorAttributeOptions getAllErrorAttributeOptions() {
    94. return ErrorAttributeOptions.of(ErrorAttributeOptions.Include.EXCEPTION, ErrorAttributeOptions.Include.MESSAGE, ErrorAttributeOptions.Include.BINDING_ERRORS, ErrorAttributeOptions.Include.STACK_TRACE);
    95. }
    96. }
    97. 第二步:采用@Bean的方式将定义的类注入Spring容器(此处参考SpringBoot的实现方式,避免踩坑)

      1. import org.springframework.beans.factory.ObjectProvider;
      2. import org.springframework.boot.autoconfigure.AutoConfigureBefore;
      3. import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
      4. import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
      5. import org.springframework.boot.autoconfigure.web.ServerProperties;
      6. import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
      7. import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
      8. import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
      9. import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
      10. import org.springframework.boot.context.properties.EnableConfigurationProperties;
      11. import org.springframework.boot.web.servlet.error.ErrorAttributes;
      12. import org.springframework.context.annotation.Bean;
      13. import org.springframework.context.annotation.Configuration;
      14. import org.springframework.web.servlet.DispatcherServlet;
      15. import xin.cosmos.basic.handler.ServletErrorHandler;
      16. import javax.servlet.Servlet;
      17. import java.util.stream.Collectors;
      18. /**
      19. * 自定义Servlet异常处理配置类
      20. * 注入Bean的形式参考{@linkplain ErrorMvcAutoConfiguration}
      21. */
      22. @Configuration(proxyBeanMethods = false)
      23. @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
      24. @ConditionalOnClass({Servlet.class, DispatcherServlet.class})
      25. // 在主WebMvcAutoConfiguration之前加载,以便错误视图可用
      26. @AutoConfigureBefore(WebMvcAutoConfiguration.class)
      27. @EnableConfigurationProperties({ServerProperties.class, WebMvcProperties.class})
      28. public class ServletErrorConfiguration {
      29. private final ServerProperties serverProperties;
      30. public ServletErrorConfiguration(ServerProperties serverProperties) {
      31. this.serverProperties = serverProperties;
      32. }
      33. /**
      34. * Servlet自定义异常处理器
      35. * @param errorAttributes
      36. * @param errorViewResolvers
      37. * @return
      38. */
      39. @Bean
      40. public ServletErrorHandler servletFilterErrorController(
      41. ErrorAttributes errorAttributes,
      42. ObjectProvider errorViewResolvers) {
      43. return new ServletErrorHandler(errorAttributes,
      44. this.serverProperties.getError(),
      45. errorViewResolvers.orderedStream().collect(Collectors.toList()));
      46. }
      47. }

      至此,已大功告成。如有不足之处,希望评论区留言。

    98. 相关阅读:
      网络安全副业如何年入数十万 (如何让你的副业超过主页)
      每周电子W4——电路与电路模型
      基于FME Desktop和FME Server的数据增量自动更新
      python+pytest接口自动化(15)-日志管理模块loguru简介
      ConcurrentLinkedQueue解析
      操作系统(Operating System)知识点复习——第八章 虚拟内存
      七夕礼物送什么给男朋友好?七夕礼物清单
      2311rust,到46版本更新
      Columbus:一个基于API实现的子域名发现服务工具
      【Docker 内核详解】namespace 资源隔离(五):User namespaces
    99. 原文地址:https://blog.csdn.net/gengzhy/article/details/126820906