Введение в перехватывающий шаблон фильтра в Java

Введение в перехват шаблона фильтра в Java

1. обзор

В этом руководстве мы собираемся представить шаблон Core J2EE уровня представленияIntercepting Filter Pattern.

Это второй урок в нашемPattern Series и продолжение руководстваFront Controller Pattern, которое можно найтиhere.

Intercepting Filters - это фильтры, запускающие действия до или после обработки входящего запроса обработчиком.

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

2. Случаи применения

Давайте расширимexample из предыдущего руководства и реализуемan authentication mechanism, request logging, and a visitor counter. Кроме того, нам нужна возможность доставлять наши страницыin various differentencoding.

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

3. Стратегии фильтрации

Позвольте нам представить различные стратегии фильтрации и примерные варианты использования. Чтобы запустить код с контейнером Jetty Servlet, просто выполните:

$> mvn install jetty:run

3.1. Стратегия пользовательского фильтра

Стратегия настраиваемой фильтрации используется во всех случаях, когда требуется упорядоченная обработка запросов в смыслеone filter is based on the results of a previous filter in an execution chain.

Эти цепочки будут созданы путем реализации интерфейсаFilterChain и регистрации в нем различных классовFilter.

При использовании нескольких цепочек фильтров с различными проблемами вы можете объединить их вместе в диспетчере фильтров:

image

 

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

Давайте реализуем эту цепочку фильтров.

Сначала мы создадим фильтр аутентификации, который проверяет, существует ли сеанс для установленного атрибута «имя пользователя», и выполним процедуру входа в систему, если нет:

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 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 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. Базовая стратегия фильтрации

В этом разделе мы представимBase Filter Strategy,, с которыми общий суперкласс используется для всех реализованных фильтров.

Эта стратегия прекрасно сочетается с пользовательской стратегией из предыдущего раздела или сStandard Filter Strategy, которые мы представим в следующем разделе.

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

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. Стандартная стратегия фильтрации

Более гибкий способ применения фильтров - реализоватьStandard Filter Strategy. Это можно сделать, объявив фильтры в дескрипторе развертывания или, начиная со спецификации 3.0 сервлета, аннотацией.

Стандартная стратегия фильтрации __ позволяет подключать новые фильтры в цепочку по умолчанию, не имея явно определенного диспетчера фильтров:

image

 

Обратите внимание, что порядок применения фильтров не может быть указан с помощью аннотации. Если вам нужно упорядоченное выполнение, вы должны придерживаться дескриптора развертывания или реализовать собственную стратегию фильтрации.

Давайте реализуем фильтр кодирования, управляемый аннотациями, который также использует базовую стратегию фильтрации:

@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 будет содержать эти дополнительные объявления:


    encoding-filter
    
      com.example.patterns.intercepting.filter.filters.EncodingFilter
    


    encoding-filter
    intercepting-filter

Давайте возьмем наш фильтр ведения журнала и аннотируем его, чтобы сервлет мог использовать его:

@WebFilter(servletNames = "intercepting-filter")
public class LoggingFilter extends BaseFilter {
    ...
}

3.4. Стратегия фильтрации шаблонов

Template Filter Strategy в значительной степени совпадает со стратегией базовой фильтрации, за исключением того, что в ней используются методы шаблона, объявленные в базовом классе, которые необходимо переопределить в реализациях:

image

 

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

Поскольку эта стратегия менее распространена, и мы не используем ее в нашем примере, конкретная реализация и вариант использования остаются в вашем воображении:

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. Заключение

Шаблон фильтра перехвата охватывает сквозные проблемы, которые могут развиваться независимо от бизнес-логики. С точки зрения бизнес-операций фильтры выполняются как цепочка действий до или после.

Как мы уже видели,Intercepting Filter Pattern можно реализовать с помощью разных стратегий. В приложениях «реального мира» эти разные подходы могут быть объединены.

Как обычно, вы найдете источникиon GitHub.