【问题标题】:Internal workings of Servlet filters in SpringSpring 中 Servlet 过滤器的内部工作原理
【发布时间】:2019-09-19 20:38:38
【问题描述】:

我来自 Node-ExpressJS,所以我熟悉中间件的概念。在学习 Spring 的过程中,我知道了一个名为 Filter 的组件,它的作用类似于 Express 中的中间件,但有一些区别。

所以我试图了解 Filter 和 FilterChain 在 Spring 中的实际工作原理。

我有以下代码:

Filter1.java

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

    .....
    .....

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        LOGGER.info("############# Invoking Filter1 ############");
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        LOGGER.info("************ Moving on to next Filter");
        LOGGER.info("Adding new Attribute");
        req.setAttribute("Custom_Attribute_1", "TEST***TEST***TEST");
        chain.doFilter(request, response);
        resp.addHeader("1st Header", "1ST"); // Custom header that never shows up

        LOGGER.info("+++++++++ GOING BACK FROM Filter1 +++++++++");

    }

}

Filter2.java

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

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        LOGGER.info("############# Invoking Filter2 ####################");
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        req.setAttribute("Filter2 Attribute", "2nd ORDER");
        resp.addHeader("2nd Header", "2ND"); //Custom header that actually shows up
        chain.doFilter(request, response);      
        LOGGER.info("+++++++++++ GOING BACK FROM Filter2 ++++++++++");
    }

}

Controller.java

@RestController
public class Controller {

   @GetMapping("/")
   public ResponseEntity<Object> createResource(HttpServletRequest req) {
     return new ResponseEntity<Object>("Resource Created",HttpStatus.OK);
   }
}

当我使用 Postman 向控制器发送请求时,我只能在响应中看到我的一个自定义标头,即 2nd Header,但看不到另一个标头。

Postman 中的响应标头

2nd Header → 2ND
Content-Type → text/plain;charset=UTF-8
Content-Length → 15
Date → Thu, 19 Sep 2019 20:16:25 GMT

chain.doFilter(request, response) 的来电和这件事有关系吗?一旦调用了FilterChain 中的doFilter,似乎就无法修改Filter1 类中的response 对象。

我想在这里理解的是:

  1. 如果需要调用 FilterChain.doFilter 以将 request 对象传播到下一个过滤器并最终传播到控制器,则不应允许在调用后修改 response 对象chain.doFilter 退货?它在内部究竟是如何工作的?调用如何向下传播到控制器,然后返回到第一个过滤器?

  2. 另外,如果 Filter1 想查看从 Filter2 返回的响应正文并可能对其进行修改,它会怎么做? p>

【问题讨论】:

  • 这里没有魔法 - 对doFilter 的调用是阻塞。当它返回(正常或异常)时,下游过滤器和请求处理器本身就完成了。把它想象成俄罗斯套娃,而不是链条。
  • 您的代码不起作用的原因与此完全相关 - 一旦doFilter 返回主请求处理器已完成 - 它很可能已刷新流并发送内容。一旦标头已经发送,您就不能再发送更多标头了 - 没有通过 TCP 的引用传递!如果您想在之后添加标头,则需要缓冲整个响应 - 这可能非常昂贵甚至不可能。

标签: java spring spring-boot servlet-filters


【解决方案1】:

基本上,doFilter() 将请求和响应对象发送到 FilterChain 中的下一个过滤器,如 javadoc 中所述。

在您的第一个示例中,在添加“第一个标题”之前,您调用到链中的下一个过滤器。这就是为什么你一开始没有得到“第一个标题”的原因。此请求将进入控制器层,然后由您的控制器进行评估。当控制器处理完对象后,响应开始填充回过滤器。

所以你的代码是这样工作的

arrived Filter1 -> Filter 2 > add "2st Header" > .. > Controller > Controller runs and prepares a response object.

Controller Response > .... > Filter 2 > Filter 1 > add "1st Header" > ...

因此,当请求到达您的控制器时,控制器将永远不会在请求上下文中包含“第一个标头”。

另外,如果 Filter1 想在从 Filter2 返回后查看响应的正文并可能对其进行修改,它会怎么做?

我没有尝试过,但你应该看看this thread,它看起来像你想要实现的。

【讨论】:

    猜你喜欢
    • 2015-04-02
    • 2015-07-08
    • 2013-04-09
    • 2011-08-29
    • 2015-08-21
    • 2014-01-22
    • 1970-01-01
    • 2017-11-25
    • 2016-10-19
    相关资源
    最近更新 更多