• SpringMVC 异常处理


    我们知道进行异常处理是 Controller 层的职责之一,但在每一个处理方法里使用try...catch块感觉又不太聪明的亚子...在 SpringMVC 中有 4 种常用方式,可以对异常进行统一处理:

    • 使用 SpringMVC 预定义的SimpleMappingExceptionResolver,发生指定异常后跳转页面;
    • 实现HandlerExceptionResolver,自定义异常处理器;
    • 使用注解@ExceptionHandler
    • 使用注解@ControllerAdvice配合@ExceptionHandler

    下面来说说这 4 种方法的具体使用。

    1. 配置 SimpleMappingExceptionResolver

    只需要在 SpringMVC 配置文件中注册该异常处理器 Bean 即可,没有 id 属性,无需显式调用或被注入给其它 Bean。它对异常的处理方式仅限于跳转到指定的响应页面,在页面中进行处理。

    示例:

    // 一个自定义的异常类
    public class MemberException extends Exception {
        public MemberException() {
            super();
        }
        public MemberException(String msg) {
            super(msg);
        }
    }
    

    配置指定异常和需要跳转的页面:

    <!-- SpirngMVC配置文件 -->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <prop key="cn.cna.exception.MemberException">/errors/memberErrors.jsp</prop>
                <!-- 当请求参数的值与接收该参数的处理器方法形参的类型不匹配时,会抛出类型匹配异常TypeMismatchException -->
                <prop key="org.springframework.beans.TypeMismatchException">/errors/typeException.jsp</prop>
            </props>
        </property>
        <property name="defaultErrorView" value="/errors/defaultErrors.jsp"/>
        <property name="exceptionAttribute" value="ex"/>
    </bean>
    

    属性解释:

    • exceptionMappings:Properties 类型属性,用于指定不同类型的异常所对应的响应页面,Key 为异常类的全限定类名,value 为响应页面路径;
    • defaultErrorView:指定默认的异常响应页面,若发生的异常没有在 exceptionMappings 中指定,则使用该页面;
    • exceptionAttribute:捕获到的异常对象,可以在异常响应页面中使用。

    这样,Controller 方法可以不用显式进行异常处理,而是通过添加throws异常声明,将异常处理的职责交给上层调用者,进而转交给注册好的 SimpleMappingExceptionResolver,根据不同的异常实例进行不同的页面响应。

    2. 实现 HandlerExceptionResolver

    使用第 1 种方法可以实现发生指定异常后的跳转,但如果想要在捕获异常后执行其他的操作,就需要自己实现HandlerExceptionResolver接口,并同样在 SpringMVC 配置文件中注册。

    HandlerExceptionResolver只有一个resolveException方法需要我们去实现,来看看代码:

    public class MemberExceptionResolver implements HandlerExceptionResolver {
        @Override
        public ModelAndView resolveException(HttpServletRequest request, response httpServletResponse, Object handler, Exception ex) {
            ModelAndView mv = new ModelAndView();
            // 将异常对象加入数据模型中
            mv.addObject("ex",ex);
            // 设置默认错误响应页面
            mv.setViewName("/errors/defaultErrors.jsp");
            // 设置MemberException响应页面
            if (ex instanceof MemberException) {
                mv.setViewName("/errors/memberErrors.jsp");
            }
            // 其他...
            return mv;
        }
    }
    

    在 SpringMVC 配置文件中注册:

    <bean class="cn.cna.resolver.MyExceptionResolver"/>
    

    你可能有个疑问,如果项目采用的是前后端分离,需要返回 Json 数据又该如何做?有两个方法:

    1. 用 response 的 writer 直接输出 Json 数据
    @Override
    public ModelAndView resolveException(HttpServletRequest request, response httpServletResponse, Object handler, Exception ex) {
        // 避免中文乱码
        response.setContentType("application/json;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        try {
            // 为了简化演示,这里省略了对象转Json字符串的操作
            String jsonStr = "";
            if (ex instanceof MemberException) {
                response.setStatus(HttpStatus.OK.value());
                jsonStr = "{'code':1,'message':'业务异常'}";
           } else {
                response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
                jsonStr = "{'code':500,'message':'服务器未知异常'}";
            }
            response.getWriter().print(jsonStr);
            response.getWriter().flush();
            response.getWriter().close();  // 关闭了输出,确保后面不会再使用该response,比如拦截器
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;  // 后续不会有渲染步骤,但若还有处理器将继续执行
    }
    
    1. 借助 MappingJackson2JsonView

    MappingJackson2JsonView是一个 Json 视图,用法如下:

    @Override
    public ModelAndView resolveException(HttpServletRequest request, response httpServletResponse, Object handler, Exception ex) {
        ModelAndView mv = new ModelAndView();
        MappingJackson2JsonView view = new MappingJackson2JsonView();
        view.setJsonPrefix("fsxJson"); // 设置 Json 前缀
        // view.setModelKey(); 只序列化指定的key
        mv.setView(view);
        // 添加key-value非常方便
        mv.addObject("code", "1");
        mv.addObject("message", "业务异常");
        return mv;
    }
    

    详细可见:@ExceptionHandler or HandlerExceptionResolver?如何优雅处理全局异常?【享学Spring MVC】,YourBatman,CSDN,写得非常深入的一篇文章,值得好好研究。

    3. 使用 @ExceptionHandler

    @ExceptionHandler是 Spring 3.0 后提供的处理异常的注解,目的在于对 REST 应用提供更多的支持(是的,ModelAndView不再是必须的)。被@ExceptionHandler标注的方法将成为一个异常处理器,通过value属性(类型是一个Class<?>数组)指定该方法所要匹配的异常。

    // org.springframework.web.bind.annotation.ExceptionHandler
    
    @Target(ElementType.METHOD) // 只能标注在方法上
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface ExceptionHandler {
        // 指定异常类型,可以多个
        Class<? extends Throwable>[] value() default {};
    }
    

    使用:

    //MemberException异常处理
    @ExceptionHandler(MemberException.class)
    public ApiResult handleMemberException(MemberException ex){
        return ApiResult.error().setMessage(ex.getMessage());
    }
    

    但是请注意,被@ExceptionHandler注解的方法只能处理同一个 Controller 中的其他方法产生的异常。一般会将异常处理方法专门定义在一个 Controller 中,让其它 Controller 继承它。但是 Java 是单继承的呀,如果基类 Controller 中还需要封装其他逻辑,就会显得臃肿,职责也不够明确。

    @ControllerAdvice + @ExceptionHandler

    @ControllerAdvice是 Spring3.2 提供的新注解,基于 AOP 思想,可以指定一个 Controller 增强器,配合@ExceptionHandler可用于全局的异常处理,现在不必纠结于继承了。

    // 全局异常处理
    // 如果是REST应用,就使用@RestControllerAdvice,不用给每个方法写@ResponseBody
    @ControllerAdvice
    public class GlobalExceptionHandler {
        @ResponseBody
        @ExceptionHandler(MemberException.class)
        public ApiResult handleMemberException(MemberException ex) {
            return ApiResult.error().setMessage(ex.getMessage());
        }
        // 添加其他异常处理方法...
    }
    

    5. 关于优先级

    从高到低:

    • @Controller + @ExceptionHandler
    • @ControllerAdvice + @ExceptionHandler
    • HandlerExceptionResolver

    三种方式并存的情况下,优先级越高的先选择,被捕获处理了的异常与后面无关,没有被捕获的就会继续传递。

    为了避免在处理异常的过程中又产生异常的情况,一个好的办法是设计一个 HandlerExceptionResolver 放在末位,用最不会出 bug 的代码来处理一切前面不能处理的异常。

    END

    参考:
    springmvc异常处理,JS_HCX,简书
    @ControllerAdvice实现优雅地处理异常,KEN DO EVERTHING,CSDN
    Spring 异常处理三种方式,喜欢日向雏田一样的女子啊,CSDN

  • 相关阅读:
    查准率(precision)和查全率(recall)
    数据集大全:25个深度学习的开放数据集
    利用贝叶斯算法实现手写体识别(Python)
    KNN算法识别手写数字
    判断点在直线的左右哪一侧
    多节点bigchaindb集群部署
    java 多线程 3 synchronized 同步
    java 多线程 1 “常用的实现多线程的2种方式”:Thread 和 Runnable
    java 字符串
    java 关键字static
  • 原文地址:https://www.cnblogs.com/zzzt20/p/12466491.html
Copyright © 2020-2023  润新知