Как добавить фильтры в сервлет без изменения web.xml

Я хотел бы иметь возможность изменять/настраивать фильтры иначе, чем web.xml. Вот статическая конфигурация из 2 фильтров. Я хотел бы иметь возможность статически настроить один фильтр и разрешить этому фильтру загружать дополнительные фильтры. Я просто хотел знать, знает ли кто-нибудь о библиотеке, в которой это уже есть.

Использование 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, где фильтры настраиваются во время выполнения.


person TJR    schedule 25.08.2011    source источник
comment
Это будет зависеть от контейнера сервлета, поэтому вы должны сообщить нам, какой из них вы используете.   -  person SJuan76    schedule 25.08.2011
comment
Должен ли он быть зависимым? Зависит ли GuiceFilter от контейнера?   -  person TJR    schedule 25.08.2011
comment
Guice запускает собственный механизм сопоставления, который ведет себя так же, как сопоставления web.xml — для веб-контейнера все запросы заканчиваются на GuiceFilter. Если вам нужен Guice, просто используйте его :)   -  person Philipp Reichart    schedule 27.09.2011


Ответы (4)


Просто сделайте ту же работу, что и контейнер. т.е. заново изобрести колесо шаблона проектирования цепочки ответственности в том виде, в котором он используется фильтрами сервлетов.

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();
        }
    }

}

с этими маленькими вспомогательными классами (которые при необходимости можно сделать private static вложенными классами выше GodFilter):

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 со всеми возможными фильтрами, чтобы упростить настройку. Вы можете использовать отражение для создания фильтров в init() вашего GodFilter.

Да ладно, это то, что web.xml и контейнер уже делают...

person BalusC    schedule 29.09.2011
comment
Разве GodFilterChain.doFilter() не должен перебирать все добавленные фильтры, а не использовать только первый? - person MRalwasser; 13.08.2012
comment
@MRalwasser: Э, нет. Возможно, вы упускаете из виду, как работает шаблон проектирования цепочки ответственности? Вызов chain.doFilter() в реализации фильтра вызовет GodFilterChain#doFilter() снова (поскольку this передается как аргумент FilterChain), а затем итератор перейдет к следующему элементу и так далее. - person BalusC; 13.08.2012
comment
ах, я совершенно упустил, что последующие вызовы iterator.next() будут выполняться внутри более глубоких уровней цепочки. Спасибо за указание на это. - person MRalwasser; 13.08.2012
comment
Это прекрасно работает! Но не могли бы вы объяснить использование Pattern? Зачем нам нужно заменить /* и удерживать позицию? - person Abylay Sabirgaliyev; 20.10.2014
comment
@ user99560: так что один и тот же код не нужно выполнять каждый раз без необходимости. Результат каждый раз один и тот же, поэтому логически эффективнее вычислить его один раз и сохранить для повторного использования. - person BalusC; 22.05.2015
comment
по этим причинам у вас 629к. это впечатляет и чисто. +1 - person natedennis; 11.02.2016
comment
@BalusC, это может быть еще один вопрос для публикации, но есть ли способ настроить фильтры в другом XML-файле, кроме их определения в web.xml? - person Agent47; 21.03.2020

Сервлет 3.0 имеет аннотацию @WebFilter для определения фильтра. Больше не нужно объявлять его в web.xml.

Но загрузка фильтра из фильтра не поддерживается. Вы могли бы реализовать это самостоятельно: это «просто» паттерн цепочки ответственности, но зачем вам это?

person JB Nizet    schedule 25.08.2011
comment
Удалил мой ответ, опередил меня на 30 секунд ›:| - person Dave; 25.08.2011
comment
@JB Nizet: TJR не запрашивает версию сервлета 3.0, он использует сервлет версии 2.5. - person developer; 25.08.2011
comment
@JB, я добавил к вопросу, что использую 2.5. Однако эта функция 3.0 делает меня счастливым. - person TJR; 25.08.2011
comment
Что искал @WebFilter(filterName = "customFilter", urlPatterns = { "/*" }) public class CustomFilter implements Filter .... делает это - person absin; 22.02.2018

Этого можно достичь за несколько простых шагов, даже для спецификации сервлета до версии 3.0:

  1. Добавьте фильтр, содержащий статическую и упорядоченную коллекцию классов (цепочку).
  2. Сопоставьте фильтр для перехвата каждого трафика.
  3. Управляйте порядком и существованием ваших вспомогательных классов (они будут вызываться вашим фильтром при перехвате трафика) в цепочке.

Ссылка: Xstream использует такой же шаблон для Serializer, но не с Servlet/Filter. :)

person Puspendu Banerjee    schedule 29.09.2011

Лично мне нравится @WebFilter. аннотацию для регистрации фильтров сервлетов.
Но другое решение — добавить фильтр во время выполнения с помощью ServletContext's addFilter.

Реализовать 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».

person R. Oosterholt    schedule 14.07.2021