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

文章目录

    • 前言
    • 版本说明
    • 测试 Demo
      • 1、自定义过滤器 DemoFilter
      • 2、自定义业务异常 ServiceException
      • 3、自定义异常处理类 DemoExceptionHandler
      • 4、DemoController
      • 5、请求测试
    • 问题分析
      • 1、日志打印记录
      • 2、Debug 方法
    • 解决方案
      • 1、修改自定义过滤器
      • 2、请求测试
    • 解决方案分析
      • 1、日志打印记录
      • 2、Debug 方法
    • 对比总结
    • 扩展
      • Filter、Interceptor、ControllerAdvice、Aspect 执行顺序
    • 附录
      • 附录1:直接抛出异常的详细日志

前言

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

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

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

版本说明

本文使用的版本如下:

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

测试 Demo

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

1、自定义过滤器 DemoFilter

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第1张图片

2、自定义业务异常 ServiceException

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第2张图片

3、自定义异常处理类 DemoExceptionHandler

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第3张图片

4、DemoController

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第4张图片

5、请求测试

请求 URL:

http://localhost:8080/hello

请求结果:

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第5张图片

返回了错误码 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

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第6张图片

org.springframework.web.servlet.DispatcherServlet#logRequest

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第7张图片

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第8张图片

org.springframework.web.servlet.DispatcherServlet#doDispatch

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第9张图片

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

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

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第10张图片

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

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第11张图片

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

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第12张图片

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第13张图片

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第14张图片

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第15张图片

解决方案

1、修改自定义过滤器

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

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第16张图片

2、请求测试

请求 URL:

http://localhost:8080/hello

请求结果:

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第17张图片

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

解决方案分析

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 方法

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第18张图片

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第19张图片

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

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第20张图片

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

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第21张图片

DemoExceptionHandler#handleServiceException

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第22张图片

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

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第23张图片

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

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第24张图片

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第25张图片

【Spring MVC】Filter 过滤器异常处理 HandlerExceptionResolver 分析_第26张图片

对比总结

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

  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 即异常处理器

异常位置 异常抛出方式 调用方法
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

附录

附录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

你可能感兴趣的:(Spring,/,Spring,Boot,学习笔记,spring,mvc,java)