Вступ до перехоплення шаблону фільтра на Java

1. Огляд

У цьому підручнику ми збираємось представити шаблон перехоплення шаблону фільтра Core J2EE рівня презентації.

Це другий підручник у нашій серії Pattern і продовження керівництва по шаблону переднього контролера, яке можна знайти тут.

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

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

2. Використовуйте кейси

Давайте розширимо приклад з попереднього керівництва та реалізуємо механізм автентифікації, реєстрацію запитів та лічильник відвідувачів . Крім того, ми хочемо мати можливість розміщувати наші сторінки в різному кодуванні .

Все це випадки використання для перехоплення фільтрів, оскільки вони є загальними для всіх запитів і повинні бути незалежними від обробників.

3. Стратегії фільтрування

Давайте представимо різні стратегії фільтрування та зразкові випадки використання. Щоб запустити код за допомогою контейнера Jetty Servlet, просто виконайте:

$> mvn install jetty:run

3.1. Стратегія користувацького фільтра

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

Ці ланцюжки будуть створені шляхом реалізації інтерфейсу FilterChain та реєстрації в ньому різних класів фільтрів .

Використовуючи кілька ланцюжків фільтрів з різними проблемами, ви можете об'єднати їх у менеджері фільтрів:

У нашому прикладі лічильник відвідувачів працює, підраховуючи унікальні імена користувачів від зареєстрованих користувачів, а це означає, що він базується на результатах фільтра автентифікації, тому обидва фільтри повинні бути прив’язані.

Давайте реалізуємо цей ланцюжок фільтрів.

Спочатку ми створимо фільтр автентифікації, який перевіряє, чи існує сеанс для набору атрибутів 'username', і видає процедуру входу, якщо ні:

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. Стратегія базового фільтра

У цьому розділі ми представимо базову стратегію фільтрування, в якій загальний суперклас використовується для всіх реалізованих фільтрів.

Ця стратегія чудово поєднується зі спеціальною стратегією з попереднього розділу або зі стандартною стратегією фільтрування, яку ми введемо в наступному розділі.

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

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 міститиме такі додаткові декларації:

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

Давайте візьмемо наш фільтр реєстрації та також анотуємо його, щоб використовувати сервлет:

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

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

The Template Filter Strategy is pretty much the same as the base filter strategy, except that it uses template methods declared in the base class that must be overridden in implementations:

Let's create a base filter class with two abstract filter methods that get called before and after further processing.

Since this strategy is less common and we don't use it in our example, a concrete implementation and use case is up to your imagination:

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. Conclusion

The Intercepting Filter Pattern captures cross-cutting concerns that can evolve independently of the business logic. From the perspective of business operations, filters are executed as a chain of pre or post actions.

Як ми бачили до цього часу, шаблон перехоплення фільтра може бути реалізований за допомогою різних стратегій. У реальних програмах ці різні підходи можна поєднувати.

Як завжди, ви знайдете джерела на GitHub .