Hey, Nice to meet You. 

必有过人之节.人情有所不能忍者,匹夫见辱,拔剑而起,挺身而斗,此不足为勇也,天下有大勇者,猝然临之而不惊,无故加之而不怒.此其所挟持者甚大,而其志甚远也.          ☆☆☆所谓豪杰之士,

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=""/>

posted @ 2021-05-21 17:31  唐浩荣  阅读(1309)  评论(0编辑  收藏  举报