Фильтры
Некоторые действия нужны для каждого запроса: записать его в лог, добавить заголовок, проверить доступ. Копировать такой код в каждый сервлет неудобно. Фильтр — класс, через который запрос проходит до сервлета, а ответ — после. Фильтры срабатывают и для сервлетов, и для REST-ресурсов, и для статических файлов.
Фильтр-логгер
Создайте файл src/main/java/com/example/blog/LoggingFilter.java:
// src/main/java/com/example/blog/LoggingFilter.java
package com.example.blog;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter("/*")
public class LoggingFilter extends HttpFilter {
@Override
protected void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
long start = System.currentTimeMillis();
chain.doFilter(request, response);
long time = System.currentTimeMillis() - start;
System.out.println(request.getMethod() + " " + request.getRequestURI()
+ " -> " + response.getStatus() + " (" + time + " ms)");
}
}@WebFilter("/*") включает фильтр для всех адресов приложения. Вызов chain.doFilter передаёт запрос дальше — следующему фильтру или сервлету. Код до этой строки выполняется перед обработкой, код после — когда ответ уже готов.
Сделайте несколько запросов, и в терминале liberty:dev (и в console.log сервера) появятся строки:
GET /blog/ -> 200 (27 ms)
GET /blog/search -> 200 (4 ms)
GET /blog/search -> 400 (4 ms)
POST /blog/subscribe -> 302 (5 ms)
getRequestURI() не включает строку запроса, поэтому ?q=java в логе не видно.
Фильтр, который меняет ответ
Второй фильтр добавляет заголовок к каждому ответу:
// src/main/java/com/example/blog/PoweredByFilter.java
package com.example.blog;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter("/*")
public class PoweredByFilter extends HttpFilter {
@Override
protected void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
response.setHeader("X-Powered-By", "Blog on Jakarta EE");
chain.doFilter(request, response);
}
}Заголовок ставится до chain.doFilter: когда сервлет начнёт писать тело, менять заголовки будет поздно.
curl -i http://localhost:8080/blog/helloHTTP/1.1 200 OK
X-Powered-By: Blog on Jakarta EE
Content-Type: text/plain; charset=UTF-8
Что обычно делают фильтры
| Задача | Пример |
|---|---|
| Логирование | Метод, адрес, время ответа |
| Заголовки | CORS, кеширование, безопасность |
| Проверка доступа | Нет сессии — перенаправить на вход |
| Кодировка | Установить UTF-8 для всех запросов |
Шаблоны адресов те же, что у сервлетов: /*, /admin/*, *.html. Порядок нескольких фильтров с аннотациями не гарантирован. Если он важен, фильтры перечисляют в файле src/main/webapp/WEB-INF/web.xml.
Частая ошибка
Забыть вызвать chain.doFilter(request, response). Тогда запрос до сервлета не доходит, а клиент получает пустой ответ с кодом 200:
HTTP/1.1 200 OK
X-Powered-By: Blog on Jakarta EE
Content-Length: 0
Не вызывать chain.doFilter можно только намеренно — например, когда фильтр сам отвечает ошибкой 403.