1. 描述
在J2EE项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。
那么,能不能将所有类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护?答案是肯定的。下面将介绍使用Spring MVC统一处理异常的解决和实现过程。
2. 分析
Spring MVC处理异常有二种方式,但这里分开三种来说明:
(1)使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver;
(2)实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器;
(3)使用@ExceptionHandler注解实现异常处理;
方式一、使用SpringMVC 简单异常处理器SimpleMappingExceptionResolver
在Spring的配置文件applicationContext.xml中增加以下内容:
<!-- 自定义的实现类 -->
<bean id="exceptionHandler" class="com.enh.test.CustomExceptionHandler"/>
<!-- 默认的实现类注入 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- 为所有的异常定义默认的异常处理页面,exceptionMappings未定义的异常使用本默认配置 -->
<property name="defaultErrorView" value="error"></property>
<!-- 定义异常处理页面用来获取异常信息的变量名,默认名为exception -->
<property name="exceptionAttribute" value="ex"></property>
<!--
定义需要特殊处理的异常,用类名或完全路径名作为key,异常页文件名作为值,
将不同的异常映射到不同的页面上。
-->
<property name="exceptionMappings">
<props>
<prop key="IOException">error/ioexp</prop>
<prop key="java.sql.SQLException">error/sqlexp</prop>
</props>
</property>
</bean>
方式二、自定义异常解析器实现接口:HandlerExceptionResolver,其中有一个方法用来处理异常
@Component
public class CustomExceptionHandler implements HandlerExceptionResolver{
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
ModelAndView mav = new ModelAndView();
// 获取异常,判断异常类型,然后跳转到页面显示异常信息
//ParameterException为自定义异常
ParameterException pe = null;
// 根据不同错误转向不同页面
if (ex instanceof ParameterException) {
pe = (ParameterException)ex;
} else {
pe=new ParameterException("未知异常");
}
//跳转到页面显示异常信息
mav.addObject("message", pe.getMessage());
mav.setViewName("error");
return mav;
}
}
方式三、使用@ControllerAdvice + @ExceptionHandler注解实现全局异常处理
其实@ExceptionHandler也可以定义在 Controller 内部,简述步骤就是先创建一个方法并用 @ExceptionHandler 注解来修饰用来处理异常,这个方法基本和 @RequestMapping 修饰的方法差不多,只是可以多一个类型为 Exception 的参数,@ExceptionHandler 中可以添加一个或多个异常的类型,如果为空的话则认为可以触发所有的异常类型错误。但是它的缺陷很明显处理异常的方法和出错的方法(或者异常最终抛出来的地方)必须在同一个controller,不能全局控制。故不在此做详细描述。
与此对应的就是使用@ControllerAdvice 和@ExceptionHandler 可以全局控制异常,使业务逻辑和异常处理分隔开。
/**
* 如果使用@ControllerAdvice + @ExceptionHandler的同时实现HandlerExceptionResolver接口
* 则@ExceptionHandler指明异常的处理类型的功能会失效,即会处理所有异常
* @author Howick
*
*/
@ControllerAdvice
public class CustomExceptionHandler {
/**
* 可以@ExceptionHandler用来指明异常的处理类型
*/
@ExceptionHandler(Exception.class)
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
ModelAndView mav = new ModelAndView();
mav.addObject("message", ex.getMessage());
mav.setViewName("error");
return mav;
}
}
在该类中,可以定义多个方法,不同的方法处理不同的异常,例如专门处理空指针的方法、专门处理数组越界的方法...,也可以直接向上面代码一样,在一个方法中处理所有的异常信息。
@ExceptionHandler 注解用来指明异常的处理类型,即如果这里指定为 NullpointerException,则数组越界异常就不会进到这个方法中来。
优先级
既然在SpringMVC中有两种处理异常的方式,那么就存在一个优先级的问题:
当发生异常的时候,SpringMVC会如下处理:
(1)SpringMVC会先从配置文件找异常解析器HandlerExceptionResolver
(2)如果找到了异常异常解析器,那么接下来就会判断该异常解析器能否处理当前发生的异常
(3)如果可以处理的话,那么就进行处理,然后给前台返回对应的异常视图
(4)如果没有找到对应的异常解析器或者是找到的异常解析器不能处理当前的异常的时候,就看当前的Controller中有没有提供对应的异常处理器,如果提供了就由Controller自己进行处理并返回对应的视图
(5)如果配置文件里面没有定义对应的异常解析器,而当前Controller中也没有定义的话,就看有没有全局ControllerAdvice提供的全局异常处理器,如果没有那么该异常就会被抛出来。