AOP na prática: implementando um aspecto de auditoria
No último artigo apresentei os conceitos básicos da Programação Orientada a Aspectos. Nesse artigo pretendo mostrar um exemplo prático de um problema muito comum em aplicações “enterprise”, a auditoria.
A auditoria é o processo de registrar todas as mudanças feitas pelos usuários para que se possa provar as ações tomadas por eles. Dessa forma é possível provar que o sistema não estava errado. Por exemplo, os dados sumiram porque alguém os excluiu.
Auditoria é uma responsabilidade “cross-cutting”, ou seja, se for programada de maneira tradicional estará espalhada por todo o sistema. Entretanto, é possível implementar tal funcionalidade utilizando técnicas de AOP, como veremos a seguir.
Dando continuidade ao artigo anterior, os exemplos estarão utilizando Spring e @AspectJ.
Exemplo Inicial
Começamos definindo uma interface geral para que nossos objetos de acesso ao banco de dados possam implementar, seguindo o padrão de projeto “Data Access Object”.

Utilizamos o recurso de tipos genéricos para que nossas implementações possam inserir qualquer tipo de objeto do domínio.
Para não complicar o exemplo a implementação da interface apenas imprimirá no console para sabermos que o método foi executado, porém imagine que poderíamos estar executando código JDBC ou Hibernate/JPA em seu lugar.

Agora implementaremos a primeira versão do nosso aspecto de auditoria, que simplesmente irá interceptar as execuções dos métodos inserir, atualizar e remover, imprimindo no console uma mensagem de aviso.

Note o uso do advice @AfterReturing para que o aspecto seja aplicado após uma execução com sucesso do método alvo. No pointcut interceptamos execuções de métodos de objetos que implementam a interface EntityDao, como a classe ConsoleClienteDao que criamos acima.
Para completar o exemplo mostramos o applicationContext.xml e o método Main para teste da aplicação.


A execução do método main irá imprimir o seguinte no console:
executou inserirCliente
depoisDeInserir
executou atualizarCliente
depoisDeAtualizar
executou removerCliente
depoisDeRemover
Agora, de posse de um exemplo muito simples, iremos melhora-lo para contemplar funcionalidades mais reais.
Auditando dados da entidade
Vamos criar uma superclasse abstrata para nossas entidades, dessa forma podemos utilizar o aspecto para obter informações de auditoria.

A classe Entity possui um id e um método a ser implementado pelas subclasses para obter uma String descritiva para auditoria. Em um sistema real poderíamos criar um POJO para substituir a String, de forma que ele poderia conter informações mais avançadas. Agora nossa classe Cliente passa a estender a classe Entity.
Para ter acesso aos dados da entidade que está sendo auditada podemos utilizar a expressão ‘args’ da sintaxe de pointcut do @AspectJ. Veja o exemplo abaixo.

Agora restringimos o método inserir a ter apenas um parâmetro, e do tipo Entity. Na expressão args(entity) definimos o nome do parâmetro, essa string deve coincidir com a variável declarada como parâmetro do método depoisDeInserir. Sendo assim, o objeto disponibilizado para o método é o mesmo passado como parâmetro do ConsoleClienteDao, e podemos utiliza-lo para auditoria.
Outra forma possível de auditar os dados da entidade seria utilizar a API de reflection para obter todas as suas propriedades, e então, montar uma descrição com a concatenação de seus valores.
Auditando mudanças nas atualizações
Em geral na auditoria, quando um objeto sofre um update, é desejável auditar as mudanças realizadas. Mas como podemos saber as mudanças feitas antes do update ocorrer? Podemos fazer um select antes e comparar os dados antes e depois da atualização.
Isso pode ser facilmente contemplado utilizando um advice @Around. Antes da execução do método atualizar fazemos um select e obtemos o objeto em seu estado atual, depois da atualização com sucesso, comparamos os dois objetos utilizando reflection (ou alguma interface), e então, auditamos o resultado.

Para manter os exemplos simples apenas comparamos os objetos com o método equals. Alem disso, nenhum tratamento foi feito para evitar NullPointerExceptions. Note que o método proceed executa o método alvo e em caso de exceções, será propagado para fora do aspecto, sem chegar a executar as ultimas linhas do método.
Filtrando quais classes participarão da auditoria
Imagine que para uma determinada aplicação é necessário fazer auditoria em apenas alguns DAO’s. Mas queremos que nosso aspecto continue consistente, ou seja, não sofra alterações constantemente. Para tal, uma idéia interessante é a utilização de anotações para marcar o pointcut do aspecto. Veja o exemplo abaixo.

Veja a expressão @target(exemplo.AptoParaAuditoria). Essa expressão significa que para o pointcut ser atingido a classe alvo tem que estar anotada com a anotação @AptoParaAuditoria. Dessa forma, a auditoria só ocorre quando desejado, nos DAO’s marcados com essa anotação. Também é possível criar filtros a nível de método utilizando a expressão @annotation(exemplo.AptoParaAuditoria).
Para melhorar o exemplo ainda é possível criar um atributo na anotação, uma enumeração, para informar em quais operações serão auditadas: inserir, atualizar, remover ou todas.
Obtendo informações do usuário corrente
Em um sistema de auditoria não basta saber o que foi feito e onde, também é necessário saber quem fez a alteração. Ou seja, é necessário obter dados do usuário logado. Em uma aplicação web isso se torna difícil, pois é impossível obter um objeto HttpRequest ou HttpSession de dentro do aspecto de auditoria.
Para essas situações existe uma técnica muito utilizada na AOP, associar recursos a Thread corrente. Assim é no Spring na área de controle de transações. Quando uma transação é aberta pelo aspecto de transação é associado a Thread corrente um java.sql.DataSource ou, no caso do Hibernate, uma org.hibernate.classic.Session. Dessa forma, ao longo da execução da Thread, de todos os lugares, é possível obter o mesmo recurso de conexão (na mesma transação), para executar o acesso ao banco de dados.
Essa solução pode ser aplicada nesse caso. Podemos implementar um filtro http (javax.servlet.Filer) que associa o request a Thread corrente. Dessa forma, de dentro do aspecto, podemos acessar o HttpRequest. Na verdade, o Spring já possui tal filtro implementando dentro do spring-web.jar, basta declararmos no web.xml.

Agora de dentro do aspecto de auditoria é possível acessar o request e a session desta forma:

Conclusão
A Programação Orientada a Aspectos é muito útil para funcionalidades como auditoria provendo sempre uma solução transparente. O Spring já nos traz vários recursos implementados que se utilizados de forma correta podem poupar muitos esforços. O @AspectJ está muito bem integrado com o Spring Framework, e o casamento das duas tecnologias pode trazer grandes benefícios.
Abraços,
Michel Zanini.
Fiz a implementação do exemplo porém o interceptor quando utiliza exemplo.Entity não funciona somente com exemplo.Cliente
JavaBoy
Abril 13, 2009 em 1:08 pm
Voce lembrou de fazer a class Cliente estender a classe Entity?
michelzanini
Abril 13, 2009 em 10:08 pm
Sim! Resolvi o problema usando exemplo.dao.EntityDao.inserir(..) no aspect.
Agora tenho um problema mas esta relacionado ao Spring:
web.xml
contextConfigLocation
/WEB-INF/applicationContext.xml
org.springframework.web.context.ContextLoaderListener
e criei uma classe para retornar o context application
public class SpringContext {
private static ApplicationContext app;
static {
if (app == null) {
app = new FileSystemXmlApplicationContext(“c:/temp/applicationContext.xml”);
}
}
public static ApplicationContext getContext() {
return app;
}
}
tem uma forma mais elegante que:
FileSystemXmlApplicationContext(“c:/temp/applicationContext.xml”);
estou com o arquivo no WEB-INF?!
Obrigado pela atenção
JavaBoy
Abril 14, 2009 em 2:56 pm
JavaBoy,
Para uma applicação web não é necessário criar a sua classe SpringContext. O ContextLoaderListener
já carrega o application context na inicialização. Depois, para obter a referencia para o objeto
ApplicationContext basta fazer:
ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext)
michelzanini
Abril 19, 2009 em 2:22 pm
Como faço para utilizar generics no pointcut? Eu tentei o exemplo abaixo e não funcionou.
@AfterReturning(“execution(public void “+
“br.dao.BaseDAO.salvar(T))”+
“&& args(baseEntidade) && @target(br.anotacao.Auditoria)”)
public void inserirApos(t) {
System.out.println(t.toString());
}
Thiago
Abril 18, 2009 em 2:17 pm
Nao vai ser possivel utilizar genericos nesse caso. Porem seu metodo pode receber um Object:
public void inserirApos(Object t) {
System.out.println(t.toString());
}
michelzanini
Abril 19, 2009 em 2:12 pm