Michel Zanini

Blog sobre o framework Spring e tecnologias relacionadas

AOP na prática: implementando um aspecto de auditoria

com 7 comentários

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.

Escrito por michelzanini

setembro 22, 2008 às 1:42 am

Publicado em spring

Etiquetado com , ,

7 Respostas

Assinar os comentários com RSS.

  1. 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

  2. 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

  3. 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

  4. Meu caso também é rerefente a Auditoria. Eu fiz o seguinte:

    @After(“@annotation(com.auditoria.Auditar)”)
    public void depoisDoMetodo() {
    System.out.println(“Auditando…”);
    }

    E ao executar um método com essa Annotation, o aspecto foi chamado. Só que eu passo as informações de auditoria na própria Annotation, pq eu posso criar vários métodos e dizer que quero auditá-los, da seguinte forma:

    @Auditar(operacao=”Enviar Fotos”, entidade = “Imóvel”)
    public void enviarFotos(){

    }

    Pois bem, eu quero obter referência à annotation do método lá no meu Aspecto, para pegar as informações de operacao e entidade.

    Não consegui fazer isso ainda de jeito nenhum. Alguem tem alguma dica?

    Grato,
    Marcio Lima

    Marcio Lima

    março 10, 2010 em 6:22 pm


Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Sair / Alterar )

Imagem do Twitter

You are commenting using your Twitter account. Sair / Alterar )

Foto do Facebook

You are commenting using your Facebook account. Sair / Alterar )

Connecting to %s

Seguir

Obtenha todo post novo entregue na sua caixa de entrada.