Introduzione all'intercettazione del pattern di filtro in Java

1. Panoramica

In questo tutorial, introdurremo il pattern Core J2EE a livello di presentazione di Intercepting Filter Pattern.

Questo è il secondo tutorial della nostra serie Pattern e un seguito alla guida Pattern del controller frontale che può essere trovata qui.

I filtri di intercettazione sono filtri che attivano azioni prima o dopo che una richiesta in entrata viene elaborata da un gestore.

I filtri di intercettazione rappresentano componenti centralizzati in un'applicazione Web, comuni a tutte le richieste ed estensibili senza influire sui gestori esistenti.

2. Casi d'uso

Estendiamo l'esempio della guida precedente e implementiamo un meccanismo di autenticazione, la registrazione delle richieste e un contatore dei visitatori . Inoltre, vogliamo la capacità di fornire le nostre pagine in varie codifiche differenti .

Tutti questi sono casi d'uso per l'intercettazione dei filtri perché sono comuni a tutte le richieste e dovrebbero essere indipendenti dai gestori.

3. Strategie di filtro

Introduciamo diverse strategie di filtro e casi d'uso esemplari. Per eseguire il codice con il contenitore Jetty Servlet, eseguire semplicemente:

$> mvn install jetty:run

3.1. Strategia di filtro personalizzata

La strategia di filtro personalizzato viene utilizzata in ogni caso d'uso che richiede un'elaborazione ordinata delle richieste, nel senso che un filtro si basa sui risultati di un filtro precedente in una catena di esecuzione .

Queste catene verranno create implementando l' interfaccia FilterChain e registrando con essa varie classi Filter .

Quando si utilizzano più catene di filtri con problemi diversi, è possibile unirli insieme in un gestore di filtri:

Nel nostro esempio, il contatore dei visitatori funziona contando i nomi utente univoci degli utenti che hanno effettuato l'accesso, il che significa che si basa sul risultato del filtro di autenticazione, pertanto entrambi i filtri devono essere concatenati.

Implementiamo questa catena di filtri.

Innanzitutto, creeremo un filtro di autenticazione che controlla se la sessione esiste per un attributo "nome utente" impostato e in caso contrario emetteremo una procedura di accesso:

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

Ora creiamo il contatore dei visitatori. Questo filtro mantiene un HashSet di nomi utente univoci e aggiunge un attributo "counter" alla richiesta:

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

Successivamente, implementeremo un FilterChain che itera i filtri registrati ed esegue il metodo 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); } } }

Per collegare i nostri componenti insieme, creiamo un semplice gestore statico che è responsabile dell'istanza delle catene di filtri, della registrazione dei suoi filtri e dell'avvio:

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

Come ultimo passaggio dovremo chiamare il nostro FilterManager come parte comune della sequenza di elaborazione delle richieste dall'interno del nostro FrontCommand :

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

3.2. Strategia di filtro di base

In questa sezione presenteremo la Base Filter Strategy, con la quale viene utilizzata una superclasse comune per tutti i filtri implementati.

Questa strategia funziona bene insieme alla strategia personalizzata della sezione precedente o con la strategia di filtro standard che introdurremo nella sezione successiva.

La classe base astratta può essere utilizzata per applicare un comportamento personalizzato che appartiene a una catena di filtri. Lo useremo nel nostro esempio per ridurre il codice boilerplate relativo alla configurazione del filtro e alla registrazione del debug:

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

Estendiamo questa classe base per creare un filtro di registrazione delle richieste, che verrà integrato nella sezione successiva:

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. Strategia di filtro standard

Un modo più flessibile per applicare i filtri è implementare la strategia di filtraggio standard . Questo può essere fatto dichiarando i filtri in un descrittore di distribuzione o, a partire dalla specifica Servlet 3.0, tramite annotazione.

La strategia di filtraggio standardconsente di inserire nuovi filtri in una catena predefinita senza avere un gestore di filtri definito esplicitamente:

Notare che l'ordine in cui vengono applicati i filtri non può essere specificato tramite annotazione. Se è necessaria un'esecuzione ordinata, è necessario attenersi a un descrittore di distribuzione o implementare una strategia di filtro personalizzata.

Implementiamo un filtro di codifica basato sull'annotazione che utilizza anche la strategia di filtro di base:

@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 uno scenario Servlet con un descrittore di distribuzione, il nostro web.xml conterrebbe queste dichiarazioni extra:

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

Prendiamo il nostro filtro di registrazione e annotiamolo anche noi, in modo da essere utilizzati dal Servlet:

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

3.4. Strategia di filtro modello

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.

Come abbiamo visto finora, il Pattern del filtro di intercettazione può essere implementato utilizzando diverse strategie. In un "mondo reale", questi diversi approcci possono essere combinati.

Come al solito, troverai i sorgenti su GitHub .