Einführung in das Abfangen von Filtermustern in Java

Einführung in das Abfangen von Filtermustern in Java

1. Überblick

In diesem Tutorial werden wir das Core-J2EE-Muster der Präsentationsebene vonIntercepting Filter Patternvorstellen.

Dies ist das zweite Tutorial in unserenPattern Series und eine Fortsetzung desFront Controller Pattern-Leitfadens, derhere. enthält

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

Das Abfangen von Filtern stellt zentrale Komponenten in einer Webanwendung dar, die für alle Anforderungen gleich sind und erweiterbar sind, ohne die vorhandenen Handler zu beeinträchtigen.

2. Anwendungsfälle

Erweitern wir dieexample aus dem vorherigen Handbuch und implementierenan authentication mechanism, request logging, and a visitor counter. Darüber hinaus möchten wir die Möglichkeit haben, unsere Seitenin various differentencoding zu liefern.

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. Um den Code mit dem Jetty Servlet-Container auszuführen, führen Sie einfach Folgendes aus:

$> mvn install jetty:run

3.1. Benutzerdefinierte Filterstrategie

Die benutzerdefinierte Filterstrategie wird in jedem Anwendungsfall verwendet, der eine geordnete Verarbeitung von Anforderungen im Sinne vonone filter is based on the results of a previous filter in an execution chain erfordert.

Diese Ketten werden erstellt, indem dieFilterChain-Schnittstelle implementiert und verschiedeneFilter-Klassen damit registriert werden.

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

image

 

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.

Implementieren wir diese Filterkette.

Zunächst 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 verwaltetHashSet eindeutiger 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 wirFilterChain, das registrierte Filter iteriert und die MethodedoFilterausfü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 ihrer 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 unsereFilterManager als gemeinsamen Teil der Anforderungsverarbeitungssequenz innerhalb unsererFrontCommand aufrufen:

public abstract class FrontCommand {
    ...

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

    ...
}

3.2. Basisfilter-Strategie

In diesem Abschnitt werden dieBase Filter Strategy, vorgestellt, mit denen eine gemeinsame Oberklasse für alle implementierten Filter verwendet wird.

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

Mit der abstrakten Basisklasse kann ein benutzerdefiniertes Verhalten angewendet werden, 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 besteht darin,Standard Filter Strategy zu implementieren. Dies kann durch Deklaration von Filtern in einem Deployment-Deskriptor oder seit Servlet-Spezifikation 3.0 durch Annotation erfolgen.

Die Standardfilterstrategie __ ermöglicht das Einfügen neuer Filter in eine Standardkette ohne einen explizit definierten Filtermanager:

image

 

Beachten Sie, dass die Reihenfolge, in der die Filter angewendet werden, nicht über Anmerkungen angegeben werden kann. Wenn Sie eine bestellte Ausführung benötigen, müssen Sie sich an einen Implementierungsdeskriptor halten oder eine benutzerdefinierte Filterstrategie implementieren.

Implementieren wir einen annotationsgesteuerten Codierungsfilter, der auch die Basisfilterstrategie verwendet:

@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 einem Servlet-Szenario mit einem Bereitstellungsdeskriptor würden unsereweb.xml diese zusätzlichen Deklarationen enthalten:


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


    encoding-filter
    intercepting-filter

Nehmen wir unseren Protokollierungsfilter und kommentieren ihn ebenfalls, um vom Servlet verwendet zu werden:

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

3.4. Vorlagenfilterstrategie

Template Filter Strategy entspricht weitgehend der Basisfilterstrategie, verwendet jedoch Vorlagenmethoden, die in der Basisklasse deklariert sind und in Implementierungen überschrieben werden müssen:

image

 

Erstellen wir eine Basisfilterklasse mit zwei abstrakten Filtermethoden, die vor und nach der weiteren Verarbeitung aufgerufen werden.

Da diese Strategie weniger verbreitet ist und wir sie in unserem Beispiel nicht verwenden, liegt eine konkrete Implementierung und ein konkreter Anwendungsfall in Ihrer Vorstellungskraft:

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

Das Intercepting Filter Pattern erfasst Querschnittsthemen, die sich unabhängig von der Geschäftslogik entwickeln können. Aus Sicht des Geschäftsbetriebs werden Filter als Kette von Vor- oder Nachaktionen ausgeführt.

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

Wie üblich finden Sie die Quellenon GitHub.