Spring Filter 中的 chain.doFilter() 方法

1、简介

本文将带你了解 Spring 中 chain.doFilter() 方法的作用。

2、什么是 Spring Filter

在 Spring 应用中,Filter 过滤器以 Java Servlet Filter 为基础,后者代表拦截请求和响应的对象。Filter 是 Java Servlet API 的一部分,在 Web 应用中扮演着重要角色,因为它们位于客户端和服务器处理逻辑之间。

利用它们,我们可以在请求到达 servlet 之前或生成响应之后执行任务。过滤器的常见用例包括

  • 认证和授权
  • 审计和日志
  • 修改请求/响应

过滤器虽然不是 Spring 框架的一部分,但与 Spring 框架完全兼容。我们可以将它们注册为 Spring Bean,并在应用中使用它们。Spring 提供了一些过滤器的实现,其中常见的包括 OncePerRequestFilterCorsFilter

3、理解 chain.doFilter() 方法

在了解 chain.doFilter() 方法之前,首先要了解过滤器链(Filter Chain)的概念及其在过滤过程中的作用。

过滤器链表示应用于传入请求或传出响应的处理逻辑顺序流。换句话说,它是用于预处理请求或后处理响应的过滤器集合。这些过滤器以明确定义的顺序排列,确保每个过滤器都能在将请求或响应传递到链中的下一阶段之前执行其处理逻辑。

要定义过滤器链,可以使用了 Java Servlet API 中的 FilterChain 接口,其中包含了我们感兴趣的方法。查看该方法的签名,就会发现请求(request)和响应(response)对象被定义为输入参数:

void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException;

chain.doFilter() 方法将请求和响应传递给链中的下一个过滤器。如果过滤器链中没有过滤器,则会将请求转发给目标资源(通常是 Servlet),并将响应发送给客户端。

3.1、为什么调用 chain.doFilter() 非常重要?

我们可以看到,chain.doFilter() 方法起着至关重要的作用,因为它能确保请求继续通过链中的所有过滤器。如果省略了该方法调用,请求就不会进入后续过滤器或 Servlet。这可能会导致意外的行为,因为后续过滤器处理的任何逻辑都将无法到达。

而,在身份验证或授权失败的情况下,则需要跳过方法调用并中断调用链。

4、责任链模式

理解了 chain.doFilter() 后,来简单了解一下它与责任链模式的联系。这里不会太深入,只是简单描述一下该模式是什么,以及它与本文的主题有什么关联。

责任链模式是一种侧重于对象间交互的 设计模式。它解释了如何按顺序组织处理程序(即单个处理组件)来处理请求。每个处理程序执行一项特定任务,然后决定是否将请求传递给下一个处理程序进行进一步处理。

如果我们将模式逻辑与 Servlet 过滤器进行比较,就会发现过滤器是责任链模式在现实世界中的一个实例。每个过滤器都是一个独立的处理程序,负责处理逻辑的一部分。

使用这种模式的好处包括灵活性,因为我们可以添加、删除或重新排列过滤器,而无需修改其他组件。此外,“责任链” 模式允许过滤器专注于单一任务,从而改进了关注点的分离。

5、实现自定义 Filter

实现自定义过滤器相当简单,只需几个步骤。

在本例中,我们要创建两个带有日志语句的简单过滤器,并展示如何使用 chain.doFilter() 方法。

5.1、创建 Filter

首先,必须实现 Filter 接口,并覆盖 doFilter() 方法。

需要注意的是,该方法与过滤器链中的 doFilter() 方法不同。过滤器的 doFilter() 方法是实现过滤器特定处理逻辑的入口点,而 chain.doFilter() 则用于将请求和响应传递给链中的下一个过滤器。

如下,实现过滤器并使用 @Order 注解来排列它们:

@Order(1)
@Component
public class FirstFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        LOG.info("Processing the First Filter");
        // 故意省略 chain.doFilter() 调用
    }
}

@Order(2)
@Component
public class SecondFilter implements Filter {

    @Override
    public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
        LOG.info("Processing the Second Filter");
        chain.doFilter(request, response);
    }
}

5.2、chain.doFilter() 方法的应用

此时,当在应用中执行任何请求时,可以看到响应没有返回。原因是我们省略了 chain.doFilter() 的调用,从而中断了链。

如果观察控制台,就会发现第二个过滤器的日志不见了:

11:02:35.253 [main] INFO  c.baeldung.chaindofilter.FirstFilter - Processing the First Filter

要恢复过滤器链,应该在第一个过滤器中包含 chain.doFilter(request, response) 方法调用:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  throws IOException, ServletException {
    LOG.info("Processing the First Filter");
    chain.doFilter(request, response);
}

在这种情况下,第一个过滤器成功地将请求和响应传递给第二个过滤器,过滤器链具有连续的流程。

再次观察控制台,确认是否存在日志语句:

11:02:59.330 [main] INFO  c.baeldung.chaindofilter.FirstFilter - Processing the First Filter
11:02:59.330 [main] INFO  c.baeldung.chaindofilter.SecondFilter - Processing the Second Filter

5.3、注册过滤器

需要注意的是,我们用 @Component 注解了过滤器,以创建一个 Spring Bean。这一步很重要,因为它允许我们在 Servlet 容器中注册过滤器。

还有另一种注册过滤器的方法,它提供了更多的控制和自定义功能,即通过 FilterRegistrationBean 类来创建。使用该类,我们可以指定 URL 模式并定义过滤器的顺序。

6、总结

本文介绍了过滤器、过滤器链以及 chain.doFilter() 方法的正确使用,以及如何在 Spring 中创建和注册自己的自定义过滤器。

此外,还介绍了与责任链模式的联系,并强调了过滤器提供的灵活性和关注点分离。


Ref:https://www.baeldung.com/java-spring-chain-dofilter