SpringMVC异常处理
SpringMVC异常处理
1.异常处理-基本介绍
● 基本介绍
- Spring MVC 通过 HandlerExceptionResolver 处理程序的异常,包括 Handler 映射、数据 绑定以及目标方法执行时发生的异常
- 主要处理 Handler 中用 @ExceptionHandler 注解定义的方法
- ExceptionHandlerMethodResolver 内部若找不到@ExceptionHandler 注解的话,会找 @ControllerAdvice 类的@ExceptionHandler 注解方法, 这样就相当于一个全局异常处理器
2.局部异常
1.应用实例
● 应用实例需求
- 如果不处理异常, 非常的不友好
● 应用实例-代码实现
exception_mes.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>异常信息提示</title>
</head>
<body>
<h1>朋友, 程序发生了异常...</h1>
异常信息- ${requestScope.reason}
</body>
</html>
success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>操作成功</title>
</head>
<body>
<h1>恭喜, 操作成功~</h1>
</body>
</html>
MyExceptionHandler.java
@Controller
public class MyExceptionHandler {
@ExceptionHandler(value={ArithmeticException.class,NullPointerException.class})
public String handlerException(Throwable e, HttpServletRequest request){
System.out.println("异常信息:"+e.getMessage());
request.setAttribute("reason",e.getMessage());
return "exception_mes";
}
/**
* 1. 编写方法,模拟异常, 算术异常
* 2. 如果我们不做异常处理,是由tomcat默认页面显示
*
* @param num
* @return
*/
@RequestMapping(value = "/testException01")
public String test01(Integer num) {
int i = 9 / num;
return "success";
}
}
测试
2.Debug处理流程
/**
找到一个Method来处理给定的 Throwable。
如果找到多个匹配项,则使用ExceptionDepthComparator
*/
@Nullable
public Method resolveMethodByThrowable(Throwable exception) {
//根据异常类型获取处理异常的目标方法
Method method = resolveMethodByExceptionType(exception.getClass());
if (method == null) {
Throwable cause = exception.getCause();
if (cause != null) {
method = resolveMethodByThrowable(cause);
}
}
return method;
}
@Nullable
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
//根据异常类型从缓存中获取目标方法
Method method = this.exceptionLookupCache.get(exceptionType);
if (method == null) {
//controller含有@ExceptionHandler则可以获取到目标方法
method = getMappedMethod(exceptionType);
this.exceptionLookupCache.put(exceptionType, method);
}
return (method != NO_MATCHING_EXCEPTION_HANDLER_METHOD ? method : null);
}
3.全局异常
1.应用实例
● 应用实例需求
演示全局异常处理机制 , ExceptionHandlerMethodResolver内部若找不 到 @ExceptionHandler 注解的话,会找 @ControllerAdvice 类的@ExceptionHandler 注解方法, 这样就相当于一个全局异常处理器
● 应用实例-代码实现
1.MyExceptionHandler.java新增异常,这里我们故意定义一个本类异常处理不包含的异常
@Controller
public class MyExceptionHandler {
@ExceptionHandler(value={ArithmeticException.class,NullPointerException.class})
public String handlerException(Throwable e, HttpServletRequest request){
System.out.println("异常信息:"+e.getMessage());
request.setAttribute("reason",e.getMessage());
return "exception_mes";
}
/**
* 1. 编写方法,模拟异常, 算术异常
* 2. 如果我们不做异常处理,是由tomcat默认页面显示
*/
@RequestMapping(value = "/testException01")
public String test01(Integer num) {
int i = 9 / num;
return "success";
}
/**
* 模拟 NumberFormatException异常
* @return
*/
@RequestMapping(value = "/testException02")
public String test02() {
int num = Integer.parseInt("hello");
return "success";
}
}
2.全局异常处理类-MyGlobalException.java
@ControllerAdvice
public class MyGlobalException {
@ExceptionHandler(value = {ClassCastException.class,NumberFormatException.class})
public String exceptionHandler(Throwable e, HttpServletRequest request){
System.out.println("全局异常处理-" + e.getMessage());
//如何将异常的信息带到下一个页面.
request.setAttribute("reason", e.getMessage());
return "exception_mes";
}
}
测试
2.Debug处理流程
可以看到SpringMVC底层在获取目标异常处理方法时,会先找本类的@ExceptionHandler修饰的异常处理方法
如果没有找到则会从被@ControllerAdvice修饰的全局异常处理类中查找,如果找到了则执行异常处理的目标方法
@Nullable
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
@Nullable HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = null;
if (handlerMethod != null) {
// Local exception handler methods on the controller class itself.
// To be invoked through the proxy, even in case of an interface-based proxy.
handlerType = handlerMethod.getBeanType();
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
}
// For advice applicability check below (involving base packages, assignable types
// and annotation presence), use target class instead of interface-based proxy.
if (Proxy.isProxyClass(handlerType)) {
handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
}
}
//从被@ControllerAdvice修饰的类中查找异常
for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
ControllerAdviceBean advice = entry.getKey();
if (advice.isApplicableToBeanType(handlerType)) {
ExceptionHandlerMethodResolver resolver = entry.getValue();
//public java.lang.String com.llp.web.exception.MyGlobalException.exceptionHandler(java.lang.Throwable,javax.servlet.http.HttpServletRequest)
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
}
}
}
return null;
}
3.异常处理时:局部异常优先级高于 全局异常
我们在全局异常类中添加上空指针的异常处理
@ExceptionHandler(value = {ClassCastException.class,NumberFormatException.class,NullPointerException.class})
public String exceptionHandler(Throwable e, HttpServletRequest request){
System.out.println("全局异常处理-" + e.getMessage());
//如何将异常的信息带到下一个页面.
request.setAttribute("reason", e.getMessage());
return "exception_mes";
}
再次进行测试
可以看到底层执行顺序是先查找局部的异常处理
4.自定义异常
1.应用实例
1.BaseException-自定义异常处理类
/**
* 自定义异常类
*/
@ResponseStatus(value = HttpStatus.BAD_REQUEST,code =HttpStatus.BAD_REQUEST,reason = "错误原因")
public class BaseException extends RuntimeException{
public BaseException() {
}
public BaseException(String message) {
super(message);
}
}
2.新增测试方法
@RequestMapping(value = "/testException03")
public String test03() {
throw new BaseException("自定义异常描述");
}
3.测试
这里我们没有对自定义异常进行局部/全局处理,最终异常由tomcat默认机制进行处理
在全局异常处理我们自定义的异常再次测试
@ControllerAdvice
public class MyGlobalException {
@ExceptionHandler(value = {ClassCastException.class,NumberFormatException.class,NullPointerException.class,BaseException.class})
public String exceptionHandler(Throwable e, HttpServletRequest request){
System.out.println("全局异常处理-" + e.getMessage());
//如何将异常的信息带到下一个页面.
request.setAttribute("reason", e.getMessage());
return "exception_mes";
}
}
5.SimpleMappingExceptionResolver
1.基本说明
● 基本说明
- 如果希望对所有异常进行统一处理,可以使用 SimpleMappingExceptionResolver
- 它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常
- 需要在 ioc 容器中配置
2.应用实例
● 应用实例-需求
对数组越界异常进行统一处理,使用 SimpleMappingExceptionResolver
● 应用实例-代码实现
1. 修改 MyExceptionHandler.java , 增加方法 test04
@RequestMapping(value = "/testException04")
public String test04() {
int[] arr = new int[]{3, 9, 10, 190};
//抛出一个数组越界的异常 ArrayIndexOutOfBoundsException
System.out.println(arr[90]);
return "success";
}
2. 配置 springDispatcherServlet-servlet.xml
<!--配置一个统一异常处理-->
<bean id="simpleMappingExceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--key:异常类型,arrEx对应的视图-->
<prop key="java.lang.ArrayIndexOutOfBoundsException">arrEx</prop>
</props>
</property>
</bean>
3.arrEx.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>数组越界异常</title>
</head>
<body>
<h1>异常信息: 数组越界异常</h1>
</body>
</html>
测试
3.对未知异常进行统一处理
● 应用实例-需求
对未知异常进行统一处理,使用 SimpleMappingExceptionResolver
1. 修改 MyExceptionHandler.java , 增加方法 test05
//如果发生了没有归类的异常, 可以给出统一提示页面
@RequestMapping(value="/testException05") public String test05(){
String str = "hello"; char c = str.charAt(10);
return "success";
}
2. 配置 springDispatcherServlet-servlet.xml
<!--配置一个统一异常处理-->
<bean id="simpleMappingExceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--key:异常类型,arrEx对应的视图-->
<prop key="java.lang.ArrayIndexOutOfBoundsException">arrEx</prop>
<prop key="java.lang.Exception">otherEx</prop>
</props>
</property>
</bean>
3.otherEx.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>未知异常信息</title>
</head>
<body>
<h1>朋友,系统发生了未知异常~, 请联系网站管理员</h1>
</body>
</html>
4.异常处理的优先级梳理
● 异常处理的优先级
局部异常 > 全局异常 > SimpleMappingExceptionResolver > tomcat 默认机制