为什么我的 Servlet Filter 运行 两次?

Why does my Servlet Filter run twice?

我有一个简单的 Servlet 过滤器:

package net.twentyonesolutions;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Date;

public class TestFilter implements Filter {

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        System.out.println(TestFilter.class.getSimpleName()       // line 12
                + " > "
                + ((HttpServletRequest)servletRequest).getRequestURL()
                + ": IP "+ servletRequest.getRemoteAddr()
                + "; at "
                + new Date().toString());

        // Pass request back down the filter chain
        filterChain.doFilter(servletRequest, servletResponse);    // line 20
    }

    public void init(FilterConfig filterConfig) throws ServletException {}

    public void destroy() {}
}

我像这样将它添加到 web.xml:

<filter>
    <filter-name>TestFilter</filter-name>
    <filter-class>net.twentyonesolutions.TestFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>TestFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

我运行在带有嵌入式 Jetty 的 IntelliJ IDEA 中将其设置为调试模式。我希望第 12 行的输出被写入一次,但它被写入了两次,而且两次都是在调用 Servlet 之前。

为什么调用了两次?它应该被调用两次吗?如果是这样,我怎么知道第一次和第二次,因为我希望我的代码只 运行 一次。

我发现了几个关于这个问题的老问题,但是 none 有一个真正的解决方案或解释。

您可以在下面的屏幕截图中看到第一次调用和第二次调用之间堆栈跟踪的差异:

更新

当我将过滤器部署到常规(非嵌入式)Jetty(与嵌入式版本相同,9.2.18.v20160721)或 Tomcat 时 - doFilter() 每次只执行一次请求,正如预期的那样,所以问题要么与 IntelliJ IDEA 中的调试有关,要么与嵌入式 Jetty 有关。

更新嵌入代码:

    WebAppContext webapp = new WebAppContext();
    webapp.setContextPath("/");
    webapp.setResourceBase(webRoot);
    webapp.setDefaultsDescriptor(webRoot + "/WEB-INF/web.xml");     

    Server server = new Server();

    ServerConnector http = new ServerConnector(server);
    http.setPort(port);             
    if (!host.equals(""))
        http.setHost(host);

    server.setConnectors(new ServerConnector[] { http });
    server.setHandler(webapp);      
    server.start();
    server.join();

更新filterChain.toString()

我在调用 filterChain.doFilter() 之前添加了 filterChain.toString() 的打印,它清楚地显示了差异。

filterChain 中的第一个对象(在第一次调用 doFilter() 之前)是我的过滤器。查看打印出来的两条线:

TestFilter > fdvnqp1k0ut37fenee569vshqf http://localhost:8080/index.cfm: IP 0:0:0:0:0:0:0:1; at Tue Sep 06 09:59:09 PDT 2016
    ... filterChain: TestFilter->CFMLServlet@cdad6a3==lucee.debug.loader.servlet.CFMLServlet,1,true
TestFilter > fdvnqp1k0ut37fenee569vshqf http://localhost:8080/index.cfm: IP 0:0:0:0:0:0:0:1; at Tue Sep 06 09:59:09 PDT 2016
    ... filterChain: CFMLServlet@cdad6a3==lucee.debug.loader.servlet.CFMLServlet,1,true

我不确定规范,但我希望我的 TestFilter 在第一次调用 TestFilter.doFilter() 之前从链中删除,不是吗?

我认为问题在于您将 WEB-INF/web.xml 设置为默认描述符。所以 web.xml 被 运行 两次 - 一次设置默认值,一次作为发现的 WEB-INF/web.xml - 所以你得到过滤器的两个实例。

不要设置默认描述符(如果您想知道默认值,请将其设置为 null - 但那样您将没有默认 servlet)。