Conceitos fundamentais do AspectJ

Este artigo foi escrito com base num projecto desenvolvido no ISCTE, em 2009, com o nome “Aplicação de auxílio à depuração e rastreabilidade com Programação Orientada por Aspectos (AspectJ)” sob orientação do professor Manuel Menezes de Sequeira. Os co-autores são David Jardim e Jairo Avelar.

O AspectJ tem 3 conceitos fundamentais:

  • ponto de junção (join points)
  • ponto de corte ou secção (point cut) e
  • conselhos (advices).

Este artigo pretende introduzir e apresentar estes 3 conceitos.

Ponto de Junção (join point)

O ponto de junção (join point) é o conceito principal em AspectJ.

Por um ponto de junção, entende-se qualquer ponto de execução identificável num programa, como por exemplo, uma chamada a um método (method execution e method call),  a chamada a um construtor (construtor call e construtor call), o acesso a um campo de dados (field read acess), a invocação de uma excepção (exception handler execution), etc

No fundo “tudo o que acontece” num programa, de grosso modo, pode ser representado por um join point.

Todos os pontos de junção possuem um contexto associado (por exemplo, a chamada a um método possui um objecto chamador  – caller e um objecto que foi chamado – callee).

Nem todos os pontos de junção de execução do programa são passíveis de serem capturados, pois alguns fazem parte do funcionamento da linguagem e têm o seu acesso vedado.

Aos pontos de junção que podem ser capturados dá-se o nome de expostos.

 

Exemplo de um joint point de chamada de um método (method execution join point e call).
Exemplo de um joint point de chamada de um método (method execution join point e call).

 

Na figura anterior podemos ver um exemplo de um ponto de junção do tipo method execution, que neste caso, corresponde ao próprio corpo do método. No outro método (main) definido na mesma classe, ao invocar o método umMetodo(5) originamos, por sua vez, um ponto de junção do tipo call.

Pontos de Corte ou Secções (pointcuts)

A partir destes pontos, são criadas as regras que dão origem aos pontos de corte (ou secções). Os pontos de corte identificam e capturam pontos de junção de um programa. São também considerados como um (sub)conjunto de pontos de junção.

O seu modo de funcionamento é semelhante a uma regra criada pelo programador; uma espécie de “query”  que é realizada sobre a aplicação e sobre todos os pontos de junção disponíveis.

A ideia é criar regras (querys) suficientemente genéricas que possam ser aplicadas em vários pontos de junção, apenas com uma declaração.

Uma vez capturado um determinado ponto de junção, podemos definir que tipo de regras de entretecimento (“tecelagem”) devem ser executadas.

Na figura seguinte, é possível observar como poderia ser definido um ponto de corte que capturasse a execução do método definido na figura anterior.De salientar que com este ponto de corte, apenas conseguirmos capturar o(s) ponto(s) de junção que correspondem à sua declaração. Os pontos de corte podem ser declarados dentro de uma classe, interface ou aspecto (aspect).

A palavra aspect define uma unidade de código que representa um aspecto.

Um aspecto é o mecanismo disponibilizado pela POA para agrupar fragmentos de código referente aos componentes não­funcionais.

O seu comportamento é semelhante a uma classe. É o local ideal para declarar pontos de corte  (pointcuts) e conselhos (advices), peso estes possam ser também declarados numa simples classe.

Exemplo de declaração de um pointcut
Exemplo de declaração de um pointcut

Existem vários tipos de pointcuts, usados consoante a query que é necessário escrever para capturar os joinpoints pretendidos. De todos os tipos de jointpoints, existem alguns que importa realçar pela sua especificidade.

Um dos quais é o within() (Formalmente designado como “Lexical-Structure based” pointcut).

Este tipo, é usado para capturar todos os pontos de junção  dentro do corpo de uma classe ou aspecto. Desta forma, não é de estranhar que a sua principal utilidade passe pela exclusão dos pontos de junção presentes nos aspectos.

Existem outros dois tipos de pointcuts que importa referenciar, já que foram particularmente importantes no desenvolvimento do nosso programa: O this() e o target(). Estes pointcuts capturam os joinpoints tendo por base os tipos de objectos em tempo de execução.

Tipicamente, de cada vez que é feita uma invocação de um método, esta invocação é efectuada a partir de um método de instância (métodos possuindo uma instância implícita – acessível através de “this”) ou de um método de classe (métodos “estáticos”).

No contexto de uma execução, o this() refere-se ao objecto “actual” e o target ao objecto onde o método está a ser invocado.

Na figura seguinte, podemos observar um caso típico de invocação onde ambos os tipos existem. Mas há existem casos particulares onde não existe this, target ou nenhum deles.

Diferença entre this() e target().
Diferença entre this() e target()

Por exemplo, quando a invocação é feita a partir de um método de classe (“estático”), como o main(), não existe caller ou this(). Quando o método invocado é estático (por exemplo em Math.sin() não existe callee ou target().

Finalmente, pode nem existir nenhum deles, caso tanto o método chamador como o método invocado sejam estáticos (no caso da invocação do Math.sin() a partir do main() por exemplo. Em suma, o caller (this) é o objecto em cujo método é feita a invocação (call).

O callee (target) é o objecto através do qual se faz a invocação. Existem ainda joinpoints onde o objecto target não está (ainda) disponível.

Na figura seguinte, por exemplo, na invocação da instrução ptx = new Point(199,123); dá-se caso do objecto ainda não estar criado. Assim, o seus this será MyClass, mas o target será null.

Exemplo de um joinpoint cujo objecto target não existe
Exemplo de um joinpoint cujo objecto target não existe

Conselhos (Advices)

É ainda necessário descrever que acção ou acções o sistema deverá executar nos pointcuts. Para isto, falta introduzir um último conceito: o conselho (advice).

Se os pontos de corte (pointcuts) são os pontos de execução de um programa onde pretendemos realizar operações,  os conselhos (advices) permitem-nos, na prática, especificar as ditas operações. Para especificar um conselho é – naturalmente – obrigatório associá-lo a um ponto de corte (pointcut), que por sua vez é um subconjunto de todos os pontos de junção (joinpoints) presentes no sistema.

Para além disso, é possível também indicar o momento mais indicado para que o código presente no concelho seja executado: antes (before), “em torno” (around) e depois (after) do instante capturado pelo ponto de corte (pointcut).

Na realidade, existem 3 tipos de after. O after(), que é sempre executado, o after() returning que apenas é executado quando o método associado ao ponto de junção retorna e o after() throwing  que é apenas executado quando o método associado ao ponto de junção lança uma exepção.

Um conselho com execução “em torno” do jointpoint pode alterar ou simplesmente substituir o código capturado pelo declarado dentro do conselho.

 

Exemplo de um advice com execução before()
Exemplo de um advice com execução before()

 

Continuado com o exemplo da 2ª figura, podemos escrever um concelho que simplesmente escreva no ecrã a seguinte frase: “Olá, estive por aqui”.

Em primero lugar, é necessário especificar o momento de execução do conselho. Neste exemplo, escolhemos before(), i.e., o código do conselho deve ser executado antes do código presente no corpo do método.

Em seguida, especificamos qual o nome do ponto de corte à qual devemos associar o conselho. Seria, igualmente possível especificar directamente o ponto de corte directamente na assinatura do conselho (por exemplo: before():execution(public void umMetodo(..));)

A vantagem na utilização de nomes para referenciar os advices aos pointcuts é simplesmente uma questão prática de reutilização de código.

Uma vez definido o código do conselho, podemos ver a sua hipotética execução na figura seguinte.

 

Exemplo de uma hipotética execução de um sistema com AspectJ
Exemplo de uma hipotética execução de um sistema com AspectJ

 

O passo inicial é a invocação do ajc (compilador do AspecJ) indicando explicitamente todos os ficheiros necessários à compilação (é possível passar uma biblioteca já compilada através de ficheiros .jar, por exemplo).

De seguida, executa-se o código e assiste-se à execução do código do conselho no local (aliás, imediatamente antes) do local que foi capturado pelo ponto de corte. Apesar de neste exemplo se usar o before(), o identificador after() teria uma semelhante prestação (executando depois do resto do código).

Contudo, o around() tem um funcionamento um pouco diferente e vale a pena realçar o seu funcionamento uma vez que é o tipo de conselho que mais usamos ao longo da implementação da nossa ferramenta, uma vez que nos permite uma melhor simplicidade e organização do código.

Por outro lado, consegue-se garantir, de uma forma mais forte, que o ponto de corte pertence ao mesmo evento da execução do programa.

Este tipo especial de conselho tem a particularidade de “envolver” a execução, permitindo continuar com a execução original, alterar o seu contexto ou simplesmente substituir as instruções originais por outras instruções. Se dentro do conselho, pretendermos executar o código original, é necessário usar uma palavra especial – proceed().

Na figura seguinte, damos um exemplo de uma utilização simples do around(). Começamos por identificar o pointcut e de seguida o código que deverá ser executado, neste caso, um simples bloco try/catch que irá lançar uma excepção em caso de existência de algum erro.

 

Exemplo simples de utilização do tipo de conselho around().
Exemplo simples de utilização do tipo de conselho around()

Responder

O seu endereço de email não será publicado. Campos obrigatórios marcados com *

Este site utiliza o Akismet para reduzir spam. Fica a saber como são processados os dados dos comentários.