1. 概述
在这个教程中,我们将学习Spring中的特殊过滤器OncePerRequestFilter
。我们将了解它解决的问题,并通过一个快速示例理解如何使用它。
2. 什么是OncePerRequestFilter
?
首先,让我们了解一下过滤器的工作原理。一个Filter(过滤器)
可以在Servlet执行前后被调用。当请求被转发到Servlet时,RequestDispatcher
可能会将其转发到另一个Servlet。在这种情况下,同一个过滤器可能会被多次调用。
然而,我们可能希望确保某个特定的过滤器对每个请求只执行一次。在使用Spring Security时,这是一个常见的用例:当请求经过过滤器链时,我们可能希望某些身份验证操作只为请求执行一次。
在这样的情况下,我们可以扩展OncePerRequestFilter
。Spring保证对于给定的请求,OncePerRequestFilter
只执行一次。
3. 使用OncePerRequestFilter
处理同步请求
让我们通过一个例子来理解如何使用这个过滤器。我们将定义一个名为AuthenticationFilter
的类,它继承自OncePerRequestFilter
,并重写doFilterInternal()
方法:
public class AuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String usrName = request.getHeader(“userName”);
logger.info("Successfully authenticated user " +
usrName);
filterChain.doFilter(request, response);
}
}
由于OncePerRequestFilter
仅支持HTTP请求,因此我们不需要像实现Filter
接口时那样将request
和response
对象强制转换。
4. 使用OncePerRequestFilter
处理异步请求
对于异步请求,OncePerRequestFilter
默认不应用。我们需要重写shouldNotFilterAsyncDispatch()
和shouldNotFilterErrorDispatch()
方法来支持这种需求。
有时,我们可能希望过滤器仅在初始请求线程中应用,而不是在异步分派中创建的额外线程中。其他时候,我们可能需要在每个额外线程中至少调用一次过滤器。在这种情况下,我们需要重写shouldNotFilterAsyncDispatch()
方法。
如果shouldNotFilterAsyncDispatch()
方法返回true
,那么过滤器将不会在后续的异步分派中被调用。然而,如果返回false
,则过滤器将在每个异步分派中精确地执行一次,每次在一个线程中。
同样地,我们会重写shouldNotFilterErrorDispatch()
方法,并根据是否要过滤错误分派返回true
或false
:
@Component
public class AuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String usrName = request.getHeader("userName");
logger.info("Successfully authenticated user " +
usrName);
filterChain.doFilter(request, response);
}
@Override
protected boolean shouldNotFilterAsyncDispatch() {
return false;
}
@Override
protected boolean shouldNotFilterErrorDispatch() {
return false;
}
}
5. 条件性跳过请求
我们可以通过重写shouldNotFilter()
方法,使过滤器仅对某些特定请求有条件应用,而对其他请求跳过:
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return Boolean.TRUE.equals(request.getAttribute(SHOULD_NOT_FILTER));
}
6. 快速示例
让我们通过一个快速示例来理解OncePerRequestFilter
的行为。首先,我们将定义一个控制器,使用Spring的DeferredResult
异步处理请求:
@Controller
public class HelloController {
@GetMapping(path = "/greeting")
public DeferredResult<String> hello(HttpServletResponse response) throws Exception {
DeferredResult<String> deferredResult = new DeferredResult<>();
executorService.submit(() -> perform(deferredResult));
return deferredResult;
}
private void perform(DeferredResult<String> dr) {
// some processing
dr.setResult("OK");
}
}
在异步处理请求时,两个线程都会经过相同的过滤器链。因此,过滤器会被调用两次:第一次是容器线程处理请求,然后是异步分派完成后。一旦异步处理完成,响应将返回给客户端。
现在,让我们定义一个实现OncePerRequestFilter
的过滤器:
@Component
public class MyOncePerRequestFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
logger.info("Inside Once Per Request Filter originated by request {}", request.getRequestURI());
filterChain.doFilter(request, response);
}
@Override
protected boolean shouldNotFilterAsyncDispatch() {
return true;
}
}
在上述代码中,我们故意从shouldNotFilterAsyncDispatch()
方法返回true
。这是为了演示我们的过滤器只在容器线程中被调用,而在后续的异步线程中不会被调用。
让我们调用我们的端点来演示这一点:
curl -X GET http://localhost:8082/greeting
输出:
10:23:24.175 [http-nio-8082-exec-1] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
10:23:24.175 [http-nio-8082-exec-1] INFO o.s.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
10:23:24.176 [http-nio-8082-exec-1] INFO o.s.web.servlet.DispatcherServlet - Completed initialization in 1 ms
10:23:26.814 [http-nio-8082-exec-1] INFO c.b.O.MyOncePerRequestFilter - Inside OncePer Request Filter originated by request /greeting
现在,让我们看看一个案例,其中我们希望请求和异步分派都调用我们的过滤器。我们只需要将shouldNotFilterAsyncDispatch()
重写为返回false
即可实现这一点:
@Override
protected boolean shouldNotFilterAsyncDispatch() {
return false;
}
输出:
2:53.616 [http-nio-8082-exec-1] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
10:32:53.616 [http-nio-8082-exec-1] INFO o.s.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
10:32:53.617 [http-nio-8082-exec-1] INFO o.s.web.servlet.DispatcherServlet - Completed initialization in 1 ms
10:32:53.633 [http-nio-8082-exec-1] INFO c.b.O.MyOncePerRequestFilter - Inside OncePer Request Filter originated by request /greeting
10:32:53.663 [http-nio-8082-exec-2] INFO c.b.O.MyOncePerRequestFilter - Inside OncePer Request Filter originated by request /greeting
从上述输出中,我们可以看到我们的过滤器被调用了两次:第一次由容器线程,然后由另一个线程。
7. 总结
在这篇文章中,我们探讨了OncePerRequestFilter
,它解决了什么问题,以及如何通过一些实际示例来实现它。
如往常一样,完整的源代码可以在GitHub上找到。