DoFilter называется дважды, предполагаемое поведение?

Я работаю над учебником по сервлету Java EE и пробовал пример настроения. Я заметил, что doFilter получает вызов дважды, как только вызов сервлета находится в цепочке, а во второй раз это не так.

Я добавил несколько printlns в TimeOfDayFilter.java и в MoodServlet.java, чтобы показать это.

TimeOfDayFilter.java:

    ...
    System.out.println("TimeOfDay before"); //added
    chain.doFilter(req, res);
    System.out.println("TimeOfDay after"); //added
    ...

MoodServlet.java:

    ...
    response.setContentType("text/html;charset=UTF-8");

    System.out.println("MoodServlet"); //added

    PrintWriter out = response.getWriter();
    ...

Результат от окна сервера Glassfish (3.1) при вызове сервлета выглядит следующим образом:

    INFO: mood was successfully deployed in 406 milliseconds.
    INFO: TimeOfDay before
    INFO: MoodServlet
    INFO: TimeOfDay after
    INFO: TimeOfDay before
    INFO: TimeOfDay after

Является ли это предполагаемым поведением? Если да, в чем причина дополнительного вызова?

Ответ 1

chain.doFilter(request,response);

Это приведет к управлению сервлетом, с которым связан фильтр. Но после выполнения соответствующего сервлета элемент управления возвращается в конце указанной строки, и все строки после этого в текущем doFilter() выполняются.

Если вы хотите передать управление на сервлет и не дать ему вернуться в фильтр, просто добавьте

return;

в конце строки chain.doFilter(запрос, ответ) в текущем фильтре.

Ответ 2

Метод Filter.doFilter вызывается один раз для каждого запроса. Вы можете выполнить некоторый код до вызова других фильтров в цепочке, а также после этого (в порядке, указанном в цепочке фильтров, согласно порядку web.xml filter-mapping), что-то вроде следующего примера:

public MyFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response,
           FilterChain chain) 
           throws IOException, ServletException
    {
        codeToExecuteBeforeOtherFiltersInTheChain(request, response);

        chain.doFilter(request, response);

        codeToExecuteAfterOtherFiltersInTheChain(request, response);

    }
}

Если ваш фильтр настроен на отправку запросов REQUEST и FORWARD, то метод MyFilter.doFilter будет вызываться один раз для исходного запроса и один раз, если запрос был перенаправлен:

Настройте сопоставление фильтра с помощью файла web.xml:

...
<filter-mapping>
      <filter-name>MyFilter</filter-name>
      <url-pattern>/*</url-pattern>
      <dispatcher>REQUEST</dispatcher>
      <dispatcher>FORWARD</dispatcher>
</filter-mapping>
...

Настройте сопоставление фильтров с помощью аннотации @WebFilter:

@WebFilter(urlPatterns = "/*", dispatcherTypes = {
    DispatcherType.REQUEST, DispatcherType.FORWARD
}) public MyFilter implements Filter {
    ...
}

Чтобы иметь возможность проверить, был ли запрос перенаправлен, вы можете использовать атрибут запроса, описанный здесь: Как узнать, когда запрос переадресован в объекте RequestWrapper

Подробнее о фильтрах см. Https://docs.oracle.com/cd/B32110_01/web.1013/b28959/filters.htm.

Ответ 3

Да, это так, что фильтр выполняется дважды в жизненном цикле, сначала звонит, когда запрос от клиента приходит в сервлет и второй раз, когда ответ отправляется клиенту после выполнения сервлета.

Порядок выполнения каким-то образом выглядит следующим образом.

Жизненный цикл фильтра

Ответ 4

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

out.println("<img src=\"resources/images/duke.snooze.gif\" alt=\"Duke sleeping\"/><br/>");

См. вывод журнала

2016-01-16T11:25:34.894+0100|Info: TimeOfDay doFilter method before sending to chain

2016-01-16T11:25:34.895+0100|Info: MoodServlet get method called

2016-01-16T11:25:34.895+0100|Info: TimeOfDay doFilter method after sending to chain

2016-01-16T11:25:34.942+0100|Info: TimeOfDay doFilter method before sending to chain

2016-01-16T11:25:34.942+0100|Info: TimeOfDay doFilter method after sending to chain

src в теге img - это не что иное, как второй запрос для сервера. Обратите внимание на шаблон URL, используемый в @WebFilter

@WebFilter(filterName = "TimeOfDayFilter",
urlPatterns = {"/*"},
initParams = {
    @WebInitParam(name = "mood", value = "awake")})

Он перехватит все запросы, поступающие в приложение для настроения. В качестве упражнения просто попробуйте удалить изображения из ответа или изменить шаблон url для перехвата только запросов, заканчивающихся в MoodServlet

@WebFilter(filterName = "TimeOfDayFilter",
urlPatterns = {"/report"},
initParams = {
    @WebInitParam(name = "mood", value = "awake")})

Оба будут приводить к одному вызову doFilter, как вы ожидали раньше.

2016-01-16T11:28:53.485+0100|Info: TimeOfDay doFilter method before sending to chain

2016-01-16T11:28:53.486+0100|Info: MoodServlet get method called

2016-01-16T11:28:53.487+0100|Info: TimeOfDay doFilter method after sending to chain

Ответ 5

Я сталкивался с той же проблемой, когда doFilter вызывается дважды (или несколько раз). Проблема заключалась в том, что фильтр обрабатывал каждый запрос, включая css, js, image и все другие файлы, в то время как я ожидал один запрос для каждой страницы, поэтому я решил эту проблему, добавив следующий код:

@WebFilter(filterName = "MyCustomFilter")
public class MyCustomFilter implements Filter {

  public void doFilter(ServletRequest request,ServletResponse response,
          FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    String accept = httpRequest.getHeader("accept");

    // Since the filter handles every request
    // we have to ensure that the request is asking for text/html
    if (accept == null || !accept.toLowerCase().startsWith("text/html")) {
      chain.doFilter(request, response);
      return;
    }

    // your code goes here

Надеюсь, что это поможет таким людям, как я, которые погуглили этот вопрос.

Ответ 6

Как сказал Мохан, @Component заставит ваш фильтр вызываться дважды, если он уже зарегистрирован в вашем классе Application, например:

resources.add(new MyFilter());

В этом случае вы должны выбрать между аннотированием или регистрацией. Но это действительно только для приложений JAX-RS, которые используют Spring. Не тема этого вопроса.

Ответ 7

Я решил ту же проблему после удаления @Component в классе CustomFilter.