zoukankan      html  css  js  c++  java
  • SpringMVC入门学习(十六)----全局异常处理

    1、SpringMVC全局异常处理的四种方式

    在项目上线之后,往往会出现一些不可预料的异常信息,对于逻辑性或设计性问题,开发人员或者维护人员需要通过日志,查看异常信息并排除异常;而对于用户,则需要为其呈现出其可以理解的异常提示页面,让用户有一个良好的使用体验。所以异常的处理对于一个Web项目来说是非常重要的。Spring MVC提供了强大的异常处理机制。

    SpringMVC提供的异常处理主要有以下四种方式:

    • 使用 Spring MVC 提供的简单异常处理器 SimpleMappingExceptionResolver
    • 实现异常处理接口 HandlerExceptionResolver
    • 使用 @ExceptionHandler 注解实现异常处理
    • 使用 @ControllerAdvice + @ExceptionHandler注解

    注意:如果XML中也配置了相同的映射关系,那么SpringMVC会优先采纳基于注解的映射

    在SpringMVC中处理异常的推荐方式:使用 @ControllerAdvice + @ExceptionHandler 注解实现全局异常处理。

    2、通过SimpleMappingExceptionResolver实现

    SimpleMappingExceptionResolver异常处理器是SpringMVC定义好的异常处理器。使用 SimpleMappingExceptionResolver 进行异常处理的优缺点:

    • 优点:集成简单、有良好的扩展性、对已有代码没有入侵性等
    • 缺点:该方法仅能获取到异常信息,若在出现异常时,对需要获取除异常以外的数据的情况不适用。

    下面在SpringMVC的XML文件中配置 SimpleMappingExceptionResolver 对象,配置如下。

    <!-- 配置异常映射 -->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" id="exceptionResolver">
        <!-- 指定默认的异常响应页面。若发生的异常不是exceptionMappings中指定的异常,则使用默认异常响应页面。 -->
        <property name="defaultErrorView" value="error"></property>
        <!-- exceptionAttribute属性:设置将异常对象存入请求域时使用的属性名 -->
        <!-- 如果没有配置这个属性,那么默认使用"exception"作为属性名。源码中文档说明如下:
               * Set the name of the model attribute as which the exception should be exposed. Default is "exception". -->
        <property name="exceptionAttribute" value="exception"></property>
        <!-- 用于指定具体的不同类型的异常所对应的异常响应页面。 -->
        <property name="exceptionMappings">
            <props>
                <!-- key属性:指定异常类型 -->
                <!-- 文本标签体:指定和异常对应的逻辑视图名称 -->
                <prop key="java.lang.ArithmeticException">show-message</prop>
                <prop key="java.lang.RuntimeException">show-runtime-message</prop>
                <prop key="java.lang.Exception">show-exception-message</prop>
            </props>
        </property>
    </bean>
    

    上面配置了三个异常类型,那么它的匹配规则是什么呢?是从最大的异常开始,还是精确匹配呢?

    • 匹配规则1:如果异常对象能够在映射关系中找到精确匹配的规则,那么就执行这个精确匹配的规则
    • 匹配规则2:如果异常对象能够在映射关系中找到多个匹配的规则,优先采纳精确匹配的规则
    • 匹配规则3:如果异常对象能够在映射关系中找到多个匹配的规则,且没有精确匹配的,那么会采纳范围更接近的那个
    • 匹配规则4:如果注解中也配置了相同的映射关系,那么SpringMVC会优先采纳基于注解的映射

    结论:在整个SpringMVC全局异常处理中,当异常发生时,会优先采取精确匹配的规则,没有的话会采纳范围更接近的那个,其它的同理。


    下面创建模拟出现异常的Controller方法:

    @RequestMapping(value = "/exception")
    public String exceptionHandler(){
        // 模拟出现异常
        System.out.println(10 / 0);
        return "success";
    }
    

    用于展示异常信息的页面:

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>异常信息页面</title>
    </head>
    <body>
        <h1>系统信息</h1>
        异常对象:${requestScope.exception}<br/>
        异常消息:${requestScope.exception.message}<br/>
    </body>
    </html>
    

    测试的结果如下图所示:

    image

    3、通过实现HandlerExceptionResolver接口

    上面使用的是Spring MVC定义好的SimpleMappingExceptionResolver异常处理器,可以实现发生指定异常后跳转到指定的页面。但若要实现在捕获到指定异常时,执行一些额外操作它是完成不了的。此时,就需要自定义异常处理器,需要使用到HandlerExceptionResolver接口。

    首先新建一个自定义异常类 CustomException:

    /**
     * 自定义异常
     */
    public class CustomException extends Exception{
    
        private String message;
        
        public CustomException(String message) {
            super(message);
            this.message = message;
        }
        public String getMessage() {
            return message;
        }
        public void setMessage(String message) {
            this.message = message;
        }
    }
    

    然后创建一个实现 HandlerExceptionResolver 接口的实现类,并且实现其唯一的方法resolveException(),这种方式可以进行全局的异常处理。

    /**
     * 异常处理,通过实现HandlerExceptionResolver接口来实现
     */
    @Component
    public class GlobalException implements HandlerExceptionResolver {
    
        @Override
        public ModelAndView resolveException(HttpServletRequest httpServletRequest,
                                             HttpServletResponse httpServletResponse,
                                             Object o, Exception exception) {
            System.out.println(exception.getMessage());
            CustomException customException = null;
    
            //解析出异常类型,如果该异常类型是自定义的异常,则直接取出异常信息,否则用自定义异常输出一下
            if (exception instanceof CustomException){
                customException = (CustomException) exception;
            }else {
                customException = new CustomException("出现了未知的错误!!!");
            }
            // 获取错误信息
            String message = customException.getMessage();
            System.out.println(message);
            System.out.println("---------");
            // 将错误信息带到页面输出
            ModelAndView mv = new ModelAndView();
            mv.addObject("exception",customException);
            mv.setViewName("show-exception-message");
            return mv;
        }
    }
    

    resolveException方法的参数“Exception e”即为Controller或其下层抛出的异常。参数“Object o”就是处理器适配器要执行的Handler对象。resolveException方法的返回值类型是ModelAndView,也就是说,可以通过这个返回值类设置发出异常时显示的页面。

    4、使用 @ExceptionHandler注解

    @ExceptionHandler注解用来将一个方法标注为异常处理方法。该注解中只有一个可选的属性value,是一个Class<?>数组,用于指定该注解的方法所要处理的异常类,即所要匹配的异常。被该注解修饰的方法的返回值为异常处理后的跳转页面,其返回值可以是ModelAndView、String,或void;方法名随意,方法的参数可以是 Exception 及其子类对象、Model、HttpServletRequest、HttpServletResponse 等。系统会自动为这些方法参数赋值。

    @ExceptionHandler注解处理异常的作用域:单个类,只针对当前Controller。

    /**
     * 基于注解的异常处理
     */
    @Controller
    public class ExceptionController {
        @RequestMapping(value = "/exception1")
        public String exception1() {
            // 模拟出现异常
            System.out.println(10 / 0);
            return "success";
        }
    
        @RequestMapping(value = "/exception2")
        public void exception2() throws CustomException {
            // 模拟出现异常
            throw new CustomException("我抛出了一个异常!!!");
        }
    
        //处理自定义异常
        @ExceptionHandler({CustomException.class, ArithmeticException.class})
        public String exceptionHandler1(Exception e, Model model) {
            // 打印错误信息
            System.out.println(e.getMessage());
            e.printStackTrace();
            // 将错误数据存入请求域
            model.addAttribute("exception", e);
            return "show-annotation-message";
        }
    }
    

    上面的代码运行结果:

    image

    注意:如果在Controller中单独使用这个注解是有缺陷的,就是不能够全局处理异常,因为进行异常处理的方法必须与出错的方法在同一个Controller里面,也就是说每个Controller类中都要写一遍,所以实用性不高。

    解决方案:可以将处理异常的信息抽取出来放在一个BaseController,然后对需要处理异常的Controller继承该类即可。

    public class BaseController {
        //处理自定义异常
        @ExceptionHandler({CustomException.class, ArithmeticException.class})
        public String exceptionHandler1(Exception e, Model model) {
            // 打印错误信息
            System.out.println(e.getMessage());
            e.printStackTrace();
            // 将错误数据存入请求域
            model.addAttribute("exception", e);
            return "show-annotation-message";
        }
    }
    

    但是还是存在同样的问题,每个类都得继承它,可见这种方式同样不可取,所以一般使用下面这种方式:@ControllerAdvice和@ ExceptionHandle 注解配合使用。

    5、用 @ControllerAdvice+@ ExceptionHandler注解(推荐)

    上面说到 @ExceptionHandler注解标注的异常处理方法必须与出错的方法在同一个Controller里面,所以这种方式是只对应单个Controller类。那么此时有一种更好的解决方案:可以使用@ControllerAdvice+@ExceptionHandler注解来解决,这个是 Spring 3.2 带来的新特性。

    两者一起使用的作用域:全局异常处理,针对全部Controller中的指定异常类

    @ControllerAdvice和@ ExceptionHandler 这两个注解配合使用的代码如下:

    /**
     * 基于注解的异常处理  @ControllerAdvice+@ExceptionHandler
     */
    // 这个注解表示当前类是一个异常映射类
    @ControllerAdvice
    public class MyException {
    
        // 在@ExceptionHandler注解中指定异常类型
        @ExceptionHandler(value = {CustomException.class, ArithmeticException.class})
        public ModelAndView exceptionMapping(Exception exception) {// 方法形参位置接收SpringMVC捕获到的异常对象
    
            // 可以将异常对象存入模型;将展示异常信息的视图设置为逻辑视图名称
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("exception", exception);
            modelAndView.setViewName("show-annotation-message");
            // 打印一下信息
            System.out.println(exception.getMessage());
            return modelAndView;
        }
    }
    

    注:@ControllerAdvice 注解的内部是使用@Component 注解修饰的,可以点进源码查看运行:

    image

    @Component,@Service,@Controller,@Repository注解修饰的类,就是把这个类的对象交由Spring IOC容器来管理,相当于配置文件中的 <bean id="" class=""/>

    作者: 唐浩荣
    本文版权归作者和博客园共有,欢迎转载,但是转载需在博客的合适位置给出原文链接,否则保留追究法律责任的权利。
  • 相关阅读:
    常见寻找OEP脱壳的方法
    Windows内核原理系列01
    HDU 1025 Constructing Roads In JGShining's Kingdom
    HDU 1024 Max Sum Plus Plus
    HDU 1003 Max Sum
    HDU 1019 Least Common Multiple
    HDU 1018 Big Number
    HDU 1014 Uniform Generator
    HDU 1012 u Calculate e
    HDU 1005 Number Sequence
  • 原文地址:https://www.cnblogs.com/tanghaorong/p/14771435.html
Copyright © 2011-2022 走看看