异常处理-基本介绍
基本介绍
1.Spring MVC通过HandlerExceptionResolver处理程序的异常,包括Handler映射、数据绑定以及目标方法执行时发生的异常。
2.主要处理Handler中用@ExceptionHandler注解定义的方法。
3.ExceptionHandlerMethodResolver内部若找不到@ExceptionHandler注解的话,会找@ControllerAdvice类的@ExceptionHandler注解方法,这样就相当于一个全局异常处理器
异常类型
1.局部异常
在注释了@Controller的处理器类Handler,内部写的异常处理,就是局部异常
需要使用注解@在其方法上进行标注
应用实例
处理原因:如果不处理异常,显示界面会非常的不友好
1.创建com/stein/springMVC/exception/MyExceptionHandler.java
注意:
1)Handler要用@Component注入到容器中
2)局部异常处理方法,用使用@ExceptionHandler进行注解
3)@ExceptionHandler的参数,是填写异常类型,并且可以是数组的形式
@Controller
public class MyExceptionHandler {
//一个正常的方法映射
@RequestMapping("/arithmetic")
public String exceptionDemo(Integer num){
Integer result=100/num;
System.out.println("result= "+result);
return "success";
}
@ExceptionHandler({ArithmeticException.class, NullPointerException.class})
public String exceptionHandler(Exception exception, HttpServletRequest request){
System.out.println("exception= "+exception.getMessage());
request.setAttribute("exception", exception.getMessage());
return "exception_msg";
}
}
2.创建操作页面 web/exception.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>exception</title>
</head>
<body>
<h1>局部异常处理</h1>
<a href="<%=request.getContextPath()%>/arithmetic?num=0">点击显示算数异常</a>
</body>
</html>
3.创建异常显示页面,web/WEB-INF/page/exception_msg.jsp
<body>
<h1>出错啦>_<</h1>
<h2>出错信息:<%=request.getAttribute("exception")%></h2>
</body>
4.测试
然后显示出错信息
Debug代码
异常是通过ExceptionHandlerMethodResolver.java类来处理的,它的异常处理机制比较多。通过Ctrl+N找到
通过形参exceptionType拿到异常,然后再拿到处理异常的method,最后通过反射调用。
这一步是通过默认的方法查找异常处理方法,没有找到就返回null
Method method = (Method)this.exceptionLookupCache.get(exceptionType);
然后找到了我写的MyExceptionHandler.exceptionHanler(exception,request)方法
不出意外的,找到了我写的异常处理方法
最终返回前端页面进行展示
2.全局异常
应用实例
全局异常处理机制:
如果在ExceptionHandlerMethodResolver内部找不到@ExceptionHandler注解的话,
会去@ControllerAdvice类找@ExceptionHandler注解方法,这样就相当于一个全局异常处理器
1.创建全局异常类,com/stein/springMVC/exception/GlobalException.java
//加入这个注解后,就表示是一个全局异常
//全局异常就不管是哪个Handler抛出的异常,都可以捕获
//控制通知
@ControllerAdvice
public class GlobalException {
//注解参数用于指定,需要捕获的异常类型
//1. 模拟NumberFormatException
//2. 在之前的局部异常中,没有对NumberFormatException异常进行涵盖
//3. 所以就会来找全局异常处理
@ExceptionHandler({NumberFormatException.class, ClassCastException.class})
public String global(Exception ex, HttpServletRequest request) {
System.out.println("全局异常信息:"+ex.getMessage());
request.setAttribute("exception", ex.getMessage());
return "exception_msg";
}
}
2.增加出现转换异常的方法,com/stein/springMVC/exception/MyExceptionHandler.java
@RequestMapping("/global")
public String globalExceptionDemo(String num){
int i = Integer.parseInt(num);
return "success";
}
3.修改页面,增加装换异常访问请求exception.jsp
<body>
<h1>异常处理</h1>
<a href="<%=request.getContextPath()%>/arithmetic?num=0">点击显示局部异常</a><br>
<a href="<%=request.getContextPath()%>/global?num=hello">点击显示全局异常</a>
</body>
4.测试
选择全局异常
显示全局异常提示信息
5.postman测试
Debug处理流程
依然点击全局异常
断点不变,可以看到发生了数字格式化异常,这一步method没有找到处理异常方法,为null
看到此时,method找不到在@Controller类中的局部异常处理方法,显示noMatchingExceptionHandler,没有匹配的异常处理
找到了全局异常处理方法
进入到处理方法
最终成功显示异常信息
注意事项与细节
异常处理时:局部异常优先级高于全局异常
给局部异常处理添加上NumberFormatException.class后,局部异常优先处理
局部异常exception= For input string: "hello"
3.自定义异常(类型)
局部异常和全局异常,是通过作用范围来区分的。
自定义异常,说的是自定义异常的类型。比如有ArithmeticException.class, NullPointerException.class,NumberFormatException.class这些异常了,我还需要自定义一个年龄异常AgeException.class
通过@ResponseStatus注解,可以自定义该异常
关系:那么自定义异常(类型),可以通过默认tomcat调用,或者局部异常、全局异常进行选择调用。像这样:
@ExceptionHandler({ArithmeticException.class, NullPointerException.class,NumberFormatException.class,ArithmeticException.class})
应用实例
1.创建自定义异常类型,com/stein/springMVC/exception/AgeException.java
//reason表示显示的错误原因
//value填写Http错误状态,是枚举类型的
@ResponseStatus(value = HttpStatus.BAD_REQUEST,reason = "年龄需要在1-120岁之间")
public class AgeException extends RuntimeException {
}
2.添加可能出现自定义异常的方法,MyExceptionHandler.java
@RequestMapping("/age")
public String age(int age){
if(age>0 && age<120){
return "success";
}else {
throw new AgeException();
}
}
3.添加访问链接,exception.jsp
<a href="<%=request.getContextPath()%>/age?age=121">点击显示自定义异常</a><br><br>
4.测试
自定义异常(类型)
返回结果
这个tomcat默认页面太生硬,想用自己页面显示
1.把AgeException.class添加到GlobalException.global()的注解中
@ExceptionHandler({NumberFormatException.class, ClassCastException.class,AgeException.class})
2.为了配合exception_msg.jsp的属性exception.getMessage()返回结果不为null,添加AgeException.java的构造器
@ResponseStatus(value = HttpStatus.BAD_REQUEST,reason = "年龄需要在1-120岁之间")
public class AgeException extends RuntimeException {
public AgeException() {
}
public AgeException(String message) {
super(message);
}
}
3.业务代码中,完善message
@RequestMapping("/age")
public String age(int age){
if(age>0 && age<120){
return "success";
}else {
throw new AgeException("年龄需要在1-120岁之间");
}
}
4.再次测试
点击自定义异常后,出现了我们自己写的异常页面exception_msg.jsp
同样,这个自定义异常类型,添加到局部异常也可以生效,这儿就不演示了。
Debug处理流程
跟全局异常Debug一样,参考走一遍即可。
统一处理异常信息
SimpleMappingExceptionResolver
- 基本说明
1.如果希望对所有异常进行统一处理,可以使用SimpleMappingExceptionResolver
2.它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常
3.需要在ioc容器中配置
应用实例
1.配置SimpleMappingExceptionResolver
<!--配置一个统一异常处理-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!--这个属性用于设置异常映射,即当发生某种异常时,应该跳转到哪个视图。-->
<property name="exceptionMappings">
<!--它包含一个`<props>`标签,这是Spring配置中用于定义`java.util.Properties`类型属性的方式。每个`<prop>`标签表示一个键值对。-->
<props>
<!--键(key)是异常类的全限定名-->
<!--值(value)是`arrEx`,是一个逻辑视图页面-->
<prop key="ArrayIndexOutOfBoundsException">arrEx</prop>
</props>
</property>
</bean>
2.按配置创建异常视图,web/WEB-INF/page/arrEx.jsp
<h1>朋友,当前页面异常>_<,如有疑问,可以联系管理员</h1>
3.增加出现数组越界的映射方法,MyExceptionHandler
@RequestMapping("/array") public String array(){ //两种写法都可以 int[] ints = {1, 3, 6, 12}; //int[] ints =new int[]{1,3,6,12}; //越界异常 System.out.println("ints[5]="+ints[5]); return "success"; }
4.添加访问页面的链接,exception.jsp
<a href="<%=request.getContextPath()%>/array">点击测试统一异常处理</a><br><br>
5.测试
对未知异常统一处理
异常的情况有很多,当无法对每一种异常都进行单独配置的时候,需要一种兜底的方案,就是对其他异常的统一处理。
应用实例
1.使用SimpleMappingExceptionResolver进行配置,web/WEB-INF/springMVC-servlet.xml
<!--配置一个统一异常处理-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!--这个属性用于设置异常映射,即当发生某种异常时,应该跳转到哪个视图。-->
<property name="exceptionMappings">
<!--它包含一个`<props>`标签,这是Spring配置中用于定义`java.util.Properties`类型属性的方式。每个`<prop>`标签表示一个键值对。-->
<props>
<!--键(key)是异常类的全限定名-->
<!--值(value)是`arrEx`,是一个逻辑视图页面-->
<prop key="java.lang.ArrayIndexOutOfBoundsException">arrEx</prop>
<!--两种写法都可以,但是推荐上面一种,避免重名-->
<!--<prop key="ArrayIndexOutOfBoundsException">arrEx</prop>-->
<!--配置其他的未知异常-->
<!--key配置的是所有异常Exception-->
<!--响应的页面名称是allEx-->
<prop key="java.lang.Exception">allEx</prop>
</props>
</property>
</bean>
2.创建响应页面,web/WEB-INF/page/allEx.jsp
<h1>哎呀,发生了未知异常>_<</h1>
3.添加未知异常映射方法,MyExceptionHandler.java
这是一个字符串越界异常
@RequestMapping("/unknown")
public String unknownException(){
String str="hello";
System.out.println("str.charAt[5]="+str.charAt(5));
return "success";
}
4.添加访问链接,exception.jsp
<a href="<%=request.getContextPath()%>/unknown">配置统一的未知异常处理</a><br><br>
5.测试
哎呀,发生了未知异常>_<
异常处理的优先级
局部异常 > 全局异常 > SimpleMappingExceptionResolver > tomcat默认机制
1.对比测试局部异常、全局异常、SimpleMappingExceptionResolver的优先级,他们3个的范围都加上ArrayIndexOutOfBoundsException.class,然后进行数组越界异常测试。
结果响应的只有局部异常:
局部异常exception= 5
2.接下来取消局部异常的参赛资格,取消其ArrayIndexOutOfBoundsException.class的设置,将剩下2个的进行对比。然后进行数组越界异常测试。
此时,响应的是全局异常
全局异常信息:5
3.然后全局异常退出,删除其越界异常的设置,只保留SimpleMappingExceptionResolver的设置,进行越界测试。
结果是按照SimpleMappingExceptionResolver配置的arrEx.jsp进行了响应
朋友,当前页面异常>_<,如有疑问,可以联系管理员
4.将SimpleMappingExceptionResolver的设置也取消,包括越界异常和所有异常。进行越界测试
结果,Tomcat默认的异常机制开始生效了
综上,确认了他们的优先级排序。