【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析

前言

最近在开发过滤器功能的时候遇到一个有趣的问题:

FilterChain#doFilter 方法之前抛出了一个异常,但是这个异常无法被异常处理器捕获,需要使用 HandlerExceptionResolver 来处理异常。

因此本文来分析一下这个问题。

版本说明

本文使用的版本如下:

  • Spring Bootv3.1.5
  • Lombokv1.18.26
  • Hutoolv5.8.15

测试 Demo

Demo 打成了压缩包(在文章开头)可以自行下载使用。

1、自定义过滤器 DemoFilter

在这里插入图片描述

2、自定义业务异常 ServiceException

在这里插入图片描述

3、自定义异常处理类 DemoExceptionHandler

在这里插入图片描述

4、DemoController

在这里插入图片描述

5、请求测试

请求 URL:

http://localhost:8080/hello

请求结果:

在这里插入图片描述

返回了错误码 500,且错误信息是:

{
  "timestamp": "2023-11-26T03:43:07.655+00:00",
  "status": 500,
  "error": "Internal Server Error",
  "path": "/hello"
}

这里并没有返回理想中的结果:

在自定义异常处理器重定义的错误码是 400,返回信息应该是 ServiceException: / by zero

问题分析

1、日志打印记录

根据日志的打印结果来 Debug 分析一下整个流程:

2023-11-26T11:49:25.841+08:00  INFO 5748 --- [  XNIO-1 task-2] space.zlyx.demo.mvc.filter.DemoFilter    : DemoFilterBefore: 在请求经过过滤器之前的操作
2023-11-26T11:49:25.842+08:00 ERROR 5748 --- [  XNIO-1 task-2] io.undertow.request                      : UT005023: Exception handling request to /hello

space.zlyx.demo.mvc.exception.ServiceException: ServiceException: / by zero
# 省略详细报错(完整日志可查看文末附录)

2023-11-26T11:49:25.842+08:00 DEBUG 5748 --- [  XNIO-1 task-2] o.s.web.servlet.DispatcherServlet        : "ERROR" dispatch for GET "/error", parameters={}
2023-11-26T11:49:25.843+08:00 DEBUG 5748 --- [  XNIO-1 task-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2023-11-26T11:49:25.843+08:00 DEBUG 5748 --- [  XNIO-1 task-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Using 'application/json', given [*/*] and supported [application/json, application/*+json]
2023-11-26T11:49:25.844+08:00 DEBUG 5748 --- [  XNIO-1 task-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Writing [{timestamp=Sun Nov 26 11:49:25 CST 2023, status=500, error=Internal Server Error, path=/hello}]
2023-11-26T11:49:25.845+08:00 DEBUG 5748 --- [  XNIO-1 task-2] o.s.web.servlet.DispatcherServlet        : Exiting from "ERROR" dispatch, status 500

日志信息可以看出异常抛出之后没有进入异常处理器,直接就写出了。

2、Debug 方法

org.springframework.web.servlet.DispatcherServlet#doService

在这里插入图片描述

org.springframework.web.servlet.DispatcherServlet#logRequest

在这里插入图片描述

在这里插入图片描述

org.springframework.web.servlet.DispatcherServlet#doDispatch

在这里插入图片描述

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle

在这里插入图片描述

org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor#handleReturnValue

在这里插入图片描述

org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

解决方案

1、修改自定义过滤器

在自定义过滤器中使用 HandlerExceptionResolver 处理异常信息:

在这里插入图片描述

2、请求测试

请求 URL:

http://localhost:8080/hello

请求结果:

在这里插入图片描述

修改之后返回了正确的错误信息以及错误码。

解决方案分析

1、日志打印记录

2023-11-26T13:02:11.053+08:00  INFO 11220 --- [  XNIO-1 task-2] space.zlyx.demo.mvc.filter.DemoFilter    : DemoFilterBefore: 在请求经过过滤器之前的操作
2023-11-26T13:02:11.053+08:00 DEBUG 11220 --- [  XNIO-1 task-2] .m.m.a.ExceptionHandlerExceptionResolver : Using @ExceptionHandler space.zlyx.demo.mvc.handler.DemoExceptionHandler#handleServiceException(ServiceException, HttpServletRequest)
2023-11-26T13:02:11.054+08:00 ERROR 11220 --- [  XNIO-1 task-2] s.z.d.mvc.handler.DemoExceptionHandler   : 业务异常捕获:Filter ServiceException: / by zero
2023-11-26T13:02:11.054+08:00 DEBUG 11220 --- [  XNIO-1 task-2] m.m.a.RequestResponseBodyMethodProcessor : Using 'text/plain', given [*/*] and supported [text/plain, */*, application/json, application/*+json]
2023-11-26T13:02:11.054+08:00 DEBUG 11220 --- [  XNIO-1 task-2] m.m.a.RequestResponseBodyMethodProcessor : Writing ["Filter ServiceException: / by zero"]
2023-11-26T13:02:11.055+08:00 DEBUG 11220 --- [  XNIO-1 task-2] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [ServiceException(code=null, message=Filter ServiceException: / by zero)]
2023-11-26T13:02:11.055+08:00 DEBUG 11220 --- [  XNIO-1 task-2] o.s.web.servlet.DispatcherServlet        : GET "/hello", parameters={}
2023-11-26T13:02:11.055+08:00 DEBUG 11220 --- [  XNIO-1 task-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to space.zlyx.demo.mvc.controller.DemoController#hello(HttpServletRequest, HttpServletResponse)
2023-11-26T13:02:11.055+08:00  INFO 11220 --- [  XNIO-1 task-2] s.z.demo.mvc.controller.DemoController   : DemoController hello
2023-11-26T13:02:11.055+08:00 DEBUG 11220 --- [  XNIO-1 task-2] m.m.a.RequestResponseBodyMethodProcessor : Found 'Content-Type:text/plain;charset=UTF-8' in response
2023-11-26T13:02:11.055+08:00 DEBUG 11220 --- [  XNIO-1 task-2] m.m.a.RequestResponseBodyMethodProcessor : Writing ["Hello World"]
2023-11-26T13:02:11.056+08:00 DEBUG 11220 --- [  XNIO-1 task-2] o.s.web.servlet.DispatcherServlet        : Completed 400 BAD_REQUEST
2023-11-26T13:02:11.056+08:00  INFO 11220 --- [  XNIO-1 task-2] space.zlyx.demo.mvc.filter.DemoFilter    : DemoFilterAfter: 在请求经过过滤器之后的操作

2、Debug 方法

在这里插入图片描述

在这里插入图片描述

org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#doResolveHandlerMethodException

在这里插入图片描述

org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle

在这里插入图片描述

DemoExceptionHandler#handleServiceException

在这里插入图片描述

org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue

在这里插入图片描述

org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

对比总结

经过两种不同的方式的调用对比,可以得出以下结论:

  1. FilterChain#doFilter 之前直接抛出异常,则过滤器链中断,抛出异常之后结果由 HttpEntityMethodProcessor 进行处理。
  2. 使用 HandlerExceptionResolver#resolveException 方法,异常会被 ExceptionHandlerExceptionResolver 进行处理,此时能够被异常拦截器捕获,最终返回的结果由 RequestResponseBodyMethodProcessor 进行处理。
  3. 使用 HandlerExceptionResolver 时,因为过滤器链没有中断,因此内部会继续执行 controller 方法,但是请求结果已经提前输出了。

扩展

Filter、Interceptor、ControllerAdvice、Aspect 执行顺序

在没有异常的情况下,各组件调用顺序如下(不包含 ControllerAdvice):

  • FilterChain#doFilter 之前
  • Interceptor#preHandle
  • Aspect#before
  • Controller
  • Aspect#afterReturning
  • Aspect#after
  • Interceptor#postHandle
  • Interceptor#afterCompletion
  • FilterChain#doFilter 之后

在本文基础上顺便测试了一下 Interceptor 以及 Aspect,并在各个组件中抛出异常,得到的结果如下:

注:ControllerAdvice 即异常处理器

异常位置异常抛出方式调用方法
FilterHandlerExceptionResolverFilterChain#doFilter 之前(异常)
ControllerAdvice
Interceptor#preHandle
Aspect#before
Controller
Aspect#afterReturning
Aspect#after
Interceptor#postHandle
Interceptor#afterCompletion
FilterChain#doFilter 之后
Interceptor直接抛异常FilterChain#doFilter 之前
Interceptor#preHandle(异常)
ControllerAdvice
FilterChain#doFilter 之后
Aspect直接抛异常FilterChain#doFilter 之前
Interceptor#preHandle
Aspect#before(异常)
ControllerAdvice
Interceptor#afterCompletion
FilterChain#doFilter 之后
Controller直接抛异常FilterChain#doFilter 之前
Interceptor#preHandle
Aspect#before
Controller(异常)
Aspect#afterThrowing
Aspect#after
ControllerAdvice
Interceptor#afterCompletion
FilterChain#doFilter 之后

请求进入执行顺序:
Filter —— Interceptor —— Aspect —— Controller

异常抛出执行顺序:
Controller —— Aspect —— ControllerAdvice —— Interceptor —— Filter

附录

附录1:直接抛出异常的详细日志

2023-11-26T11:49:25.841+08:00  INFO 5748 --- [  XNIO-1 task-2] space.zlyx.demo.mvc.filter.DemoFilter    : DemoFilterBefore: 在请求经过过滤器之前的操作
2023-11-26T11:49:25.842+08:00 ERROR 5748 --- [  XNIO-1 task-2] io.undertow.request                      : UT005023: Exception handling request to /hello

space.zlyx.demo.mvc.exception.ServiceException: ServiceException: / by zero
	at space.zlyx.demo.mvc.filter.DemoFilter.doFilter(DemoFilter.java:40) ~[classes/:na]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:67) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.0.13.jar:6.0.13]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.13.jar:6.0.13]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:67) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.0.13.jar:6.0.13]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.13.jar:6.0.13]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:67) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.0.13.jar:6.0.13]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.13.jar:6.0.13]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:67) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:117) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46) ~[undertow-core-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60) ~[undertow-core-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43) ~[undertow-core-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:276) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:132) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:256) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:101) ~[undertow-servlet-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.server.Connectors.executeRootHandler(Connectors.java:393) ~[undertow-core-2.3.10.Final.jar:2.3.10.Final]
	at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:859) ~[undertow-core-2.3.10.Final.jar:2.3.10.Final]
	at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18) ~[jboss-threads-3.5.0.Final.jar:3.5.0.Final]
	at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513) ~[jboss-threads-3.5.0.Final.jar:3.5.0.Final]
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538) ~[jboss-threads-3.5.0.Final.jar:3.5.0.Final]
	at org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1282) ~[xnio-api-3.8.8.Final.jar:3.8.8.Final]
	at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

2023-11-26T11:49:25.842+08:00 DEBUG 5748 --- [  XNIO-1 task-2] o.s.web.servlet.DispatcherServlet        : "ERROR" dispatch for GET "/error", parameters={}
2023-11-26T11:49:25.843+08:00 DEBUG 5748 --- [  XNIO-1 task-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2023-11-26T11:49:25.843+08:00 DEBUG 5748 --- [  XNIO-1 task-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Using 'application/json', given [*/*] and supported [application/json, application/*+json]
2023-11-26T11:49:25.844+08:00 DEBUG 5748 --- [  XNIO-1 task-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Writing [{timestamp=Sun Nov 26 11:49:25 CST 2023, status=500, error=Internal Server Error, path=/hello}]
2023-11-26T11:49:25.845+08:00 DEBUG 5748 --- [  XNIO-1 task-2] o.s.web.servlet.DispatcherServlet        : Exiting from "ERROR" dispatch, status 500

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MichelleChung

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值