如何在不修改web.xml的情况下向servlet添加过滤器

33
我希望能够以一种不同于web.xml的方式修改/配置过滤器。这是2个静态配置的过滤器。我想要的是有一个过滤器是静态配置的,允许该过滤器加载其他过滤器。我只是想知道是否有人知道已经有这样的库了。
使用 Servlet API 2.5。
<web-app>
  ...
  <filter>
    <filter-name>MyFilter1</filter-name>
    <filter-class>com.me.MyFilter1</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>MyFilter1</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  ...
  <filter>
    <filter-name>MyFilter2</filter-name>
    <filter-class>com.me.MyFilter2</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>MyFilter2</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  ...
</web-app>

我曾在Guice中看到这样的做法,使用GuiceFilter在运行时配置过滤器。


这将取决于Servlet容器,因此您应该告诉我们您正在使用哪个容器。 - SJuan76
必须要依赖吗?GuiceFilter 与容器有依赖关系吗? - TJR
Guice使用自己的映射机制,其行为与web.xml映射完全相同--对于Web容器,所有请求都在GuiceFilter处结束。如果您想要使用Guice,只需使用它即可 :) - Philipp Reichart
4个回答

41

只需执行与容器已经执行的相同工作。也就是说,重新发明责任链设计模式的轮子,就像Servlet过滤器所使用的那样。

public class GodFilter implements Filter {

    private Map<Pattern, Filter> filters = new LinkedHashMap<Pattern, Filter>();

    @Override
    public void init(FilterConfig config) throws ServletException {
        Filter1 filter1 = new Filter1();
        filter1.init(config);
        filters.put(new Pattern("/foo/*"), filter1);

        Filter2 filter2 = new Filter2();
        filter2.init(config);
        filters.put(new Pattern("*.bar"), filter2);

        // ...
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest hsr = (HttpServletRequest) request;
        String path = hsr.getRequestURI().substring(hsr.getContextPath().length());
        GodFilterChain godChain = new GodFilterChain(chain);

        for (Entry<Pattern, Filter> entry : filters.entrySet()) {
            if (entry.getKey().matches(path)) {
                godChain.addFilter(entry.getValue());
            }
        }

        godChain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        for (Filter filter : filters.values()) {
            filter.destroy();
        }
    }

}

利用这些小帮手类(如果需要,可以将其制作为上述GodFilterprivate static嵌套类):

public class Pattern {

    private int position;
    private String url;

    public Pattern(String url) {
        this.position = url.startsWith("*") ? 1
                      : url.endsWith("*") ? -1
                      : 0;
        this.url = url.replaceAll("/?\\*", "");
    }

    public boolean matches(String path) {
        return (position == -1) ? path.startsWith(url)
             : (position == 1) ? path.endsWith(url)
             : path.equals(url);
    }

}

并且

public class GodFilterChain implements FilterChain {

    private FilterChain chain;
    private List<Filter> filters = new ArrayList<Filter>();
    private Iterator<Filter> iterator;

    public GodFilterChain(FilterChain chain) {
        this.chain = chain;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if (iterator == null) {
            iterator = filters.iterator();
        }

        if (iterator.hasNext()) {
            iterator.next().doFilter(request, response, this);
        } else {
            chain.doFilter(request, response);
        }
    }

    public void addFilter(Filter filter) {
        if (iterator != null) {
            throw new IllegalStateException();
        }

        filters.add(filter);
    }

}

如果必要的话,您还可以使用XML配置文件提供所有可能的过滤器,以便最终获得更简单的配置。您可以在GodFilterinit()中使用反射来创建过滤器。

哦,不用担心,这就是web.xml和容器已经在做的事情了...


GodFilterChain.doFilter() 不应该循环遍历所有添加的过滤器,而只使用第一个吗? - MRalwasser
@MRalwasser:嗯,不是这样的。也许你没有理解“责任链”设计模式的工作原理?在过滤器的实现中,chain.doFilter()调用将再次调用GodFilterChain#doFilter()(因为它作为FilterChain参数被传递给了this),然后迭代器会继续前进到下一个元素,以此类推。 - BalusC
1
@user99560:这样就不必每次都执行完全相同的代码,从而提高逻辑效率。结果每次都是相同的,因此计算一次并获取其重用是更加高效的。 - BalusC
1
这些是你拥有629k的原因。这很令人印象深刻和干净。+1 - natedennis
@BalusC,这可能是另一个要发布的问题,但是否有一种方法可以在不在web.xml中定义它们的情况下,在不同的xml文件中配置过滤器? - Agent47
显示剩余2条评论

19

Servlet 3.0引入了@WebFilter注解来定义过滤器,不再需要在web.xml中声明。

但是从一个过滤器(load a filter)加载另一个过滤器并不支持,你可以自行实现:它只是责任链模式(chain of responsibility pattern),但为什么要这样做呢?


2
我的回答被删除了,你比我快了30秒 >:| - Dave
2
@JB Nizet:TJR 并不是在询问 Servlet 3.0 版本,他特别需要 Servlet 2.5。 - developer
@JB,我已经在问题中添加了我正在使用2.5版本。虽然3.0的功能确实让我很高兴。 - TJR
1
我在寻找什么?@WebFilter(filterName = "customFilter", urlPatterns = { "/*" }) public class CustomFilter implements Filter .... 这是什么意思? - absin

4

即使是在3.0之前的Servlet规范下,也可以通过简单的步骤实现:

  1. 添加一个包含静态有序类集合(链)的过滤器。
  2. 将该过滤器映射到拦截所有流量。
  3. 在链中操作您的辅助类的顺序和存在(这些类将在拦截流量时由您的过滤器私下调用)。

参考:Xstream使用相同类型的模式进行序列化,不过不是使用Servlet/Filter。 :)


1
我个人喜欢使用 @WebFilter 注解注册 Servlet 过滤器。
但另一种解决方案是使用 ServletContextaddFilter 函数在运行时添加过滤器。

实现 ServletContextListener,例如:

public class MyContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent ce) {
        ServletContext servletContext = ce.getServletContext();
    
        // you can even conditionally add this
        servletContext.addFilter("My filter 1", MyFilter1.class)
                .addMappingForUrlPatterns(allOf(DispatcherType.class), false, "/*");
    }
}

注册监听器:
<listener>
    <listener-class>com.me.MyContextListener</listener-class>
</listener>   

当然,您需要实现一个过滤器。但是在您的问题中,您已经提到了一个示例过滤器“ MyFilter1”。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接