zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Spring 全家桶之 Spring Web MVC(九)- Exception

SpringWebMVC 全家 Exception
2023-06-13 09:11:16 时间

一、Spring MVC 的异常处理流程

Spring MVC 中通过HandlerExceptionResolver处理程序的异常,包括Handler映射数据绑定以及木币方法执行时发生的异常

Spring MVC 提供了HandlerExceptionResolver的实现类

默认提供了三个HandlerExceptionResolver

在controller包中新增一个HandlerExceptionController

@Controller
public class HandlerExceptionController {

    @RequestMapping("/handler")
    public String handler(Integer x){
        int res = 10 / x;
        System.out.println(x);
        return "success";
    }
}

启动应用,在浏览器输入http://localhost:8080/handler?x=0

这个报错页面是由Tomcat提供的,并不是Spring MVC提供的。

在DispatcherServlet的doDispatch()方法的1067行打上断点,开启Debug模式,在浏览器输入http://localhost:8080/handler?x=0

1067行代码就是执行目标方法,并且此时dispatchException=null,此时没有异常,待目标方法执行后就是出现异常,点击Step Over进入异常处理

目标方法中的异常出现,并赋值各个dispatchException,继续点击Step Over,并Step Into 到processDispatchResult方法中

此时异常不为空,并且异常类型不是if条件中的异常类型所以会直接进入esle代码块中,继续Step Over 到processHandlerException方法执行的这一行,并且Step Into 到processHandlerException方法中,该方法返回一个ModelAndView类

进入Step Over,进入到for循环中

此时就出现了前面说的Spring MVC 默认配置的三个HandlerExceptionResolver,在这个for循环中3个异常解析器会逐个解析 by zero这个异常,继续Step Over

多次点击Step Over,可以确定默认配置的三个异常解析器都无法解析 by zero 这个异常,也就是说Spring MVC最终不会返回任何的页面,我们看到的页面是Tomcat提供的错误页面

Spring MVC 默认配置的三个异常解析器的使用场景

  • ExceptionHandlerExceptionResolver:解析@ExceptionHandler注解标注的异常
  • ResponseStatusExceptionResolver:解析@ResponseStatus注解标注的异常
  • DEfaultHandlerExceptionResolver:判断是否是Spring MVC自带的异常

二、ExceptionHandlerExceptionResolver

ExceptionHandlerExceptionResolver异常处理器用于处理@ExceptionHandler注解指定的异常,在HandlerExceptionController中handler()方法中增加可能会出现异常的代码

@RequestMapping("/handler")
public String handler(Integer x){
    int res = 10 / x;
    System.out.println(x);
    return "success";
}

在HandlerExceptionController中增加异常处理方法,使用@ExceptionHandler注解指定能处理的异常类型

// 专门处理异常的方法,指定类型
@ExceptionHandler(Exception.class)
public String handlerExceptionAlpha(){
    System.out.println("handlerExceptionAlpha()方法运行了");
    // 返回自定义的错误页面
    return "error";
}

在pages目录下新增一个错误页面error.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>这是自定义的错误页面</h2>
</body>
</html>

重新启动应用,浏览器输入 localhost:8080/handler?x=0

能够返回自定义的页面,但是没有显示异常信息。想要获取异常信息可以在方法中返回ModelAndView,将错误信息放在ModelAndView中,再从页面中取出

// 专门处理异常的方法,指定类型
// 直接返回ModelAndView,将异常信息方法封装在类中
@ExceptionHandler(Exception.class)
public ModelAndView handlerException(Exception e){
    System.out.println("handlerException()方法运行了");
    ModelAndView mv = new ModelAndView("error");
    mv.addObject("e", e);
    return mv;
}

在页面中取出异常信息

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>这是自定义的错误页面</h2>
    <h3>错误信息为以下内容</h3>
    <p>${e}</p>
</body>
</html>

重新启动应用,再次输入 localhost:8080/handler?x=0

页面中成功显示出异常信息

全局处理异常

显然在Controller类中书写异常处理方法很不优雅,可以新建一个exception包,新建一个GlobalExceptionResolver全局异常处理类,该类需要添加一个@ControllerAdvice类,告诉Spring MVC这是一个异常处理类,将HandlerExceptionController中的异常处理方法移到该类中,HandlerExceptionController中只保留handler()方法

@ControllerAdvice
public class GlobalExceptionResolver {

    // 专门处理异常的方法,指定类型
    // 直接返回ModelAndView,将异常信息方法封装到对象中
    @ExceptionHandler(Exception.class)
    public ModelAndView handlerException(Exception e){
        System.out.println("全局handlerException()方法运行了");

        ModelAndView mv = new ModelAndView("error");
        mv.addObject("e", e);
        return mv;
    }

    @ExceptionHandler(ArithmeticException.class)
    public ModelAndView handlerArithmeticException(Exception e){
        System.out.println("全局handlerArithmeticException()方法运行了");

        ModelAndView mv = new ModelAndView("error");
        mv.addObject("e", e);
        return mv;
    }
}

再次启动应用

控制台输出是由lhandlerArithmeticException处理的这个异常

如果有多个异常的情况下可以写多个异常处理的方法,指定处理的异常可以是具体的异常也可以是Exception类,当同时出现时精确匹配优先

在HandlerExceptionController中增异常处理方法,处理Exception类异常

// 专门处理异常的方法,指定类型
// 直接返回ModelAndView,将异常信息方法封装到对象中
@ExceptionHandler(Exception.class)
public ModelAndView handlerException(Exception e){
    System.out.println(this.getClass().getName() + "类中的handlerException()方法运行了");

    ModelAndView mv = new ModelAndView("error");
    mv.addObject("e", e);
    return mv;
}

重新启动该应用

根据控制台的输出,可以确定不管是否是精确匹配,优先使用同一个类下的异常处理方法来处理异常

三、ResponseStatusExceptionResolver

如果想要处理自定义的异常,则需要用到@ResponseStatus注解来标注,该注解不能标在方法上。在handler()方法上标注@ResponseStatus注解,看看会发生什么

这会导致正常页面也出现报错

该注解需要标在自定义异常类上,HandlerExceptionController中新增一个方法handlerAlpha()

@RequestMapping("/alpha")
public String handlerAlpha(@RequestParam("username") String username){
    if (!"admin".equals(username)){
        System.out.println("不是Admin,登录失败");
        throw new NonAdminException();
    }
    return "success";
}

定义一个异常类,当非管理员登录时抛出该异常

@ResponseStatus(value = HttpStatus.CONFLICT, reason = "不是管理员不能登录,走吧走吧.....")
public class NonAdminException extends RuntimeException {


}

重启应用,当username为admin时,输出sucess页面

不是admin时,输出了指定的错误页面,并输出了异常信息

根据控制台的输出,可以确定该异常是被同一类下的异常处理方法处理的;注释HandlerExceptionController中的异常处理方法,将GlobalEXception中的Exception异常处理也注释掉;再次重启,浏览器中输入http://localhost:8080/alpha?username=admin111

四、DefaultHandlerExceptionResolver

HandlerExceptionController中新增一个方法

@PostMapping(value = "/bravo")
public String handlerBravo(){
    return "success";
}

该方法是POST方法,重启应用,浏览器中输入localhost:8080/bravo

页面中的这段异常信息就是Spring MVC自定义的异常中的信息。开启GlobalException中的Exception处理方法

当GlobalException中的Exception处理方法被注释掉时,就是默认的DefaultHandlerExceptionResolver进行的处理

启动DEBUG模式,点击首页的bravo超链接

进入循环异常处理器列表的代码块中

多次Step Over后,只有DefaultHandlerExceptionResolver,可以处理这类异常

Step Into 到resolveException()这个方法中

继续Step Over

进入这个doResolveException()方法中

这里就包含了请求方法不支持的异常,也就是我们出现的异常

五、SimpleMappingExceptionResolver

SimpleMappingExceptionResolver是通过配置来进行异常处理的,在Spring MVC 配置文件中配置这个异常处理器

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <prop key="java.lang.NullPointerException">error</prop>
        </props>
    </property>
</bean>

注释掉异常处理中的Exception异常处理方法,在HandlerExceptionController 新增一个方法模拟空指针异常

@RequestMapping("/charlie")
public String handleCharlie(){
    System.out.println("NullPointException.....");
    // 模拟空指针异常的情况
    String name = null;
    System.out.println(name.length());
    return "success";
}

index页面增加超链接

<a href="/charlie">charlie</a>

重新启动应用,点击index页面的超链接

没有出现错误信息,需要将错误信息保存,并修改Spring MVC配置文件才可以

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <!-- exceptionMappings:配置哪些异常去哪些页面 -->
    <property name="exceptionMappings">
        <props>
            <!-- key:异常全类名;value:要去的页面视图名; -->
            <prop key="java.lang.NullPointerException">error</prop>
        </props>
    </property>
    <!--指定错误信息取出时使用的key  -->
    <property name="exceptionAttribute" value="e"></property>
</bean>

error页面通过配置的e或者默认的exception来去除错误信息

{e} - {exception}

如果全局异常处理存在处理空指针的方法

会优先使用全局的异常处理来处理,如果全部不能处理,在使用配置的方式处理

开启Debug模式

现在有四个全局异常处理器,SimpleMapping排在最后,第一个异常处理器就可以将这类异常处理掉,处理完成之后就不会使用其余3个异常处理器处理异常了。