在项目中采用spring的异常处理机制:
示例一、在Controller中加@ExceptionHandler注解定义异常拦截的方法,在方法中定义返回的页面:
@Controller
public class ExceptionTestController {@ExceptionHandlerpublic String handleException(Exception e) {return "error";}@RequestMapping("/error")public String error(Model model) {throw new RuntimeException();}
}
error.jsp
<html>
<head><title>ok</title>
</head>
<body><h1>===============error============</h1>
</body>
</html>
示例二、将异常处理做成一个公共的:
@ControllerAdvice
public class ViewExceptionHandler {@ExceptionHandler(value = Exception.class)public String handleException(Exception e) {return "error";}
}
效果:
两者区别:示例一只能捕获所属Controller中的方法抛出的异常,不能捕获其他Controller中的,示例二可以捕获所有Controller抛出的异常。
下面看源码分析:
由handlerAdapter进行反射调用后,调到controller中的具体方法,抛出的异常最终被捕获,将异常赋值给dispatchException,并调用processDispatchResult方法:
这里获取到handlerMethod
这里异常解析器是一个混合体,里面有三个异常解析器,这三个异常解析器是什么时候初始化的?
在DispatcherServlet的父类FrameworkServlet中定义了一个内部类,监听spring容器启动后的事件发布:
会调到DispatcherServlet中的onRefresh方法:
在这个方法中会拿到spring容器中所有实现了HandlerExceptionResolver接口的实例:
那spring容器中的实例又是怎么来的呢?就是在开启mvc功能的时候:
如果没有自定义异常解析器,这里会添加默认的,看到的那三个异常解析器就是通过addDefaultHandlerExceptionResolvers方法添加的。这里不再赘述了,有兴趣的可以看一下这部分的源码。
言归正传,看这个混合体的resolveException方法:
最终会调到ExceptionHandlerExceptionResolver中的doResolveHandlerMethodException
看上图中的getExceptionHandlerMethod方法:
以上两个截图的意思是寻找controller中的带@ExceptionHandler注解的方法,也就是对应我们示例一中的情况(异常拦截方法和注解要求在controller里面),并获取@ExceptionHandler注解的值,也就是异常类型(如果没定义,就是java.lang.Exception),并建立异常类型和异常处理方法的映射。
建立好映射关系以后,会根据异常类型找到对应的处理方法,并根据controller和异常处理方法作为参数创建ServletInvocableHandlerMethod实例。
由构建的ServletInvocableHandlerMethod进行方法调用,参数解析后,最终会通过反射调到我们定义的异常处理方法:
接下来的逻辑和其他普通的方法处理一样,不再赘述。
也可以像示例二中的方式,我们单独的写一个全局的异常处理类,加上@ControllerAdvice注解
我们可以定义很多个方法,每个方法拦截的异常类型不一样,比如IOException、空指针异常等等。下面看示例二的源码:
和示例一的逻辑差不多,注释掉controller中的@ExceptionHandler方法之后,根据异常获取controller中对应的异常处理方法为空,就会往下走:
在exceptionHandlerAdviceCache当中找到一个我们定义的全局异常拦截类:
接下来的处理和示例一的就相同了,也是拿我们定义的异常处理类和方法构造ServletInvocableHandlerMethod,然后进行反射调用。
那么exceptionHandlerAdviceCache中是什么时候初始化的?
上面提到,在没有定义异常解析器的时候,spring会添加默认的异常解析器,看代码:
在这里会调用afterPropertiesSet()方法:
以上代码是拿到spring容器中所有的Object实例,查找有ControllerAdvice注解的bean。接下来拿到ControllerAdvice注解的bean的list集合后进行排序,然后遍历,如图:
new ExceptionHandlerMethodResolver(adviceBean.getBeanType())和上面讲过的一样,在构造方法中找全局异常拦截类中带@ExceptionHandler的方法,然后解析,然后建立异常类型和异常处理方法的映射关系。
然后将全局异常处理类和异常解析器放入exceptionHandlerAdviceCache。