Spring 在3.2版本后面增加了一个ControllerAdvice注解。网上的资料说的都是ControllerAdvice配合ExceptionHandler注解可以统一处理异常。而Spring MVC是如何做到的资料却比较少,下面会先给出使用的例子和踩过的一个坑。然后进行相应的源码分析,之后再介始ControllerAdvice另外的两种使用方式。
ControllerAdvice的简单使用
- ControllerAdvice配合ExceptionHandler可以统一处理系统的异常,我们先定义一个ExceptionAdvice类用于处理系统的两种类型的异常。代码如下:
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.pptv.frame.dto.common.ResponseDTO;
import com.pptv.frame.dto.common.ServiceCodeEnum;
@ControllerAdvice
public class ExceptionAdvice {
@ExceptionHandler({
ArrayIndexOutOfBoundsException.class
})
@ResponseBody
public ResponseDTO handleArrayIndexOutOfBoundsException(ArrayIndexOutOfBoundsException e) {
// TODO 记录log日志
e.printStackTrace();
ResponseDTO responseDTO = new ResponseDTO();
responseDTO.wrapResponse(ServiceCodeEnum.E999998, "数组越界异常");
return responseDTO;
}
@ExceptionHandler({
Exception.class
})
@ResponseBody
public ResponseDTO handleException(Exception e) {
// TODO 记录log日志
e.printStackTrace();
ResponseDTO responseDTO = new ResponseDTO();
responseDTO.wrapResponse(ServiceCodeEnum.E999998, "未知异常");
return responseDTO;
}
}
- Spring mvc 的配置如下(这里用到了mvc:annotation-driven):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cxf="http://cxf.apache.org/core"
xmlns:p="http://cxf.apache.org/policy" xmlns:ss="http://www.springframework.org/schema/security"
xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
http://cxf.apache.org/policy http://cxf.apache.org/schemas/policy.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
http://cxf.apache.org/bindings/soap http://cxf.apache.org/schemas/configuration/soap.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan
base-package="frame.web.controller;frame.web.advice" />
<!--===================== view resovler ===================== -->
<bean id="jstlViewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="order" value="1" />
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/jsp/" />
</bean>
<!-- 配置Fastjson支持 -->
<mvc:annotation-driven conversion-service="conversionService">
<mvc:message-converters register-defaults="true">
<bean
class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes" value="text/html;charset=UTF-8" />
<property name="features">
<array>
<value>WriteMapNullValue</value>
<value>WriteNullStringAsEmpty</value>
</array>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- 自定义参数转换 -->
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
</bean>
</beans>
遇到的一个坑是当spring mvc配置文件不用<mvc:annotation-drive>这个标签而是手动将RequestMappingHandlerMapping与RequestMappingHandlerAdapter这两个类让spring容器管理,上面的ControllerAdvice将不起作用
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" />
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="webBindingInitializer">
<bean
class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
<property name="conversionService" ref="conversionService" />
</bean>
</property>
<property name="messageConverters">
<list>
<ref bean="mappingJackson2HttpMessageConverter" />
<ref bean="stringHttpMessageConverter" />
</list>
</property>
</bean>
Spring MVC是如何处理异常的
下面来看看Spring MVC是如何处理异常的,为什么我手动配置了RequestMappingHandlerMapping和RequestMappingHandlerAdapter ControllerAdvice就不会对异常进行拦截呢而通过<mvc:annotation-drive>这个标签就可以呢?我们从Spring MVC的入口看一下异常是如何处理的。下面是关键代码(关键代码都有相应的注释):
public class DispatcherServlet extends FrameworkServlet {
/**
*这个方法是Spring MVC的入口方法,可以看到Spring MVC人具体处理流程
**/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
//这里有个try,下面的catch就是用于处理异常的
try {
//检查是否是上传文件的请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
//根据请求的request得到HandlerExecutionChain 对象,里面有Inceptor和相应的Controller
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response)