随笔 - 1162  文章 - 0  评论 - 16  阅读 - 59万 

一、SpringMVC 异常处理

  1、SpringMVC 通过 HandlerExceptionResolver 处理程序的异常,包括 Handler 映射、数据绑定以及目标方法执行时发生的异常。
  2、SpringMVC 提供的 HandlerExceptionResolver 的实现类
    

 

    

 

二、HandlerExceptionResolver

  1、DispatcherServlet 默认装配的 HandlerExceptionResolver

    当自己没有配置异常处理器,也没有使用 <mvc:annotation-driven /> 配置,DispatcherServlet 会默认加载 DispatcherServlet.properties 中的 HandlerExceptionResolver:

    DispatcherServlet 加载异常处理器:

复制代码
    private void initHandlerExceptionResolvers(ApplicationContext context) {
        this.handlerExceptionResolvers = null;

        if (this.detectAllHandlerExceptionResolvers) {
            // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
            Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
                    .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
                // We keep HandlerExceptionResolvers in sorted order.
                OrderComparator.sort(this.handlerExceptionResolvers);
            }
        }
        else {
            try {
                HandlerExceptionResolver her =
                        context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
                this.handlerExceptionResolvers = Collections.singletonList(her);
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Ignore, no HandlerExceptionResolver is fine too.
            }
        }

        // Ensure we have at least some HandlerExceptionResolvers, by registering
        // default HandlerExceptionResolvers if no other resolvers are found.
        if (this.handlerExceptionResolvers == null) {
            this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class); //默认策略
            if (logger.isDebugEnabled()) {
                logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default");
            }
        }
    }
复制代码

 

    DispatcherServlet.properties 中的异常处理器:

1
2
3
org.springframework.web.servlet.HandlerExceptionResolver=
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

   

 

  2、使用了 <mvc:annotation-driven /> 配置

    当使用了 <mvc:annotation-driven /> 标签后,会自动注入更多的组件,其中关于异常处理器组件如下:

        parserContext.registerComponent(new BeanComponentDefinition(exceptionHandlerExceptionResolver, methodExceptionResolverName));
        parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
        parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));

 

    

     

 

  最终生效的异常处理器

1
2
3
ExceptionHandlerExceptionResolver
ResponseStatusExceptionResolver
DefaultHandlerExceptionResolver

  

 

三、SpringMVC 异常处理流程

  从页面发起一个请求,通过传入的参数来触发运行时异常:

  页面请求:(可以从地址栏动态传参)

<a href="${ctp}/handle01?i=10">test01</a>

 

  控制器方法:(如果参数为0,就会有异常)

复制代码
    @RequestMapping(value = "/handle01")
    public String handle01(Integer i) {
        System.out.println("handle01");
        System.out.println(10 / i);
        return "success";
    }
复制代码

 

  开启SpringMVC高级功能:

    <mvc:annotation-driven/>

 

  异常处理流程:

  来到 DispatcherServlet 中 dispatch() 方法:

复制代码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HttpServletRequest processedRequest = request;
   HandlerExecutionChain mappedHandler = null;
   boolean multipartRequestParsed = false;


   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);


   try {
      ModelAndView mv = null;
      Exception dispatchException = null;


      try {
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);


         // Determine handler for the current request.
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null || mappedHandler.getHandler() == null) {
            noHandlerFound(processedRequest, response);
            return;
         }


         // Determine handler adapter for the current request.
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());


         // Process last-modified header, if supported by the handler.
         String method = request.getMethod();
         boolean isGet = "GET".equals(method);
         if (isGet || "HEAD".equals(method)) {
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
            if (logger.isDebugEnabled()) {
               logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
            }
            if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
               return;
            }
         }


         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }


         // Actually invoke the handler. 
         // ① 执行目标方法,如果没有异常正常运行;
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());


         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }


         applyDefaultViewName(request, mv);
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
         dispatchException = ex;
      }
      // ② 处理结果(可能会有异常)
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   // ⑦ 异常解析器不能处理异常,执行完 afterCompletion后,抛出异常,抛给Tomcat
   catch (Exception ex) {
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   catch (Error err) {
      triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
   }
   finally {
      if (asyncManager.isConcurrentHandlingStarted()) {
         // Instead of postHandle and afterCompletion
         if (mappedHandler != null) {
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
         }
      }
      else {
         // Clean up any resources used by a multipart request.
         if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
         }
      }
   }
}
复制代码

 

   processDispatchResult() 方法:

复制代码
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
      HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {


   boolean errorView = false;

   // ③ 如果有异常就处理异常
   if (exception != null) { //如果有异常
      if (exception instanceof ModelAndViewDefiningException) {
         logger.debug("ModelAndViewDefiningException encountered", exception);
         mv = ((ModelAndViewDefiningException) exception).getModelAndView();
      }
      else { //处理异常
         Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
         // ④ 使用异常解析器解析异常
         mv = processHandlerException(request, response, handler, exception);
         errorView = (mv != null);
      }
   }


   // Did the handler return a view to render?
   if (mv != null && !mv.wasCleared()) {
      //⑥ 页面渲染
      render(mv, request, response);
      if (errorView) {
         WebUtils.clearErrorRequestAttributes(request);
      }
   }
   else {
      if (logger.isDebugEnabled()) {
         logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
               "': assuming HandlerAdapter completed request handling");
      }
   }


   if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
      // Concurrent handling started during a forward
      return;
   }


   if (mappedHandler != null) {
      mappedHandler.triggerAfterCompletion(request, response, null);
   }
}
复制代码

 

   processHandlerException() 方法:

复制代码
所有的异常解析器尝试解析,解析完成进行后续,解析失败下一个解析器
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
      Object handler, Exception ex) throws Exception {


   // Check registered HandlerExceptionResolvers...
   ModelAndView exMv = null;
   //⑤ 遍历所有的异常解析器
   //如果异常解析器能够处理,返回 exMV,然后到 ⑥ 页面渲染
   //如果所有异常解析器不能够处理,直接抛出异常 ex,然后到 catch ⑦ 
   for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
      exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
      if (exMv != null) {
         break;
      }
   }
   if (exMv != null) {
      if (exMv.isEmpty()) {
         request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
         return null;
      }
      // We might still need view name translation for a plain error model...
      if (!exMv.hasView()) {
         exMv.setViewName(getDefaultViewName(request));
      }
      if (logger.isDebugEnabled()) {
         logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
      }
      WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
      return exMv;
   }


   throw ex;
}
复制代码

 

   如果异常解析器都不能处理就直接抛出去,抛给Tomcat了,显示错误页面。

  SpringMVC  中装配的异常解析器:

1
2
3
ExceptionHandlerExceptionResolver:处理标注 @ExceptionHandler
ResponseStatusExceptionResolver:处理标注 @ResponseStatus
DefaultHandlerExceptionResolver:判断是否SpringMVC自带的异常

  

 

四、ExceptionHandlerExceptionResolver 异常解析器

  1、定义异常处理方法

    可以在控制器中定义处理异常方法,告诉SpringMVC这个方法专门处理这个类发生的异常,可以使用 value 属性来指定要处理的异常类型。

    (1)给方法上随便写一个 Exception,用来接收发生的异常;

    (2)要携带异常信息不能给参数位置写 Model;

    (3)直接返回 ModelAndView,可以放进异常信息;

    示例:

复制代码
    @ExceptionHandler(value = {ArithmeticException.class, NullPointerException.class})
    public ModelAndView handleException01(Exception exception) {
        System.out.println("本类的:handleException01..." + exception);

        ModelAndView mv = new ModelAndView("MyError");
        //获取异常信息
        mv.addObject("ex", exception);

        //视图解析器拼串,自定义错误页面
        return mv;
    }
复制代码

 

    可以写多个处理异常的方法:

复制代码
    @ExceptionHandler(value = {Exception.class})
    public ModelAndView handleException02(Exception exception) {
        System.out.println("本类的:handleException02..." + exception);

        ModelAndView mv = new ModelAndView("MyError");
        //获取异常信息
        mv.addObject("ex", exception);

        //视图解析器拼串
        return mv;
    }
复制代码

 

    如果有多个 @ExceptionHandler 都能处理异常,精确匹配优先处理

 

  2、全局处理异常

    可以单独定义一个类来处理全部的异常情况:

复制代码
/**
 * 1、集中处理所有异常的类加入到 ioc 容器中
 *      https://blog.csdn.net/qq_40726812/article/details/115521502
 * 2、@ControllerAdvice 专门处理异常的类
 */
@ControllerAdvice
public class MyCenterException {

    @ExceptionHandler(value = {ArithmeticException.class})
    public ModelAndView handleException01(Exception exception) {
        System.out.println("全局的:handleException01..." + exception);

        ModelAndView mv = new ModelAndView("MyError");
        //获取异常信息
        mv.addObject("ex", exception);

        //视图解析器拼串
        return mv;
    }

    @ExceptionHandler(value = {Exception.class})
    public ModelAndView handleException02(Exception exception) {
        System.out.println("全局的:handleException02..." + exception);

        ModelAndView mv = new ModelAndView("MyError");
        //获取异常信息
        mv.addObject("ex", exception);

        //视图解析器拼串
        return mv;
    }
}
复制代码

 

  配置文件中开启 SpringMVC 高级功能:

<mvc:annotation-driven/>

 

  当同时配置了全局异常和本类异常处理同时存在,本类处理优先,本类中精确匹配优先。

 

  3、ExceptionHandlerExceptionResolver 异常解析器

  主要处理 Handler 中用 @ExceptionHandler 注解定义的方法;

  @ExceptionHandler 注解定义的方法优先级问题:例如发生的是NullPointerException,但是声明的异常有 RuntimeException 和 Exception,此候会根据异常的最近继承关系找到继承深度最浅的那个 @ExceptionHandler 注解方法,即标记了 RuntimeException 的方法;

  ExceptionHandlerMethodResolver 内部若找不到 @ExceptionHandler 注解的话,会找 @ControllerAdvice 中的 @ExceptionHandler 注解方法;

 

五、ResponseStatusExceptionResolver 异常解析器

  1、使用 @ResponseStatus

    这个注解标注在自定义异常类上。

    自定义异常类:

@ResponseStatus(reason = "用户被拒绝登录", value = HttpStatus.NOT_ACCEPTABLE)
public class UserNameNotFoundException extends RuntimeException{}

 

    页面请求:

<a href="${ctp}/handle02?username=admin111">handle02</a>

 

    控制器方法:

复制代码
    @RequestMapping(value = "/handle02")
    public String handle02(@RequestParam("username") String username) {
        if (!"admin".equals(username)) {
            System.out.println("登录失败!");
            throw new UserNameNotFoundException();
        }
        System.out.println("登录成功!");
        return "success";
    }
复制代码

 

    展现效果:

    

    

  2、ResponseStatusExceptionResolver 异常解析器

  在异常及异常父类中找到 @ResponseStatus 注解,然后使用这个注解的属性进行处理。

  定义一个 @ResponseStatus 注解修饰的异常类

  

  若在处理器方法中抛出了上述异常:若ExceptionHandlerExceptionResolver 不解析述异常。

  由于触发的异常 UnauthorizedException 带有@ResponseStatus 注解。因此会被 ResponseStatusExceptionResolver 解析到。最后响应HttpStatus.UNAUTHORIZED 代码给客户端。HttpStatus.UNAUTHORIZED 代表响应码401,无权限。

  关于其他的响应码请参考 HttpStatus 枚举类型源码。

 

六、DefaultHandlerExceptionResolver 异常解析器

  DefaultHandlerExceptionResolver:判断是否 SpringMVC 自带的异常。

  SpringMVC 自带的异常,如:HttpRequestMethodNotSupportedException ,如果没人处理,就使用 DefaultHandlerExceptionResolver 处理。

  SpringMVC 自带的异常:

 

NoSuchRequestHandlingMethodException、

HttpRequestMethodNotSupportedException、

HttpMediaTypeNotSupportedException、

HttpMediaTypeNotAcceptableException等。

 

 

  示例代码:

  页面请求:(get请求)

<a href="${ctp}/handle03">handle03</a>

 

  控制器方法:(post方式接收)

    @RequestMapping(value = "/handle03", method = RequestMethod.POST)
    public String handle03() {

        return "success";
    }

 

  会抛出异常信息

  

 

   然后用异常解析器来尝试解析,前两个都解析不了,使用 DefaultHandlerExceptionResolver 来解析。

  DefaultHandlerExceptionResolver 解析器:

复制代码
    @Override
    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
            Object handler, Exception ex) {

        try {
            if (ex instanceof NoSuchRequestHandlingMethodException) {
                return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, request, response,
                        handler);
            }
            else if (ex instanceof HttpRequestMethodNotSupportedException) {
                return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request,
                        response, handler);
            }
            else if (ex instanceof HttpMediaTypeNotSupportedException) {
                return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, request, response,
                        handler);
            }
            else if (ex instanceof HttpMediaTypeNotAcceptableException) {
                return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, request, response,
                        handler);
            }
            else if (ex instanceof MissingServletRequestParameterException) {
                return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, request,
                        response, handler);
            }
            else if (ex instanceof ServletRequestBindingException) {
                return handleServletRequestBindingException((ServletRequestBindingException) ex, request, response,
                        handler);
            }
            else if (ex instanceof ConversionNotSupportedException) {
                return handleConversionNotSupported((ConversionNotSupportedException) ex, request, response, handler);
            }
            else if (ex instanceof TypeMismatchException) {
                return handleTypeMismatch((TypeMismatchException) ex, request, response, handler);
            }
            else if (ex instanceof HttpMessageNotReadableException) {
                return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, request, response, handler);
            }
            else if (ex instanceof HttpMessageNotWritableException) {
                return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, request, response, handler);
            }
            else if (ex instanceof MethodArgumentNotValidException) {
                return handleMethodArgumentNotValidException((MethodArgumentNotValidException) ex, request, response, handler);
            }
            else if (ex instanceof MissingServletRequestPartException) {
                return handleMissingServletRequestPartException((MissingServletRequestPartException) ex, request, response, handler);
            }
            else if (ex instanceof BindException) {
                return handleBindException((BindException) ex, request, response, handler);
            }
            else if (ex instanceof NoHandlerFoundException) {
                return handleNoHandlerFoundException((NoHandlerFoundException) ex, request, response, handler);
            }
        }
        catch (Exception handlerException) {
            logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
        }
        return null;
    }
复制代码

 

  DefaultHandlerExceptionResolver 用来解析 SpringMVC 的自定义异常,这个解析器是对这些已经定义好的异常进行处理的,解析完毕后,来到错误页面。

  例如:

复制代码
    protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
            HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {

        pageNotFoundLogger.warn(ex.getMessage());
        String[] supportedMethods = ex.getSupportedMethods();
        if (supportedMethods != null) {
            response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
        }
        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
        return new ModelAndView();
    }
复制代码

  错误页面:

  

 

 

七、简单映射异常解析器 SimpleMappingExceptionResolver

  如果希望对所有的异常或自定义的异常进行统一处理,可以使用 SimpleMappingExceptionResolver,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。

  配置 SimpleMappingExceptionResolver:

复制代码
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <!-- exceptionMappings:配置哪些异常去哪些页面 属性是 Properties类型-->
        <property name="exceptionMappings">
            <!--自定义的异常与跳转的视图名-->
            <props>
                <!--key:异常全类名, value:视图页面视图名称 -->
                <prop key="java.lang.NullPointerException">error</prop>
                <prop  key="java.lang.ArithmeticException">error</prop>
            </props>
        </property>
        <!--指定错误信息取出使用的 key-->
        <property name="exceptionAttribute" value="ex"></property>
    </bean>
复制代码

 

  控制器方法:模拟空指针异常

复制代码
    @RequestMapping(value = "/handle04")
    public String handle04() {
        System.out.println("handle04 空指针异常");
        String str = null;
        System.out.println(str.charAt(0));
        return "success";
    }
复制代码

 

  异常页面:

复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h2>空指针异常!!!</h2>
<h2>出错信息:${ex } --- ${exception}</h2>
</body>
</html>
复制代码

 

 

  分析:

  当配置了 SimpleMappingExceptionResolver,会有四个异常解析器:

 

   发生异常后会根据异常解析器顺序进行解析,SimpleMappingExceptionResolver:

 

   解析完毕后会到错误页面进行渲染。

  异常解析器:自动将异常对象信息,存放到 request 范围内

复制代码
<!-- 配置SimpleMappingExceptionResolver异常解析器 --
<bean id="simpleMappingExceptionResolver"  class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <!-- exceptionAttribute 默认值(通过ModelAndView传递给页面):
             exception   ->  ${requestScope.exception}
             public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";
        -->
         <property name="exceptionAttribute" value="ex"></property>
         <property name="exceptionMappings">
              <props>
                  <prop  key="java.lang.NullPointerException">error</prop>
                  <prop  key="java.lang.ArithmeticException">error</prop>
              </props>
         </property>
     </bean>
复制代码

 

  源码分析 : SimpleMappingExceptionResolver L187/L339
  

 

 

复制代码
    @Override
    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
            Object handler, Exception ex) {

        // Expose ModelAndView for chosen error view.
        String viewName = determineViewName(ex, request);
        if (viewName != null) {
            // Apply HTTP status code for error views, if specified.
            // Only apply it if we're processing a top-level request.
            Integer statusCode = determineStatusCode(request, viewName);
            if (statusCode != null) {
                applyStatusCodeIfPossible(request, response, statusCode);
            }
            return getModelAndView(viewName, ex, request);
        }
        else {
            return null;
        }
    }

    protected ModelAndView getModelAndView(String viewName, Exception ex, HttpServletRequest request) {
        return getModelAndView(viewName, ex);
    }


    protected ModelAndView getModelAndView(String viewName, Exception ex) {
        ModelAndView mv = new ModelAndView(viewName);
        if (this.exceptionAttribute != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("Exposing Exception as model attribute '" + this.exceptionAttribute + "'");
            }
            mv.addObject(this.exceptionAttribute, ex);
        }
        return mv;
    }
复制代码

 

 

 

posted on   格物致知_Tony  阅读(391)  评论(0编辑  收藏  举报
编辑推荐:
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· 面试官:你是如何进行SQL调优的?
历史上的今天:
2020-12-06 Java 面向对象(十五)类的成员 之 内部类
2020-12-06 Java 面向对象(十四):接口
点击右上角即可分享
微信分享提示

目录导航