首页 > 解决方案 > Spring 中 Servlet 过滤器的内部工作原理

问题描述

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

所以我试图了解 aFilterFilterChain实际在 Spring 中的工作方式。

我有以下代码:

过滤器1.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 +++++++++");

    }

}

过滤器2.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 ++++++++++");
    }

}

控制器.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)与它有什么关系吗?似乎一旦调用了类中的response对象就无法修改。Filter1doFilterFilterChain

我想在这里理解的是:

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

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

标签: javaspringspring-bootservlet-filters

解决方案


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

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

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

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 返回后查看响应的正文并可能对其进行修改,它会如何做呢?

我没有尝试过,但你应该看看这个线程,它看起来像你想要实现的。


推荐阅读