spring错误处理之HandlerExceptionResolver

错误处理接口

spring的错误处理主要是由接口HandlerExceptionResolver来定义的。不同的实现类有自己不同的错误处理机制。如果没有合适的Handler错误处理器,则最终会被容器处理,例如tomcat。好在spring本身提供了好多错误处理工我们使用,很少需要我们开发自己的错误处理器。

AbstractHandlerExceptionResolver

AbstractHandlerExceptionResolver是一个抽象类,定义了错误处理的逻辑框架。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {
private Set<?> mappedHandlers;

private Class<?>[] mappedHandlerClasses;

/**
* Check whether this resolver is supposed to apply (i.e. if the supplied handler
* matches any of the configured {@linkplain #setMappedHandlers handlers} or
* {@linkplain #setMappedHandlerClasses handler classes}), and then delegate
* to the {@link #doResolveException} template method.
*/
@Override
truepublic ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
truetrueif (shouldApplyTo(request, handler)) {
truetruetrueif (this.logger.isDebugEnabled()) {
truetruetruetruethis.logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
truetruetrue}
truetruetrueprepareResponse(ex, response);
truetruetrue//调用模板方法
truetruetrueModelAndView result = doResolveException(request, response, handler, ex);
truetruetrueif (result != null) {
truetruetruetruelogException(ex, request);
truetruetrue}
truetruetruereturn result;
truetrue} else {
truetruetruereturn null;
truetrue}
true}
true/**
* Check whether this resolver is supposed to apply to the given handler.
* <p>The default implementation checks against the configured
* {@linkplain #setMappedHandlers handlers} and
* {@linkplain #setMappedHandlerClasses handler classes}, if any.
* @param request current HTTP request
* @param handler the executed handler, or {@code null} if none chosen
* at the time of the exception (for example, if multipart resolution failed)
* @return whether this resolved should proceed with resolving the exception
* for the given request and handler
* @see #setMappedHandlers
* @see #setMappedHandlerClasses
*/
trueprotected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
truetrueif (handler != null) {
truetruetrueif (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
truetruetruetruereturn true;
truetruetrue}
truetruetrueif (this.mappedHandlerClasses != null) {
truetruetruetruefor (Class<?> handlerClass : this.mappedHandlerClasses) {
truetruetruetruetrueif (handlerClass.isInstance(handler)) {
truetruetruetruetruetruereturn true;
truetruetruetruetrue}
truetruetruetrue}
truetruetrue}
truetrue}
truetrue// Else only apply if there are no explicit handler mappings.
truetruereturn (this.mappedHandlers == null && this.mappedHandlerClasses == null);
true}
true/**
* 每个具体的子类来实现自己的异常处理逻辑
*/
trueprotected abstract ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex);
}

从上面的代码逻辑可以知道:所有AbstractHandlerExceptionResolver的子类必须实现自己的异常处理逻辑;可以选择覆盖shouldApplyTo方法来判断自己能否处理该Handler的异常。

SimpleMappingExceptionResolver

SimpleMappingExceptionResolver的处理逻辑

SimpleMappingExceptionResolver 这个异常处理器是最早使用的。出现在好多springMVC异常处理blog中。
它的异常逻辑很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 视图模型对象中存放异常对象的key
public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";
// 异常和异常视图的映射
private Properties exceptionMappings;
//不处理的异常类
private Class<?>[] excludedExceptions;

@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
truetrue//检查该异常对应的错误视图名
truetrueString viewName = determineViewName(ex, request);
truetrueif (viewName != null) {
truetruetrue// 检查该视图名是否配置了对应的http状态码
truetruetrueInteger statusCode = determineStatusCode(request, viewName);
truetruetrueif (statusCode != null) {
// 设置http响应状态码
truetruetruetrueapplyStatusCodeIfPossible(request, response, statusCode);
truetruetrue}
truetruetrue//获取待渲染的ModelAndView对象
truetruetruereturn getModelAndView(viewName, ex, request);
truetrue} else {
truetruetruereturn null;
truetrue}
}
protected ModelAndView getModelAndView(String viewName, Exception ex) {
truetrueModelAndView mv = new ModelAndView(viewName);
truetrueif (this.exceptionAttribute != null) {
truetruetrueif (logger.isDebugEnabled()) {
truetruetruetruelogger.debug("Exposing Exception as model attribute '" + this.exceptionAttribute + "'");
truetruetrue}
truetruetruemv.addObject(this.exceptionAttribute, ex);
truetrue}
truetruereturn mv;
}

SimpleMappingExceptionResolver使用例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">  
<!-- 定义默认的异常处理页面 -->
<property name="defaultErrorView" value="error"/>
<!-- 定义异常处理页面用来获取异常信息的变量名,默认名为exception -->
<property name="exceptionAttribute" value="ex"/>
<property name="excludedExceptions">
<list>
<value>org.apache.shiro.authz.UnauthorizedException</value>
</list>
</property>
<!-- 定义需要特殊处理的异常,用类名或完全路径名作为key,异常视图名作为值 -->
<property name="exceptionMappings">
<props>
<prop key="IOException">error/ioexp</prop>
<prop key="java.sql.SQLException">error/sqlexp</prop>
</props>
</property>
<!-- 异常视图名 到 http状态码的映射 -->
<property name="statusCodes">
<map>
<entry key="common/error/resourceNotFoundError" value="404" />
<entry key="common/error/dataAccessError" value="500" />
</map>
</property>
</bean>

SimpleMappingExceptionResolver存在的问题

SimpleMappingExceptionResolver对于响应是页面的请求异常是很合适的,但是请求的是REST接口的话,该异常处理器就不合适了。

DefaultHandlerExceptionResolver

DefaultHandlerExceptionResolver 是springMVC默认的异常处理器之一。这个可以从springMVC默认的配置文件dispatchServlet.properties文件可以知道:

1
2
3
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
trueorg.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
trueorg.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

DefaultHandlerExceptionResolver的处理逻辑很简单,就是判断异常的类型然后返回对应的ModelAndView
如果异常不在if else 列表中则返回null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
true@SuppressWarnings("deprecation")
trueprotected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
truetruetry {
truetruetrueif (ex instanceof org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) {
truetruetruetruereturn handleNoSuchRequestHandlingMethod((org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) ex,
truetruetruetruetruetruerequest, response, handler);
truetruetrue}
truetruetrueelse if (ex instanceof HttpRequestMethodNotSupportedException) {
truetruetruetruereturn handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request,
truetruetruetruetruetrueresponse, handler);
truetruetrue}
truetruetrue//省略一大堆esle if判断
} catch(Exception handlerException) {
if (logger.isWarnEnabled()) {
truetruetruetruelogger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
truetruetrue}
}
return null;

ResponseStatusExceptionResolver

该处理器用来哪些标注了ResponseStatus注解的异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
//判断异常类型是否含有 ResponseStatus 注解
ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
if (responseStatus != null) {
try {
return resolveResponseStatus(responseStatus, request, response, handler, ex);
}
catch (Exception resolveEx) {
logger.warn("Handling of @ResponseStatus resulted in Exception", resolveEx);
}
}
else if (ex.getCause() instanceof Exception) {
ex = (Exception) ex.getCause();
return doResolveException(request, response, handler, ex);
}
return null;
}
/**
* 根据 ResponseStatus 注解的属性值设置response的响应码和对应的解释消息
*/
protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
trueint statusCode = responseStatus.code().value();
trueString reason = responseStatus.reason();
trueif (this.messageSource != null) {
truetruereason = this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale());
true}
trueif (!StringUtils.hasLength(reason)) {
truetrueresponse.sendError(statusCode);
true}
trueelse {
truetrueresponse.sendError(statusCode, reason);
true}
truereturn new ModelAndView();
}
```

### ExceptionHandlerExceptionResolver

该处理器在Handler的请求处理方法发生异常后,调用该Handler上有注解:`@ExceptionHandler`的方法来处理异常。

`ExceptionHandlerExceptionResolver`继承自`AbstractHandlerMethodExceptionResolver`

#### AbstractHandlerMethodExceptionResolver

```java
public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExceptionResolver {
@Override
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
if (handler == null) {
return super.shouldApplyTo(request, handler);
}
// 只处理 HandlerMethod 调用中发生的异常。
else if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
handler = handlerMethod.getBean();
return super.shouldApplyTo(request, handler);
}
else {
return false;
}
true }
true @Override
trueprotected final ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
truetruereturn doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
true}
true/**
* 模板方法,子类来实现
*/
trueprotected abstract ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception ex);
}

ExceptionHandlerExceptionResolver的异常处理逻辑

1
2
3
4
5
6
7
8
/**
* 查找一个标有注解@ExceptionHandler 的方法,调用该方法来处理异常。
*/
@Override
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {
truetrue//省略代码
}

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Controller
public class IndexController {
@RequestMapping("/")
public ModelAndView index(){
throw new RuntimeException("error occur");
}

@ExceptionHandler(RuntimeException.class)
public ModelAndView error(RuntimeException error, HttpServletRequest request) {
ModelAndView mav = new ModelAndView();
mav.setViewName("error");
mav.addObject("param", "Runtime error");
return mav;
}

@ExceptionHandler()
public ModelAndView error(Exception error, HttpServletRequest request, HttpServletResponse response) {
ModelAndView mav = new ModelAndView();
mav.setViewName("error");
mav.addObject("param", "Exception error");
return mav;
}
}

ControllerAdvice

如果在每个Controller里面都写一个或多个标注了注解ExceptionHandler的异常处理方法是非常繁琐的,而且代码是重复的,当然不能这么做。 可以将这些公有的方法抽离到父类中。还有一种方案就是将这些方法抽离到标注了注解ControllerAdvice的类中,并将该类注册到容器中。

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 页面
*/
@ExceptionHandler(value = {BusinessException.class})
public ModelAndView errorHandler(HttpServletRequest servletRequest, BusinessException e) {
//自定义的处理逻辑
}
/**
* 接口
*/
@ExceptionHandler(value = {BusinessException.class})
@ResponseBody
public WebResult<String> jsonErrorHandler(HttpServletRequest servletRequest, BusinessException e) {
//自定义的处理逻辑
}
}
文章目录
  1. 1. 错误处理接口
  2. 2. AbstractHandlerExceptionResolver
  3. 3. SimpleMappingExceptionResolver
    1. 3.1. SimpleMappingExceptionResolver的处理逻辑
    2. 3.2. SimpleMappingExceptionResolver使用例子:
    3. 3.3. SimpleMappingExceptionResolver存在的问题
  4. 4. DefaultHandlerExceptionResolver
  5. 5. ResponseStatusExceptionResolver
    1. 5.1. ExceptionHandlerExceptionResolver的异常处理逻辑
    2. 5.2. 使用示例
  6. 6. ControllerAdvice
|