zoukankan      html  css  js  c++  java
  • 第一节:SpringMVC 异常处理

    一、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 中的异常处理器:

    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));

        

         

      最终生效的异常处理器

    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  中装配的异常解析器:

    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;
        }
  • 相关阅读:
    Generative Adversarial Nets
    【 剑指Offer 1 】数据结构
    Hopfield神经网络
    LSTMs 长短期记忆网络系列
    【 记忆网络 2 】 End-to-End Memory Network
    MessagePack Java Jackson Dataformat
    MessagePack Java 0.6.X 动态类型
    MessagePack Java 0.6.X 可选字段
    MessagePack Java 0.6.X 不使用注解(annotations)来序列化
    MessagePack Java 0.6.X List, Map 对象的序列化和反序列化
  • 原文地址:https://www.cnblogs.com/niujifei/p/15650771.html
Copyright © 2011-2022 走看看