异常处理
# 异常处理机制
要想查看异常处理源码,在 idea
中下载spring-webmvc
源码,然后双击 shift
搜索 DispatcherServlet
查看。
在 Spring MVC 的
org.springframework.web.servlet.DispatcherServlet.java
中,系统通过下面代码尝试调用请求对应的函数,如果代码抛出异常,先将异常
存储在变量 dispatchException
中。最后调用方法 processDispatchResult()
来处理结果。
try {
//...
// Actually invoke the handler.尝试调用请求对应控制器中的函数
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}catch (Exception ex) {
// 如果有异常,则先存储起来
dispatchException = ex;
}
// 进入结果处理流程
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
2
3
4
5
6
7
8
9
10
在下面 dispatcheDispatchResult()
方法中,先判断抛出的异常是否属于 ModelAndViewDefiningException
异常,是则走入对应流程,但这不是本文重点。当不满足条件进入带 else
代码块中时,会通过 processHandlerException()
来进入其他异常处理流程。
//processDispatchResult:
boolean errorView = false;
// 如果有异常,则进入异常处理流程
if (exception != null) {
// ModelAndViewDefiningException 异常的处理
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
// 其他异常的处理
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// 进入异常处理流程
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在 processHandlerException()
函数中,通过注册在容器中的HandlerExceptionResolver
,遍历匹配最佳的ExceptionResolver
来处理这个异常。
//processHandlerException():
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
// 遍历匹配处理
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
// 尝试处理
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
通过程序断点调式,打印出 this.handlerExceptionReesolvers
的值:
handlerExceptionResolvers:
|--DefaultErrorAttributes #默认
|--HandlerExceptionResolverCompostie # composite
|--ExceptionHandlerExceptionResolver #1
|-- ResponseStatusExceptionResolver #2
|-- ? instanceof ResponseStatusException
|-- annotation of ResponseStatus
|--DefaultHandlerExceptionResolver
2
3
4
5
6
7
8
当遍历到#1时候,即 ExceptionHandlerExceptionResolver
。它的处理机制是在容器中寻找有注解 @HandlerException()
的方法,该注解中的值中
有异常列表,如果抛出的异常在这个列表中,则会调用这个方法来处理。
如果1不满足条件,则会来到#2处,即ResponseStatusExceptionResolver
。首先会在容器中寻找 ResponseStatusException
的实例,找不到则寻找
被 @ResponseStatus
注解的异常类,如果这个异常类刚好是代码中抛出的异常,则用注解中的值来进行响应。
下面用 @ExceptionHandler
和 @ResponseStatus
示例。
# ExceptionHandler
要在方法上注解 @ExceptionHandler
, 首先在方法所在类上注解 @RestControllerAdvice
, 为了将该类扫描到Spring IOC容器中。
@ExceptionHandler
可以传入异常类数组,当这些异常被抛出时,会触发到对应的函数。
注意是 @RestControllerAdvice, 而不是 @ControllerAdvice,两者处理方式不一样。
@RestControllerAdvice
public class GlobalExceptionHander{
@ExceptionHandler({Exception.class})
public void handler(Exception e) {
//statement
}
}
2
3
4
5
6
7
8
场景:编码时当接口出现异常时,可以抛出自定的异常类,再通过这种方式捕捉异常,封装通用的返回体给客户端。
// 响应类
class Response<T> {
private String code;
private String message;
private T data;
// .. getter setter
}
class ServiceException extends RuntimeException {
private String code;
public ServiceException(String code, String message) {
super(message);
this.code = code;
}
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHander({ServiceException.class})
public Response handleServiceException(ServiceException e) {
e.printStackTrace();
return Response.builder().code(e.getCode()).message(e.getMessage());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# ResponseStatus
ResponseStatus 将异常类和返回信息绑定起来,只要某个抛出异常类,当没有ExceptionHandler
能处理时,系统会将这个类上的注解信息封装到通用的返回体中,
直接返回。如果异常没有被 ResponseStatus
注解,则进入下一阶段的异常处理流程。
@ResponseStatus(code = HttpStatus.FORBIDDEN, reasone= "Forbidden")
class ForbiddenException extends RuntimeException{
}
//
xx () {
throw new ForbiddenException(); // { "status": 403, "error": "Forbidden"}
}
2
3
4
5
6
7
8
通过 ResponseStatus 这种方式灵活性没有 ExceptionHandler 方式来的灵活,后者可以自定义响应体。