Introduction à l’interception des filtres en Java

Introduction à l'interception de modèle de filtre en Java

1. Vue d'ensemble

Dans ce didacticiel, nous allons présenter le modèle Core J2EE de niveau présentation deIntercepting Filter Pattern.

Ceci est le deuxième tutoriel de nosPattern Series et un suivi du guideFront Controller Pattern que vous pouvez trouverhere.

Intercepting Filters sont des filtres qui déclenchent des actions avant ou après le traitement d'une demande entrante par un gestionnaire.

Les filtres d'interception représentent des composants centralisés dans une application Web, communs à toutes les demandes et extensibles sans affecter les gestionnaires existants.

2. Cas d'utilisation

Étendons lesexample du guide précédent et implémentonsan authentication mechanism, request logging, and a visitor counter. De plus, nous souhaitons pouvoir livrer nos pagesin various differentencoding.

Tous ces cas sont des cas d'utilisation d'interception de filtres car ils sont communs à toutes les demandes et doivent être indépendants des gestionnaires.

3. Stratégies de filtrage

Laissez-nous vous présenter différentes stratégies de filtrage et cas d'utilisation exemplaires. Pour exécuter le code avec le conteneur Jetty Servlet, exécutez simplement:

$> mvn install jetty:run

3.1. Stratégie de filtrage personnalisée

La stratégie de filtre personnalisé est utilisée dans chaque cas d'utilisation qui nécessite un traitement ordonné des requêtes, au sens deone filter is based on the results of a previous filter in an execution chain.

Ces chaînes seront créées en implémentant l'interfaceFilterChain et en enregistrant diverses classesFilter avec elle.

Lorsque vous utilisez plusieurs chaînes de filtres avec des préoccupations différentes, vous pouvez les associer dans un gestionnaire de filtres:

image

 

Dans notre exemple, le compteur de visiteurs fonctionne en comptant les noms d'utilisateur uniques des utilisateurs connectés, ce qui signifie qu'il est basé sur le résultat du filtre d'authentification. Par conséquent, les deux filtres doivent être chaînés.

Implémentons cette chaîne de filtres.

Tout d'abord, nous allons créer un filtre d'authentification qui vérifie si la session existe pour un attribut "nom d'utilisateur" défini et émet une procédure de connexion dans le cas contraire:

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

    ...
}

Créons maintenant le compteur de visiteurs. Ce filtre gère unHashSet de noms d'utilisateur uniques et ajoute un attribut «compteur» à la requête:

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

    ...
}

Ensuite, nous allons implémenter unFilterChain qui itère les filtres enregistrés et exécute la méthodedoFilter:

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

Pour connecter nos composants ensemble, créons un gestionnaire statique simple qui est responsable de l'instanciation des chaînes de filtres, de l'enregistrement de ses filtres et de son lancement:

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

En guise de dernière étape, nous devrons appeler nosFilterManager en tant que partie commune de la séquence de traitement de la demande à partir de nosFrontCommand:

public abstract class FrontCommand {
    ...

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

    ...
}

3.2. Stratégie de filtrage de base

Dans cette section, nous présenterons lesBase Filter Strategy, avec lesquels une superclasse commune est utilisée pour tous les filtres implémentés.

Cette stratégie fonctionne bien avec la stratégie personnalisée de la section précédente ou avec lesStandard Filter Strategy que nous présenterons dans la section suivante.

La classe de base abstraite peut être utilisée pour appliquer un comportement personnalisé appartenant à une chaîne de filtres. Nous allons l'utiliser dans notre exemple pour réduire le code standard lié à la configuration du filtre et à la journalisation du débogage:

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

Étendons cette classe de base pour créer un filtre de journalisation des demandes, qui sera intégré dans la section suivante:

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. Stratégie de filtrage standard

Une manière plus flexible d'appliquer des filtres consiste à implémenter lesStandard Filter Strategy. Cela peut être fait en déclarant les filtres dans un descripteur de déploiement ou, depuis la spécification Servlet 3.0, par annotation.

La stratégie de filtrage standard __ permet d'insérer de nouveaux filtres dans une chaîne par défaut sans avoir de gestionnaire de filtre explicitement défini:

image

 

Notez que l'ordre dans lequel les filtres sont appliqués ne peut pas être spécifié via une annotation. Si vous avez besoin d'une exécution ordonnée, vous devez vous en tenir à un descripteur de déploiement ou mettre en œuvre une stratégie de filtrage personnalisée.

Implémentons un filtre de codage basé sur les annotations qui utilise également la stratégie de filtrage de 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);
    }
}

Dans un scénario Servlet avec un descripteur de déploiement, nosweb.xml contiendraient ces déclarations supplémentaires:


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


    encoding-filter
    intercepting-filter

Reprenons notre filtre de journalisation et annotons-le également, afin d'être utilisé par le servlet:

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

3.4. Stratégie de filtre de modèle

LeTemplate Filter Strategy est à peu près identique à la stratégie de filtre de base, sauf qu'il utilise des méthodes de modèle déclarées dans la classe de base qui doivent être remplacées dans les implémentations:

image

 

Créons une classe de filtre de base avec deux méthodes de filtrage abstraites qui sont appelées avant et après un traitement ultérieur.

Étant donné que cette stratégie est moins courante et que nous ne l’utilisons pas dans notre exemple, une mise en œuvre concrète et un cas d’utilisation sont à votre 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

Le modèle de filtre d'interception capture les problèmes transversaux qui peuvent évoluer indépendamment de la logique métier. Du point de vue des opérations commerciales, les filtres sont exécutés comme une chaîne d’actions avant ou après.

Comme nous l'avons vu jusqu'à présent, lesIntercepting Filter Pattern peuvent être implémentés en utilisant différentes stratégies. Dans un «monde réel», ces différentes approches peuvent être combinées.

Comme d'habitude, vous trouverez les sourceson GitHub.