Introdução ao padrão de filtro interceptador em Java

Introdução ao padrão de filtro interceptador em Java

1. Visão geral

Neste tutorial, vamos apresentar o padrão Core J2EE da camada de apresentaçãoIntercepting Filter Pattern.

Este é o segundo tutorial em nossoPattern Series e uma continuação do guiaFront Controller Pattern que pode ser encontradohere.

Intercepting Filters são filtros que acionam ações antes ou depois de uma solicitação de entrada ser processada por um manipulador.

A interceptação de filtros representa componentes centralizados em um aplicativo da Web, comuns a todas as solicitações e extensíveis sem afetar os manipuladores existentes.

2. Casos de Uso

Vamos estender oexample do guia anterior e implementaran authentication mechanism, request logging, and a visitor counter. Além disso, queremos a capacidade de entregar nossas páginasin various differentencoding.

Todos esses são casos de uso para interceptar filtros, porque são comuns a todas as solicitações e devem ser independentes dos manipuladores.

3. Estratégias de filtro

Vamos apresentar diferentes estratégias de filtro e casos de uso exemplares. Para executar o código com o contêiner Jetty Servlet, basta executar:

$> mvn install jetty:run

3.1. Estratégia de filtro personalizado

A estratégia de filtro customizado é usada em todos os casos de uso que requerem um processamento ordenado de solicitações, no significado deone filter is based on the results of a previous filter in an execution chain.

Essas cadeias serão criadas implementando a interfaceFilterChain e registrando várias classesFilter com ela.

Ao usar várias cadeias de filtros com preocupações diferentes, você pode uni-las em um gerenciador de filtros:

image

 

Em nosso exemplo, o contador de visitantes está funcionando contando nomes de usuário exclusivos de usuários conectados, o que significa que é baseado no resultado do filtro de autenticação, portanto, os dois filtros devem ser encadeados.

Vamos implementar essa cadeia de filtros.

Primeiro, criaremos um filtro de autenticação que verifica se a sessão existe para um atributo 'nome de usuário' definido e emitirá um procedimento de login, caso contrário:

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

    ...
}

Agora vamos criar o contador de visitantes. Este filtro mantém umHashSet de nomes de usuário exclusivos e adiciona um atributo 'contador' à solicitação:

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

    ...
}

A seguir, implementaremos umFilterChain que itera filtros registrados e executa o métododoFilter:

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

Para conectar nossos componentes, vamos criar um gerenciador estático simples que é responsável por instanciar cadeias de filtros, registrar seus filtros e iniciá-los:

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

Como última etapa, teremos que chamar nossoFilterManager como parte comum da sequência de processamento da solicitação de dentro de nossoFrontCommand:

public abstract class FrontCommand {
    ...

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

    ...
}

3.2. Estratégia de Filtro Básico

Nesta seção, apresentaremos osBase Filter Strategy, com os quais uma superclasse comum é usada para todos os filtros implementados.

Essa estratégia funciona bem em conjunto com a estratégia personalizada da seção anterior ou com osStandard Filter Strategy que apresentaremos na próxima seção.

A classe base abstrata pode ser usada para aplicar um comportamento personalizado que pertence a uma cadeia de filtros. Vamos usá-lo em nosso exemplo para reduzir o código clichê relacionado à configuração do filtro e registro de depuração:

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

Vamos estender essa classe base para criar um filtro de registro de solicitação, que será integrado na próxima seção:

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. Estratégia de filtro padrão

Uma maneira mais flexível de aplicar filtros é implementarStandard Filter Strategy. Isso pode ser feito declarando filtros em um descritor de implementação ou, desde a especificação Servlet 3.0, por anotação.

A estratégia de filtro padrão __ permite conectar novos filtros a uma cadeia padrão sem ter um gerenciador de filtros definido explicitamente:

image

 

Observe que a ordem na qual os filtros são aplicados não pode ser especificada por meio de anotação. Se você precisar de uma execução ordenada, precisará seguir um descritor de implantação ou implementar uma estratégia de filtro personalizada.

Vamos implementar um filtro de codificação orientado por anotações que também usa a estratégia de filtro 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);
    }
}

Em um cenário de Servlet com um descritor de implantação, nossoweb.xml conteria estas declarações extras:


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


    encoding-filter
    intercepting-filter

Vamos pegar nosso filtro de registro e anotá-lo também, a fim de sermos usados ​​pelo Servlet:

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

3.4. Estratégia de filtro de modelo

OTemplate Filter Strategy é praticamente o mesmo que a estratégia de filtro de base, exceto que usa métodos de modelo declarados na classe de base que devem ser substituídos nas implementações:

image

 

Vamos criar uma classe de filtro base com dois métodos de filtro abstratos que são chamados antes e depois do processamento adicional.

Uma vez que essa estratégia é menos comum e não a usamos em nosso exemplo, uma implementação concreta e um caso de uso depende de sua imaginação:

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. Conclusão

O padrão de filtro interceptador captura preocupações transversais que podem evoluir independentemente da lógica de negócios. Da perspectiva das operações de negócios, os filtros são executados como uma cadeia de ações anteriores ou posteriores.

Como vimos até agora, oIntercepting Filter Pattern pode ser implementado usando estratégias diferentes. Em aplicações no mundo real, essas diferentes abordagens podem ser combinadas.

Como de costume, você encontrará as fonteson GitHub.