SpringMVC异常处理

  |   0 评论   |   0 浏览

SpringMVC异常处理

1.异常处理-基本介绍

● 基本介绍

  1. Spring MVC 通过 HandlerExceptionResolver 处理程序的异常,包括 Handler 映射、数据 绑定以及目标方法执行时发生的异常
  2. 主要处理 Handler 中用 @ExceptionHandler 注解定义的方法
  3. 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";
    }

}

测试

image-20220625092207672

2.Debug处理流程

image-20220625121854555

/**
 	找到一个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);
}

image-20220625123953038

image-20220625124029924

image-20220625124052522

image-20220625124118652

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";
    }
}

测试

image-20220625124502655

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;
}

image-20220625124848376

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";
}

再次进行测试

可以看到底层执行顺序是先查找局部的异常处理

image-20220625125553245

image-20220625125822956

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默认机制进行处理

image-20220625130155154

在全局异常处理我们自定义的异常再次测试

@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";
    }
}

image-20220625130618220

5.SimpleMappingExceptionResolver

1.基本说明

● 基本说明

  1. 如果希望对所有异常进行统一处理,可以使用 SimpleMappingExceptionResolver
  2. 它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常
  3. 需要在 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>

测试

image-20220625131456941

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 默认机制


标题:SpringMVC异常处理
作者:llp
地址:https://llinp.cn/articles/2022/06/25/1656134479531.html