SpringMVC 异常拦截和处理流程

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜

文章合集 : 🎁 juejin.cn/post/694164…

Github : 👉 github.com/black-ant

CASE 备份 : 👉 gitee.com/antblack/ca…

一 . 前言

这一篇只关注一个小点 , 学习一下 SpringMVC 是如何进行异常拦截和处理的

二 . 使用

以最基础的 Exception 拦截的使用为例 , 常见的使用方式为 :

1
2
3
4
java复制代码@ExceptionHandler(Exception.class)
public void deaCommonException(Exception exception, HttpServletResponse response) {
logger.info("------> 处理通用异常 <-------");
}

当在请求中触发了异常之后 , 就会被该通用拦截器拦截到 , 最终给前端抛出 500 异常 .

那么整个流程里面 , 代码层面是怎么处理的呢 ?

三 . 源码梳理

3.1 拦截的入口

方法是在 DispatcherServlet # doDispatch 中进行核心的处理 , 当方法中出现异常了 , 自然也能在其中被 catch 处理掉 , 其主要流程为 :

  • Step 1 : 调用方法执行
  • Step 2 : 抛出异常后被 catch 捕获 , 记录该异常 , 并不往更外层抛出
  • Step 3 : processDispatchResult 中如果发现存在异常 , 则进行异常的处理

doDispatch

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
java复制代码protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {


try {
ModelAndView mv = null;
// 准备异常接收对象
Exception dispatchException = null;

try {
// 省略主流程处理 , 主要是 handler 的调用
mappedHandler = getHandler(processedRequest);
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

}

// 此处接收 exception , 映射给接收对象
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 核心逻辑 , 对 Exception 进行处理
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}

processDispatchResult

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
java复制代码private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {

boolean errorView = false;

if (exception != null) {
// 视图处理类 ,特定条件的异常会转发给特定的处理视图 , 可以在处理程序处理期间的任何时间抛出
if (exception instanceof ModelAndViewDefiningException) {
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
// Step 1 : 获取当前请求的 handler 类 , 业务处理类
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// Step 2 : 核心 , 处理主流程处理
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}


if (mv != null && !mv.wasCleared()) {
// 渲染给定的ModelAndView -> resolveViewName
render(mv, request, response);
if (errorView) {

// request.removeAttribute("javax.servlet.error.status_code");
// request.removeAttribute("javax.servlet.error.exception_type");
// request.removeAttribute("javax.servlet.error.message");
// request.removeAttribute("javax.servlet.error.exception");
// request.removeAttribute("javax.servlet.error.request_uri");
// request.removeAttribute("javax.servlet.error.servlet_name");
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
}

if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
return;
}

if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}

3.2 异常的处理主流程

当异常被捕获到 , 并且通过 processHandlerException 发起异常处理流程后 , 会通过如下流程开始依次处理异常 :

  • DispatcherServlet # processHandlerException : 处理起点
  • HandlerExceptionResolverComposite # resolveException
  • AbstractHandlerExceptionResolver # resolveException
  • AbstractHandlerMethodExceptionResolver # doResolveException
  • ExceptionHandlerExceptionResolver # doResolveHandlerMethodException
  • ServletInvocableHandlerMethod # invokeAndHandle : 调用具体方法
  • 调用最后的 @ExceptionHandler 处理方法处理异常
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
java复制代码protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {

// 成功和错误响应可能使用不同的内容类型
// HandlerMapping.class.getName() + ".producibleMediaTypes";
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
// -
// - DefaultErrorAttributes
// - HandlerExceptionResolverComposite
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}

throw ex;
}

AnnotationConfigServletWebServerApplicationContext_parent.png

循环 Exception resolve 列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

if (this.resolvers != null) {
// 遍历 resolvers 处理类 , 常见的有以下几种 , 添加默认异常解析器
// - ExceptionHandlerExceptionResolver
// - ResponseStatusExceptionResolver
// - DefaultHandlerExceptionResolver
for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (mav != null) {
return mav;
}
}
}
return null;
}

注意 , 这里调用的 resolveException 均为父类 AbstractHandlerExceptionResolver , 由父类再调用子类 doResolveException .

调用 Method 处理方法

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
java复制代码protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {

// 通过业务方法和异常类型从集合中获取去
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
}

// setHandlerMethodArgumentResolvers + setHandlerMethodReturnValueHandlers

ServletWebRequest webRequest = new ServletWebRequest(request, response);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();

// 此处会调用具体的代理方法 , 会根据是否存在 Throwable cause 分别调用2个不同的方法
exceptionHandlerMethod.invokeAndHandle

//如果完全处理了请求 ,这直接返回 ModelAndView
if (mavContainer.isRequestHandled()) {
return new ModelAndView();
}
else {
// 省略返回处理的 ModelAndView 或者 RedirectAttributes 对象
}
}

image.png

获取实际

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
72
73
74
java复制代码protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
@Nullable HandlerMethod handlerMethod, Exception exception) {

Class<?> handlerType = null;

// 如果 HandlerMethod 存在 , 则先尝试处理
if (handlerMethod != null) {
// 控制器类本身的局部异常处理程序方法 , 此处是具体的业务方法
handlerType = handlerMethod.getBeanType();
// 从缓存中获取当前异常类对应的 ExceptionResolver , 不存在则 new ExceptionHandlerMethodResolver , 并且缓存
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);

Method method = resolver.resolveMethod(exception);

// 如果控制器本身存在存在局部异常处理方法 , 则直接返回
if (method != null) {
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
}

// 代理类需要获取实际类
if (Proxy.isProxyClass(handlerType)) {
handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
}
}

// 此处遍历所有的异常类
for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
ControllerAdviceBean advice = entry.getKey();

if (advice.isApplicableToBeanType(handlerType)) {
// Exception 处理类 , 包含核心的类信息
ExceptionHandlerMethodResolver resolver = entry.getValue();
//
Method method = resolver.resolveMethod(exception);

if (method != null) {
return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
}
}
}

return null;
}

// 从缓存中尝试获取Method
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
Method method = this.exceptionLookupCache.get(exceptionType);
if (method == null) {
method = getMappedMethod(exceptionType);
// 不存在则获取后添加到缓存中
this.exceptionLookupCache.put(exceptionType, method);
}
return method;
}

private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<>();

// 遍历所有的 Method Mapper
// PS : 这里是第二个循环 ,第一个循环遍历 Class , 第二个循环遍历 Method
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
if (mappedException.isAssignableFrom(exceptionType)) {
matches.add(mappedException);
}
}
if (!matches.isEmpty()) {
matches.sort(new ExceptionDepthComparator(exceptionType));
// 返回匹配的第一个方法 ?
return this.mappedMethods.get(matches.get(0));
}
else {
return null;
}
}

四 . 补充 : @ExceptionHandler 的扫描和加载

这一部分来看一下 , @ExceptionHandler 是如何被扫描到容器中的.

1
2
3
4
5
6
7
java复制代码// 在 ExceptionHandlerExceptionResolver 中存在2个Map 用于存放对应的关联关系

// 用于保存 Class 对应的 Resolver , 对应业务Controller 对应 Resolver
Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =new ConcurrentHashMap<>(64);

// 对应 Error 处理类对应 resolver
Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =new LinkedHashMap<>();

4.1 扫描和注入

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
java复制代码// C- ExceptionHandlerExceptionResolver
public void afterPropertiesSet() {
// 初始化 Advice , 处理 ResponseBodyAdvice
initExceptionHandlerAdviceCache();

if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}

private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}

// 所有的 Error 处理类
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
// 通过 BeanType 构建 Resolver
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);

// 将 Resolver 存入缓存
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
}
}
}

总结

时间有限 , 整体来说还是过一下流程 ,方便后续的问题排查

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%