Знакомство с фильтрами

Но и это еще не все. Правда, ты же не думаешь, что сервлеты устроены так просто?

Кроме сервлетов, которые мы уже разобрали, есть еще так называемые “служебные сервлеты” — фильтры. Они очень похожи на сервлеты, но их основная задача — помогать сервлетам обрабатывать запросы.

Фильтр — это как секретарь, а сервлет – директор. Прежде чем документ попадет на стол директору, он пройдет через руки секретаря. И после того, как директор его подпишет, он снова попадет секретарю, уже как исходящая корреспонденция, например.

Такой секретарь может отбраковывать часть запросов к директору (например, спам). Или давать стандартные ответы на известные ему вопросы (“директора нет на месте”). И так далее. Более того, таких секретарей может быть несколько: один может фильтровать спам сразу для всех директоров, другой перекидывать запросы между разными директорами и тому подобное.

Так же работают и фильтры:

служебные сервлеты”

Классы Filter, FilterChain, FilterConfig

Фильтры очень похожи на сервлеты, но с парой небольших отличий. Чтобы написать свой фильтр, нужно наследоваться от интерфейса javax.servlet.Filter.

У фильтра так же есть методы init() и destroy(). Вместо метода service() у фильтра есть метод doFilter(). И даже есть свой класс FilterConfig. Фильтр также добавляется в сервлет в файле web.xml или же с помощью аннотации @WebFilter.

Список методов:

Методы Описание
1 init(FilterConfig config) инициализация фильтра
2 destroy() выгрузка фильтра
3 doFilter(ServletRequest , ServletResponse, FilterChain) обработка (фильтрация) запроса

В чем же отличие сервлета и фильтра?

Фильтров может быть несколько, и они последовательно обрабатывают запрос (и ответ). Они объединены в так называемую цепочку — и для них даже есть специальный класс FilterChain.

После обработка запроса в методе doFilter() нужно вызвать метод doFilter() следующего фильтра в цепочке. Пример:


public class MyFilter implements Filter { 
 
  public void init(FilterConfig arg0) throws ServletException {
  } 
    
  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws Exception { 
        
      PrintWriter out = resp.getWriter(); 
      out.print("Дописываем что-то перед телом ответа"); 
        
      chain.doFilter(req, resp); //вызываем следующий фильтр в цепочке 
        
      out.print("Дописываем что-то после тела ответа"); 
    } 
 
  public void destroy() {
  } 
} 

Вообще-то так дописывать тело ответа нельзя. Формально фильтры и сервлеты независимы друг от друга и могут изменяться независимо. Их могут писать разные разработчики в разное время. Функция фильтров именно служебная, например:

  • Логирование всех входящих запросов (и ответов)
  • Сжатие данных
  • Шифрование (и расшифровка) данных
  • Валидация данных запроса
  • Добавление/удаление нужных заголовков
  • Перенаправление запросов
  • Контроль доступа (проверка, залогинен ли пользователь)

Класс RequestDispatcher

Иногда в процессе работы фильтра внутри метода doFilter() может возникнуть необходимость вызвать другой сервлет. Для этого у контейнера есть специальный объект RequestDispatcher.

Получить его можно двумя способами:

  • У объекта HttpServletRequest
  • У объекта ServletContext

Этот объект можно использовать для того, чтобы перенаправить существующий запрос на другой сервлет. Например, выяснилось, что пользователь не авторизован и мы хотим показать ему страницу с авторизацией. Ну или произошла ошибка на сервере и мы хотим отобразить пользователю error-страницу :)


public class HelloServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)  throws Exception {
           String path = "/error.html";
           ServletContext servletContext = this.getServletContext();
           RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher(path);
           requestDispatcher.forward(request, response);
    }
}

Также ты можешь вызвать RequestDispatcher из фильтра.


public class MyFilter implements Filter { 
 
  public void init(FilterConfig arg0) throws ServletException {
  } 
    
  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws Exception { 
           String path = "/error.html";
           ServletContext servletContext = req.getServletContext();
           RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher(path);
           requestDispatcher.forward(req, resp);
    } 
 
  public void destroy() {
  } 
} 

Обрати внимание, что запрос будет обработан в методе forward(), и вызывать doFilter()после использования RequestDispatcher не нужно.

Сравнение редиректа и форварда

И еще один важный момент. Если ты хочешь в своем сервлете перенаправить пользователя на другой URI, то сделать это можно двумя способами:

  • redirect
  • forward

Мы их уже разбирали, но для удобства проговорю это еще раз.

Когда ты выполняешь redirect через вызов response.sendRedirect("ссылка"), то сервер отсылает браузеру (клиенту) ответ 302 и указанную тобой ссылку. А браузер, проанализировав ответ сервера, загружает переданную тобой ссылку. То есть ссылка в браузере меняется на новую.

Если ты выполняешь forward через вызов requestDispatcher.forward(), то новый запрос выполняется внутри контейнера, и его ответ твой сервлет отсылает браузеру (клиенту) как ответ твоего сервлета. При этом браузер получает ответ от нового сервлета, но ссылка в браузере не меняется.