一、先构建一个普通程序
1、测试页面:
<a href="testExceptionHandlerExceptionResolver?i=10">测试异常</a>
2、编辑控制器:
@RequestMapping("/testExceptionHandlerExceptionResolver") public String testExceptionHandlerExceptionResolver(@RequestParam("i") int i){ System.out.println("result: " + (10 / i)); return "success"; }
3、结果成功页面,就显示成功两字,很简单就不写了
二、编辑异常捕获
1、编辑捕获异常的代码
//这个是 之前已经写好的 控制器代码 @RequestMapping("/testExceptionHandlerExceptionResolver") public String testExceptionHandlerExceptionResolver(@RequestParam("i") int i){ System.out.println("result: " + (10 / i)); return "success"; } /** * 1. 在 @ExceptionHandler 方法的入参中可以加入 Exception 类型的参数, 该参数即对应发生的异常对象 * 2. @ExceptionHandler 方法的入参中不能传入 Map. 若希望把异常信息传导页面上, 需要使用 ModelAndView 作为返回值 * 3. @ExceptionHandler 方法标记的异常有优先级的问题. * 4. @ControllerAdvice: 如果在当前 Handler 中找不到 @ExceptionHandler 方法来出来当前方法出现的异常, * 则将去 @ControllerAdvice 标记的类中查找 @ExceptionHandler 标记的方法来处理异常. */ @ExceptionHandler({ArithmeticException.class}) public ModelAndView handleArithmeticException(Exception ex){ System.out.println("@ExceptionHandler+ArithmeticException.class+出异常了: " + ex); ModelAndView mv = new ModelAndView("error"); mv.addObject("exception", ex); return mv; } @ExceptionHandler({RuntimeException.class}) public ModelAndView handleArithmeticException2(Exception ex){ System.out.println("@ExceptionHandler+RuntimeException.class+ [出异常了]: " + ex); ModelAndView mv = new ModelAndView("error"); mv.addObject("exception", ex); return mv; }
注意:上面两个异常捕获器,精度不一样,实际使用时,抛出的异常按精度来捕获。【还有,异常捕获器中不能在方法中添加map 形参,这和普通的控制器不同,所以保存信息,必须先新建ModelAndView,然后保存并返回】
上面两个异常捕获器,作用域在这个控制器的类文件中,如果想要作用于所有的控制器类文件的话,需要新建一个异常捕获器类。
//因为是注解,所有这个全局异常捕获器,需要放在 注解扫描器 能够扫描到的目录 //在转发配置中,我们可以看到 <!-- 配置自动扫描的包 --> //<context:component-scan base-package="com.test.springmvc"></context:component-scan> @ControllerAdvice public class SpringMVCTestExceptionHandler { @ExceptionHandler({RuntimeException.class}) public ModelAndView handleArithmeticException(Exception ex){ System.out.println("全局+@ControllerAdvice+@ExceptionHandler+ArithmeticException.class+----> 出异常了: " + ex); ModelAndView mv = new ModelAndView("error"); mv.addObject("exception", ex); return mv; } }
2、返回结果失败页面,就显示失败两字,很简单就不写了。
核心注意:上面提到的属于同一层的异常捕获,出现异常时,只能先内部捕获后全局捕获,且按照精确度优先原则。这一层的异常捕获,只要有一个捕获了,该层的其他捕获器就不捕获了。
三、产生异常测试
除数为0,抛出异常。
<a href="testExceptionHandlerExceptionResolver?i=0">测试异常</a>
四、@ResponseStatus注解
直接在异常页,显示编辑过的响应头信息。
1、可以注解一个类或一个方法,当注解为一个类时,该异常作用于所有控制器。
@ResponseStatus(value=HttpStatus.FORBIDDEN, reason="用户名和密码不匹配!") public class UserNameNotMatchPasswordException extends RuntimeException{ /** * */ private static final long serialVersionUID = 1L; public UserNameNotMatchPasswordException() { System.out.println("全局+@ResponseStatus+RuntimeException+ArithmeticException.class+----> 出异常了: "); } }
2、也可以直接注解在控制器方法上。此时,控制器无论是否抛出异常,都会按照设置的异常信息,展示异常网页。(也就是无论是否有异常,控制器自己都会捕获异常,并展示@ResponseStatus
设置好的异常信息)
@ResponseStatus(reason="测试",value=HttpStatus.NOT_FOUND) @RequestMapping("/testResponseStatusExceptionResolver") public String testResponseStatusExceptionResolver(@RequestParam("i") int i){ if(i == 13){ System.out.println("throw +testResponseStatusExceptionResolver..."); throw new UserNameNotMatchPasswordException(); } System.out.println("testResponseStatusExceptionResolver..."); return "success"; }
注意:@ResponseStatus注解
1、如果控制器没有抛出异常,也不会跳转到success页面,而是用tomcat默认的异常页面,例如: HTTP Status 404 – 测试
2、如果出现异常,且有@ExceptionHandler, @ControllerAdvice 异常捕获类,那么异常会被二次捕获了。【先ResponseStatus捕获,然后ExceptionHandler又捕获,这两种捕获器属于两个层次】
[FirstIntercept] preHandle throw +testResponseStatusExceptionResolver... 全局+@ResponseStatus+RuntimeException+ArithmeticException.class+----> 出异常了: @ExceptionHandler+RuntimeException.class+ [出异常了]: com.test.springmvc.test.UserNameNotMatchPasswordException [FirstIntercept] afterCompletion
3、如果出现异常,且没有@ExceptionHandler, @ControllerAdvice 异常捕获类,那么就由ResponseStatus 捕获一次就结束了。
五、DefaultHandlerExceptionResolver
这个就是springmvc默认的异常处理,比如我们控制器编写错误,出异常了,如果我们写异常处理模块,那么默认情况下就是这个类处理的。比如控制器只支持post请求,然后用get请求就报错,请求方法不支持。
目前测试到的异常情况看,默认异常处理器,捕获到异常后,不会将异常传递给其他异常捕获器了。
直接在网页中显示错误:
HTTP Status 405 - Request method 'GET' not supported type Status report message Request method 'GET' not supported description The specified HTTP method is not allowed for the requested resource. Apache Tomcat/8.0.36
控制台有错误信息:
org.springframework.web.servlet.PageNotFound handleHttpRequestMethodNotSupported WARNING: Request method 'GET' not supported
六、SimpleMappingExceptionResolveer
在控制器中:
@RequestMapping("/testSimpleMappingExceptionResolver") public String testSimpleMappingExceptionResolver(@RequestParam("i") int i){ String [] vals = new String[10]; System.out.println(vals[i]); return "success"; }
测试网页:
http://localhost:8080/springmvc-2/testSimpleMappingExceptionResolver?i=10
编辑转发配置器:
<!-- 配置使用 SimpleMappingExceptionResolver 来映射异常 --> <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <!-- 配置请求域 异常属性名 ex,默认值为 exception --> <property name="exceptionAttribute" value="ex"></property> <!-- 异常类型 映射 跳转页 --> <property name="exceptionMappings"> <props> <prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop> </props> </property> </bean>
七、异常处理器的优先级
同一个异常:
DefaultHandlerExceptionResolver (优先级第一,捕获到异常后,就将异常截断,在网页中展示异常信息,不会再往下传递)
@ResponseStatus(优先级第二,捕获到异常后,会将异常往后传递,返回结果以最后一个异常返回网页信息为准)
在控制器中,添加这个@ResponseStatus注解,就算没有抛出异常,也会展示指定状态的异常的网页。
@ResponseStatus(reason="测试",value=HttpStatus.NOT_FOUND) @RequestMapping("/testResponseStatusExceptionResolver") public String testResponseStatusExceptionResolver(@RequestParam("i") int i){ if(i == 13){ System.out.println("throw +testResponseStatusExceptionResolver..."); throw new UserNameNotMatchPasswordException(); } System.out.println("testResponseStatusExceptionResolver..."); return "success"; }
如果上面的控制器没有@ResponseStatus(reason="测试",value=HttpStatus.NOT_FOUND)
,然后异常时抛出了UserNameNotMatchPasswordException类
对UserNameNotMatchPasswordException查看编码信息:
@ResponseStatus(value=HttpStatus.FORBIDDEN, reason="用户名和密码不匹配!") public class UserNameNotMatchPasswordException extends RuntimeException{ /** * */ private static final long serialVersionUID = 1L; public UserNameNotMatchPasswordException() { System.out.println("全局+@ResponseStatus+RuntimeException+ArithmeticException.class+----> 出异常了: "); } }
看上面的异常类,如果项目中没有低优先级的异常捕获器,那么网页就返回错误信息就结束了。如果有其他低优先级的异常了,那么异常还会被继续处理。
@ExceptionHandler和@ControllerAdvice(优先级第三,异常被捕获后,不会继续往下传递了)
@ExceptionHandler适用于和控制器位于同一个文件中,@ControllerAdvice单独一个文件,作用域针对所有的控制器。
SimpleMappingExceptionResolver(异常映射器,优先级最低,针对异常的类型,指定错误和异常的返回页)
<!-- 配置使用 SimpleMappingExceptionResolver 来映射异常 --> <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <!-- 配置请求域 异常属性名 ex,默认值为 exception --> <property name="exceptionAttribute" value="ex"></property> <!-- 异常类型 映射 跳转页 --> <property name="exceptionMappings"> <props> <prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop> </props> </property> </bean>
补充:
针对指定的异常信息返回页,可以在返回页的网页中,直接读取异常信息。
默认jstl 表达式为: ${requestScope.exception}
,在SimpleMappingExceptionResolver 中信息配置时,将异常的属性名改成了ex,所以错误信息展示页 该调用 ${requestScope.ex}
来获取异常信息。