1. Обзор
В этом руководстве мы собираемся представить базовый шаблон J2EE уровня представления Intercepting Filter Pattern.
Это второй учебник в нашей серии паттернов
и продолжение руководства по паттернам Front Controller
, которое можно найти здесь .
Перехватывающие фильтры
— это фильтры, запускающие действия до или после обработки входящего запроса обработчиком.
Перехватывающие фильтры представляют собой централизованные компоненты веб-приложения, общие для всех запросов и расширяемые, не затрагивающие существующие обработчики.
2. Варианты использования
Давайте расширим пример из предыдущего руководства и реализуем механизм аутентификации, ведение журнала запросов и счетчик посетителей . Кроме того, нам нужна возможность доставки наших страниц в различных кодировках .
Все это варианты использования для перехвата фильтров, поскольку они являются общими для всех запросов и не должны зависеть от обработчиков.
3. Стратегии фильтрации
Давайте представим различные стратегии фильтрации и примеры использования. Чтобы запустить код с контейнером Jetty Servlet, просто выполните:
$> mvn install jetty:run
3.1. Пользовательская стратегия фильтрации
Стратегия пользовательского фильтра используется в каждом случае использования, требующем упорядоченной обработки запросов, в том смысле, что один фильтр основан на результатах предыдущего фильтра в цепочке выполнения .
Эти цепочки будут созданы путем реализации интерфейса FilterChain
и регистрации в нем различных классов Filter .
При использовании нескольких цепочек фильтров с разными задачами вы можете объединить их вместе в диспетчере фильтров:
В нашем примере счетчик посетителей работает, подсчитывая уникальные имена пользователей от вошедших в систему пользователей, что означает, что он основан на результате фильтра аутентификации, поэтому оба фильтра должны быть связаны.
Давайте реализуем эту цепочку фильтров.
Во-первых, мы создадим фильтр аутентификации, который проверяет, существует ли сеанс для заданного атрибута «имя пользователя», и запускает процедуру входа, если нет:
public class AuthenticationFilter implements Filter {
...
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
HttpSession session = httpServletRequest.getSession(false);
if (session == null || session.getAttribute("username") == null) {
FrontCommand command = new LoginCommand();
command.init(httpServletRequest, httpServletResponse);
command.process();
} else {
chain.doFilter(request, response);
}
}
...
}
Теперь давайте создадим счетчик посетителей. Этот фильтр поддерживает HashSet
уникальных имен пользователей и добавляет к запросу атрибут counter:
public class VisitorCounterFilter implements Filter {
private static Set<String> users = new HashSet<>();
...
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) {
HttpSession session = ((HttpServletRequest) request).getSession(false);
Optional.ofNullable(session.getAttribute("username"))
.map(Object::toString)
.ifPresent(users::add);
request.setAttribute("counter", users.size());
chain.doFilter(request, response);
}
...
}
Далее мы реализуем FilterChain
, который перебирает зарегистрированные фильтры и выполняет метод doFilter
:
public class FilterChainImpl implements FilterChain {
private Iterator<Filter> filters;
public FilterChainImpl(Filter... filters) {
this.filters = Arrays.asList(filters).iterator();
}
@Override
public void doFilter(ServletRequest request, ServletResponse response) {
if (filters.hasNext()) {
Filter filter = filters.next();
filter.doFilter(request, response, this);
}
}
}
Чтобы связать наши компоненты вместе, давайте создадим простой статический менеджер, который отвечает за создание экземпляров цепочек фильтров, регистрацию его фильтров и инициирует его:
public class FilterManager {
public static void process(HttpServletRequest request,
HttpServletResponse response, OnIntercept callback) {
FilterChain filterChain = new FilterChainImpl(
new AuthenticationFilter(callback), new VisitorCounterFilter());
filterChain.doFilter(request, response);
}
}
В качестве последнего шага нам нужно будет вызвать наш FilterManager
как общую часть последовательности обработки запросов из нашей FrontCommand
:
public abstract class FrontCommand {
...
public void process() {
FilterManager.process(request, response);
}
...
}
3.2. Базовая стратегия фильтрации
В этом разделе мы представим стратегию базовой фильтрации,
в которой для всех реализованных фильтров используется общий суперкласс.
Эта стратегия хорошо сочетается с пользовательской стратегией из предыдущего раздела или со стандартной стратегией фильтрации
, которую мы представим в следующем разделе.
Абстрактный базовый класс можно использовать для применения пользовательского поведения, принадлежащего цепочке фильтров. Мы будем использовать его в нашем примере, чтобы сократить шаблонный код, связанный с настройкой фильтра и ведением журнала отладки:
public abstract class BaseFilter implements Filter {
private Logger log = LoggerFactory.getLogger(BaseFilter.class);
protected FilterConfig filterConfig;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("Initialize filter: {}", getClass().getSimpleName());
this.filterConfig = filterConfig;
}
@Override
public void destroy() {
log.info("Destroy filter: {}", getClass().getSimpleName());
}
}
Давайте расширим этот базовый класс, чтобы создать фильтр регистрации запросов, который будет интегрирован в следующий раздел:
public class LoggingFilter extends BaseFilter {
private static final Logger log = LoggerFactory.getLogger(LoggingFilter.class);
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) {
chain.doFilter(request, response);
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String username = Optional
.ofNullable(httpServletRequest.getAttribute("username"))
.map(Object::toString)
.orElse("guest");
log.info(
"Request from '{}@{}': {}?{}",
username,
request.getRemoteAddr(),
httpServletRequest.getRequestURI(),
request.getParameterMap());
}
}
3.3. Стандартная стратегия фильтрации
Более гибким способом применения фильтров является реализация стандартной стратегии фильтрации
. Это можно сделать, объявив фильтры в дескрипторе развертывания или, начиная со спецификации сервлета 3.0, с помощью аннотации.
Стандартная стратегия фильтрации `` позволяет подключать новые фильтры к цепочке по умолчанию, не имея явно определенного менеджера фильтров:
Обратите внимание, что порядок применения фильтров нельзя указать с помощью аннотации. Если вам нужно упорядоченное выполнение, вы должны придерживаться дескриптора развертывания или реализовать собственную стратегию фильтрации.
Давайте реализуем фильтр кодирования, управляемый аннотациями, который также использует стратегию базового фильтра:
@WebFilter(servletNames = {"intercepting-filter"},
initParams = {@WebInitParam(name = "encoding", value = "UTF-8")})
public class EncodingFilter extends BaseFilter {
private String encoding;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
super.init(filterConfig);
this.encoding = filterConfig.getInitParameter("encoding");
}
@Override
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain) {
String encoding = Optional
.ofNullable(request.getParameter("encoding"))
.orElse(this.encoding);
response.setCharacterEncoding(encoding);
chain.doFilter(request, response);
}
}
В сценарии сервлета с дескриптором развертывания наш файл web.xml
будет содержать следующие дополнительные объявления:
<filter>
<filter-name>encoding-filter</filter-name>
<filter-class>
com.foreach.patterns.intercepting.filter.filters.EncodingFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>encoding-filter</filter-name>
<servlet-name>intercepting-filter</servlet-name>
</filter-mapping>
Давайте возьмем наш фильтр ведения журнала и аннотируем его, чтобы сервлет мог его использовать:
@WebFilter(servletNames = "intercepting-filter")
public class LoggingFilter extends BaseFilter {
...
}
3.4. Стратегия фильтра шаблонов
Стратегия шаблонного фильтра
во многом аналогична базовой стратегии фильтра, за исключением того, что она использует методы шаблона, объявленные в базовом классе, которые должны быть переопределены в реализациях:
Давайте создадим базовый класс фильтра с двумя абстрактными методами фильтра, которые вызываются до и после дальнейшей обработки.
Поскольку эта стратегия менее распространена и мы не используем ее в нашем примере, конкретная реализация и вариант использования зависят от вашего воображения:
public abstract class TemplateFilter extends BaseFilter {
protected abstract void preFilter(HttpServletRequest request,
HttpServletResponse response);
protected abstract void postFilter(HttpServletRequest request,
HttpServletResponse response);
@Override
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
preFilter(httpServletRequest, httpServletResponse);
chain.doFilter(request, response);
postFilter(httpServletRequest, httpServletResponse);
}
}
4. Вывод
Шаблон перехватывающего фильтра фиксирует сквозные проблемы, которые могут развиваться независимо от бизнес-логики. С точки зрения бизнес-операций фильтры выполняются как цепочка действий до или после.
Как мы уже видели, шаблон перехватывающего фильтра
может быть реализован с использованием различных стратегий. В приложениях «реального мира» эти разные подходы можно комбинировать.
Как обычно, вы найдете исходники на GitHub
.