最近在开发过滤器功能的时候遇到一个有趣的问题:
在 FilterChain#doFilter
方法之前抛出了一个异常,但是这个异常无法被异常处理器捕获,需要使用 HandlerExceptionResolver
来处理异常。
因此本文来分析一下这个问题。
本文使用的版本如下:
Spring Boot
:v3.1.5
Lombok
:v1.18.26
Hutool
:v5.8.15
Demo 打成了压缩包(在文章开头)可以自行下载使用。
请求 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
。
根据日志的打印结果来 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
日志信息可以看出异常抛出之后没有进入异常处理器,直接就写出了。
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
在自定义过滤器中使用 HandlerExceptionResolver
处理异常信息:
请求 URL:
http://localhost:8080/hello
请求结果:
修改之后返回了正确的错误信息以及错误码。
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: 在请求经过过滤器之后的操作
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
经过两种不同的方式的调用对比,可以得出以下结论:
FilterChain#doFilter
之前直接抛出异常,则过滤器链中断,抛出异常之后结果由 HttpEntityMethodProcessor
进行处理。HandlerExceptionResolver#resolveException
方法,异常会被 ExceptionHandlerExceptionResolver
进行处理,此时能够被异常拦截器捕获,最终返回的结果由 RequestResponseBodyMethodProcessor
进行处理。HandlerExceptionResolver
时,因为过滤器链没有中断,因此内部会继续执行 controller 方法,但是请求结果已经提前输出了。在没有异常的情况下,各组件调用顺序如下(不包含 ControllerAdvice
):
在本文基础上顺便测试了一下 Interceptor
以及 Aspect
,并在各个组件中抛出异常,得到的结果如下:
注:ControllerAdvice
即异常处理器
异常位置 | 异常抛出方式 | 调用方法 |
---|---|---|
Filter | HandlerExceptionResolver | FilterChain#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
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