Einführung in das Abfangen von Filtermustern in Java

1. Übersicht

In diesem Tutorial werden wir das Core J2EE-Muster der Präsentationsschicht Intercepting Filter Pattern vorstellen .

Dies ist das zweite Tutorial in unserer Pattern-Serie und eine Fortsetzung des Front Controller Pattern- Handbuchs, das Sie hier finden.

Intercepting-Filter sind Filter, die Aktionen auslösen, bevor oder nachdem eine eingehende Anforderung von einem Handler verarbeitet wurde.

Das Abfangen von Filtern stellt zentralisierte Komponenten in einer Webanwendung dar, die allen Anforderungen gemeinsam sind und erweiterbar sind, ohne dass vorhandene Handler beeinträchtigt werden.

2. Anwendungsfälle

Erweitern wir das Beispiel aus dem vorherigen Handbuch und implementieren einen Authentifizierungsmechanismus, eine Anforderungsprotokollierung und einen Besucherzähler . Darüber hinaus möchten wir die Möglichkeit haben, unsere Seiten in verschiedenen Codierungen bereitzustellen .

All dies sind Anwendungsfälle zum Abfangen von Filtern, da sie allen Anforderungen gemeinsam sind und von den Handlern unabhängig sein sollten.

3. Filterstrategien

Lassen Sie uns verschiedene Filterstrategien und beispielhafte Anwendungsfälle vorstellen. Führen Sie einfach Folgendes aus, um den Code mit dem Jetty Servlet-Container auszuführen:

$> mvn install jetty:run

3.1. Benutzerdefinierte Filterstrategie

Die benutzerdefinierte Filterstrategie wird in jedem Anwendungsfall verwendet, der eine geordnete Verarbeitung von Anforderungen erfordert. Die Bedeutung eines Filters basiert auf den Ergebnissen eines vorherigen Filters in einer Ausführungskette .

Diese Ketten werden durch die Umsetzung des erstellt werden filter Schnittstelle und Registrierung verschiedene Filterklassen mit.

Wenn Sie mehrere Filterketten mit unterschiedlichen Bedenken verwenden, können Sie sie in einem Filtermanager zusammenfügen:

In unserem Beispiel zählt der Besucherzähler eindeutige Benutzernamen von angemeldeten Benutzern. Dies bedeutet, dass er auf dem Ergebnis des Authentifizierungsfilters basiert. Daher müssen beide Filter verkettet werden.

Lassen Sie uns diese Filterkette implementieren.

Zuerst erstellen wir einen Authentifizierungsfilter, der prüft, ob die Sitzung für ein festgelegtes Attribut "Benutzername" vorhanden ist, und geben eine Anmeldeprozedur aus, wenn nicht:

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); } } ... }

Jetzt erstellen wir den Besucherzähler. Dieser Filter verwaltet ein HashSet mit eindeutigen Benutzernamen und fügt der Anforderung ein ' counter' -Attribut hinzu:

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); } ... }

Als Nächstes implementieren wir eine FilterChain , die registrierte Filter iteriert und die doFilter- Methode ausführt :

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); } } }

Um unsere Komponenten miteinander zu verbinden, erstellen wir einen einfachen statischen Manager, der für die Instanziierung von Filterketten, die Registrierung seiner Filter und deren Initiierung verantwortlich ist:

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); } }

Als letzten Schritt müssen wir unseren FilterManager als gemeinsamen Teil der Anforderungsverarbeitungssequenz in unserem FrontCommand aufrufen :

public abstract class FrontCommand { ... public void process() { FilterManager.process(request, response); } ... }

3.2. Basisfilter-Strategie

In diesem Abschnitt stellen wir die Basisfilterstrategie vor, mit der für alle implementierten Filter eine gemeinsame Oberklasse verwendet wird.

Diese Strategie passt gut zur benutzerdefinierten Strategie aus dem vorherigen Abschnitt oder zur Standardfilterstrategie , die wir im nächsten Abschnitt vorstellen werden.

Die abstrakte Basisklasse kann verwendet werden, um benutzerdefiniertes Verhalten anzuwenden, das zu einer Filterkette gehört. In unserem Beispiel wird es verwendet, um den Boilerplate-Code im Zusammenhang mit der Filterkonfiguration und der Debug-Protokollierung zu reduzieren:

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()); } }

Erweitern wir diese Basisklasse, um einen Anforderungsprotokollierungsfilter zu erstellen, der in den nächsten Abschnitt integriert wird:

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

Eine flexiblere Methode zum Anwenden von Filtern ist die Implementierung der Standardfilterstrategie . Dies kann durch Deklarieren von Filtern in einem Bereitstellungsdeskriptor oder, seit Servlet-Spezifikation 3.0, durch Annotation erfolgen.

Die Standardfilterstrategieallows to plug-in new filters into a default chain without having an explicitly defined filter manager:

Note that the order, in which the filters get applied, cannot be specified via annotation. If you need an ordered execution, you have to stick with a deployment descriptor or implement a custom filter strategy.

Let's implement an annotation driven encoding filter that also uses the base filter strategy:

@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); } }

In a Servlet scenario with having a deployment descriptor, our web.xml would contain these extra declarations:

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

Let's pick-up our logging filter and annotate it too, in order to get used by the Servlet:

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

3.4. Template Filter Strategy

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.

Wie wir bisher gesehen haben, kann das Intercepting Filter Pattern mit verschiedenen Strategien implementiert werden. In einer "realen" Anwendung können diese verschiedenen Ansätze kombiniert werden.

Wie immer finden Sie die Quellen auf GitHub .