Esse artigo faz parte da revista Java Magazine edição 20. Clique aqui para ler todos os artigos desta edição

jm20_capa.jpg

Clique aqui para ler esse artigo em PDF.imagem_pdf.jpg

Programando Servlets

Parte 2: Conceitos Avançados

No segundo e último artigo da série apresentaremos conceitos avançados da API de Servlets, como filtros, wrappers e listeners

Na primeira parte desta série, na Edição 18, apresentamos os conceitos básicos da API de Servlets, de tal forma que mesmo os leitores não familiarizados pudessem ter um primeiro contato com a tecnologia. Neste artigo apresentaremos conceitos mais avançados que, além de poderem ser usados para incrementar aplicações web, estão presentes em muitos frameworks de terceiros, como Struts, Cactus, WebWork e SiteMesh.

 

Nota: Os exemplos dessa parte foram desenvolvidos e testados usando o Tomcat 5 (para mais informações sobre como configurar esse servidor para testar os exemplos, consulte a primeira parte).

Logging

A tarefa de "logar" ações de uma aplicação é tão importante que existem vários frameworks e mesmo uma API oficial[1] para isso. Percebendo essa necessidade, a equipe responsável pela especificação de Servlets acrescentou um mecanismo (rudimentar) de logging na API: os métodos log(String msg), log(Exception e, String msg) e log(String msg, Throwable t) na classe ServletContext.

O mecanismo é bem primitivo quando comparado com outros frameworks como o Log4J ou o componente Logging do Jakarta Commons, principalmente por não oferecer níveis diferentes de logging, nem opções flexíveis de configuração. A configuração fica a cargo do servidor web – o Tomcat, por exemplo, usa um framework próprio, cuja configuração é feita no arquivo conf/server.xml. De qualquer forma, o uso dos métodos de logging em ServletContext já é um progresso em relação ao infame System.out.println().

Filtros

Na época em que eu escrevia esse artigo, o livro Design Patterns completava dez anos de existência. Escrito por quatro autores (a Gang of Four, ou GoF, entre eles Erich Gamma, que atualmente é um dos principais nomes no projeto Eclipse), esse texto tornou-se um marco na programação orientada a objetos, por introduzir o conceito de design patterns (padrões de projeto) ao desenvolvimento de sistemas. Desde então, mais livros surgiram, muitos descrevendo patterns específicos para um cenário ou tecnologia. Entre estes, podemos destacar o Core J2EE Patterns, escrito por consultores Java/J2EE da Sun (veja resenha nesta edição).

Um dos patterns J2EE descritos nesse livro é usado diretamente na API de Servlets. É o Intercepter Filter, um mecanismo que possibilita o pré e o pós-processamento de uma operação. Na API de Servlets, ele é representado pela interface javax.servlet.Filter e pode ser usado para fazer um pré-processamento da requisição antes que seja processada pelo Servlet e/ou um pós-processamento da resposta antes que seja enviada ao navegador web, como ilustra a Figura 1. Os filtros podem ser usados em várias situações, como para o controle de acesso às páginas, criptografia e/ou compactação das requisições, auditoria e logging dos acessos etc.

 

Nota: Filtros podem ser aplicados em qualquer tipo de requisição (como requisições de simples páginas HTML) e não apenas em requisições processadas por servlets.

.

Os filtros possuem ainda duas características importantes. A primeira é que eles são desacoplados dos servlets, ou seja, um filtro tem vida própria e só é associado para tratar uma requisição em tempo de execução, através de configurações no descritor web (web.xml), como veremos mais adiante. A outra característica, de certa forma conseqüência da primeira, é que uma requisição pode ser tratada por vários filtros em seqüência, através de uma cadeia de filtros (FilterChain), como mostra a Figura 2.

A interface javax.servlet.Filter possui apenas três métodos que, assim como em javax.servlet.Servlet, estão associados ao ciclo de vida do objeto. São eles:

·     init(FilterConfig config) – usado para inicializar o filtro, é chamado assim que a aplicação web é iniciada (note que os filtros são inicializados antes dos servlets em si). O parâmetro do tipo FilterConfig permite obter parâmetros de configuração do filtro definidos no descritor web.

·     destroy() – chamado quando a aplicação web é finalizada.

·     doFilter(ServletRequest request, ServletResponse response, FilterChain chain) – método principal da interface, pois é nele que o filtro realiza seu trabalho. Note que a assinatura é praticamente igual à do método service() de Servlet, exceto pela presença de um parâmetro adicional, do tipo FilterChain. É este que possibilita o desacoplamento do filtro com os servlets e o encadeamento de filtros. O filtro só precisa fazer a sua parte do pré-processamento da requisição e "passar a bola adiante" para o FilterChain fazer o resto do trabalho, chamando chain.doFilter(request, response). De modo similar, o filtro pode realizar o pós-processamento após o retorno desse método.

Exemplo 1: contando acessos

Como primeiro exemplo, vamos implementar um filtro que contabiliza o número de acessos a cada URL de uma aplicação web. Usaremos um Map para gravar os acessos e um servlet para mostrar os resultados (o Map será armazenado como atributo do ServletContext). Assim, precisaremos implementar os métodos init() para ler o parâmetro de inicialização com o nome do Map, e doFilter() para fazer o pré-processamento da requisição (isto é, gravar o acesso à URL no Map) e repassar o fluxo adiante, para o FilterChain. A Listagem 1 mostra o código desse filtro.

Assim como ocorre com os servlets, não basta apenas criar a classe do filtro, é preciso configurá-la no descritor web. A configuração dos filtros no descritor também é similar à configuração dos servlets – para cada filtro é preciso um elemento <filter> para definir o nome, a classe e os parâmetros de inicialização do filtro e um ou mais elementos <filter-mapping> para associá-lo a requisições. A Listagem 2 mostra os trechos do descritor com a configuração; também precisamos de um servlet para mostrar a tabela com os acessos. O código desse servlet está na Listagem 3.

 

Nota: O descritor web não oferece uma maneira explícita de configurar FilterChains. O que define a cadeia de filtros aplicados a uma requisição é a ordem que os elementos <filter-mapping> são definidos no descritor web.

Exemplo 2: registrando no log

Vamos agora incrementar o filtro para que ele registre em log no servidor o tempo de processamento de cada requisição. A mudança em si é simples: basta calcular o tempo gasto na chamada do método FilterChain.doFilter() e registrar o resultado. O importante neste exemplo, porém, é que agora o filtro também realiza o pós-processamento da requisição, como mostra o trecho modificado a seguir:

 

// Passa adiante, marcando horário de início

long inicio = System.currentTimeMillis();

chain.doFilter( request, response );

 

// Pós-processamento

long fim = System.currentTimeMillis();

long delta = fim - inicio;

contexto.log( "Tempo gasto na requisição de "

   + url + ": " + delta + " ms" );

 

Nota: Note que nesse exemplo o filtro contabiliza, além do tempo de processamento da requisição pelo servlet, o processamento realizado pelos demais filtros da cadeia (caso haja mais filtros configurados).

 

Exemplo 3: limitando o acesso

Como último exemplo de filtros, vamos alterar o filtro para que cada página só possa ser acessada um número limitado de vezes. Ou seja, agora o filtro não se limita a simplesmente fazer um pré ou pós-processamento da requisição, ele pode interromper a cadeia de filtros. Essa técnica poderia ser usada, por exemplo, na implementação de filtros que controlem o acesso de recursos da aplicação. A Listagem 4 mostra a nova versão do nosso filtro.

Para finalizar, note que os filtros foram introduzidos somente a partir da versão 2.3 da API de Servlets. Antes disso, para implementar filtros era necessário fazer malabarismos no código, como forçar todos os servlets da aplicação a estender um servlet comum, ou usar o pattern Decorator. A classe RequestProcessor do Struts é um exemplo típico dessa situação[2].

Parâmetros de inicialização da aplicação web

No exemplo anterior, tivemos que definir o nome do Map em dois lugares: nas configurações do filtro e do servlet. Não seria mais prático se houvesse um lugar onde esse parâmetro pudesse ser configurado apenas uma vez e acessado por ambos (o filtro e o servlet)? Pois este lugar existe e é justamente o ServletContext. Assim como o descritor web aceita os elementos <init-param> dentro dos elementos <servlet> e <filter>, ele também aceita elementos <context-param> dentro do elemento raiz <web-app>. Dessa forma, poderíamos reescrever o descritor como mostra a Listagem 5 e alterar os métodos init() do filtro e do servlet para simplesmente chamar:

  ...

Quer ler esse conteúdo completo? Tenha acesso completo