Spring MVC处理异常有3种方式:
1、Spring MVC提供的简单异常处理器
使用SimpleMappingExceptionResolver进行异常处理,具有集成简单、有良好的扩展性、对已有代码没有入侵性等优点,该方法仅能获取到异常信息,若在出现异常时,对需要获取除异常以外的数据的情况不适用。
2、实现Spring的异常处理接口
使用实现HandlerExceptionResolver接口的异常处理器进行异常处理,具有集成简单、有良好的扩展性、对已有代码没有入侵性等优点,同时,在异常处理时能获取导致出现异常的对象,有利于提供更详细的异常处理信息。
@Component
public class CustomExceptionHandler implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object object, Exception exception) {
if(exception instanceof IOException){
return new ModelAndView("ioexp");
}else if(exception instanceof SQLException){
return new ModelAndView("sqlexp");
}
return null;
}
}
3、使用@ExceptionHandler注解实现异常处理
使用@ExceptionHandler注解实现异常处理,具有集成简单、有扩展性好、不需要附加Spring配置等优点,在异常处理时不能获取除异常以外的数据。当局部注解@ExceptionHandler和全局注解@ControllerAdvice共存时,可以进行全局异常捕获。
@ControllerAdvice
public class MyGlobalExceptionHandler {
@ExceptionHandler(value = {ArithmeticException.class,NullPointerException.class})
public ModelAndView handlerException(Exception exception){
System.out.println("global-------exception1");
ModelAndView mv = new ModelAndView();
mv.setViewName("error");
mv.addObject("exce",exception);
return mv;
}
@ExceptionHandler(value = {Exception.class})
public ModelAndView handlerException2(Exception exception){
System.out.println("global-------exception2");
ModelAndView mv = new ModelAndView();
mv.setViewName("error");
mv.addObject("exce",exception);
return mv;
}
}
当Controller抛出的某个异常多个@ExceptionHandler标注的方法都适用时,Spring会选择最具体的异常处理方法来处理,也就是说@ExceptionHandler(value = {Exception.class})
这里标注的方法优先级最低,只有当其它方法都不适用时,才会来到这里处理。
接下来在源码中看具体流程,前端控制器DispatcherServlet对象在创建时会初始化一系列的对象
public class DispatcherServlet extends FrameworkServlet {
// ......
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
// ......
}
在DispatcherServlet的initHandlerExceptionResolvers(context)方法中,所有实现了HandlerExceptionResolver接口的bean会被保存起来,其中就有一个类型为ExceptionHandlerExceptionResolver的bean,这个bean在应用启动过程中会获取所有被@ControllerAdvice注解标注的bean对象做进一步处理。
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
implements ApplicationContextAware, InitializingBean {
// ......
private void initExceptionHandlerAdviceCache() {
// ......
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
AnnotationAwareOrderComparator.sort(adviceBeans);
for (ControllerAdviceBean adviceBean : adviceBeans) {
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType());
if (resolver.hasExceptionMappings()) {
// 找到所有ExceptionHandler标注的方法并保存成一个ExceptionHandlerMethodResolver类型的对象缓存起来
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
if (logger.isInfoEnabled()) {
logger.info("Detected @ExceptionHandler methods in " + adviceBean);
}
}
// ......
}
}
}
当Controller抛出异常时,DispatcherServlet通过ExceptionHandlerExceptionResolver来解析异常,而ExceptionHandlerExceptionResolver又通过ExceptionHandlerMethodResolver 来解析异常找到适用的@ExceptionHandler标注的方法
public class ExceptionHandlerMethodResolver {
// ......
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<Class<? extends Throwable>>();
// 找到所有适用于Controller抛出异常的处理方法,例如Controller抛出的异常
// 是BizException(继承自RuntimeException),那么@ExceptionHandler(BizException.class)和
// @ExceptionHandler(Exception.class)标注的方法都适用此异常
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
if (mappedException.isAssignableFrom(exceptionType)) {
matches.add(mappedException);
}
}
if (!matches.isEmpty()) {
// 这里通过排序找到最适用的方法,排序的规则依据抛出异常相对于声明异常的深度,例如
// Controller抛出的异常是BizException(继承自RuntimeException),那么BizException
// 相对于@ExceptionHandler(BizException.class)声明的BizException.class其深度是0,
// 相对于@ExceptionHandler(Exception.class)声明的Exception.class其深度是2,所以
// @ExceptionHandler(BizException.class)标注的方法会排在前面
Collections.sort(matches, new ExceptionDepthComparator(exceptionType));
return this.mappedMethods.get(matches.get(0));
}
else {
return null;
}
}
// ......
}