AspectJ Programacao orientada a aspectos em Java

This document presents a tutorial about AspectJ, an aspect-oriented extension to Java. Aspect-oriented programming (AOP) tries to solve some inefficiency of object-orientation, such as tangled code and code spread over several units, making harder software development and maintainability. AOP increases system modularity by separating code that implements specific functions, affecting different parts of the system, called crosscutting concerns. We present the main constructions of AspectJ, as well as examples of aspects to assist the assimilation of the concepts. We also discuss using design patterns to implement some features of AspectJ, and its benefits and liabilities. Resumo. Este documento apresenta um tutorial sobre AspectJ, uma extensão orientada a aspectos de Java. Programação orientada a aspectos (AOP) procura solucionar algumas ineficiências da orientação a objetos, como o entrelaçamento e espalhamento de código com diferentes propósitos. Este entrelaçamento e espalhamento tornam o desenvolvimento e a manutenção destes sistemas extremamente difı́cil. AOP aumenta a modularidade separando código que implementa funções especı́ficas, afetando diferentes partes do sistema, chamadas preocupações ortogonais (crosscutting concern). Nós apresentamos as principais construções de AspectJ, bem como exemplos de aspectos para auxiliar a assimilação dos conceitos. Também discutimos o uso de padrões de projetos para implementar algumas caracterı́sticas de AspectJ, e discutimos suas vantagens e desvantagens. 1. Introdução A necessidade de desenvolver software de qualidade aumentou o uso da orientação a objetos [Meyer, 1997, Booch, 1994] em busca de maiores nı́veis de reuso e manutenibilidade, aumentando a produtividade do desenvolvimento e o suporte a mudanças de requisitos. Entretanto, o paradigma orientado a objetos tem algumas limitações [Ossher et al., 1996, Ossher and Tarr, 1999], como o entrelaçamento e o espalhamento de código com diferentes propósitos, por exemplo, o entrelaçamento de código de negócio com código de apresentação, e o espalhamento de código de acesso a dados em vários módulos do sistema. Parte destas limitações podem ser compensadas com o uso de padrões de projetos [Buschmann et al., 1996, Gamma et al., 1994]. Apoiado pela CAPES Apoiado em parte pelo CNPq, processo 521994/96–9. Figura 1: Diagrama de classe do sistema Health Watcher. Por outro lado, extensões do paradigma orientado a objetos, como aspect–oriented programming (programação orientada a aspectos) [Elrad et al., 2001], subject–oriented programming [Ossher and Tarr, 1999], e adaptive programming [J. et al., 1994], tentam solucionar as limitações do paradigma orientado a objetos. Estas técnicas visam obter uma maior modularidade de software em situações práticas onde a orientação a objetos e padrões de projetos não fornecem suporte adequado. Uma destas técnicas, programação orientada a aspectos (AOP), parece bastante promissora [Murphy et al., 2001, Elrad et al., 2001, Laddad, 2002]. AOP procura solucionar a ineficiência em capturar algumas das importantes decisões de projeto que um sistema deve implementar. Esta dificuldade faz com que a implementação destas decisões de projeto sejam distribuı́das pelo código, resultando num entrelaçamento e espalhamento de código com diferentes propósitos. Este entrelaçamento e espalhamento tornam o desenvolvimento e a manutenção destes sistemas extremamente difı́cil. Desta forma, AOP aumenta a modularidade separando código que implementa funções especı́ficas, afetando diferentes partes do sistema, chamadas preocupações ortogonais (crosscutting concern). Exemplos de crosscutting concerns são persistência, distribuição, controle de concorrência, tratamento de exceções, e depuração. O aumento da modularidade implica em sistemas legı́veis, os quais são mais facilmente projetados e mantidos. AOP permite que a implementação de um sistema seja separada em requisitos funcionais e não–funcionais. Dos requisitos funcionais resultam um conjunto de componentes expressos em uma linguagem de programação atual, por exemplo, a linguagem Java [Gosling et al., 2000]. Já dos requisitos não–funcionais resultam um conjunto de aspectos (crosscutting concerns) relacionados às propriedades que afetam o comportamento do sistema. Com o uso da abordagem, os requisitos não–funcionais podem ser facilmente manipulados sem causar impacto no código de negócio (requisitos funcionais), uma vez que estes códigos não estão entrelaçados e espalhados em várias unidades do sistema. Desta forma, AOP possibilita o desenvolvimento de programas utilizando tais aspectos, o que inclui isolamento, composição e reuso do código de implementação dos aspectos. 2. Um exemplo de crosscutting concern — distribuição Vamos tomar como exemplo um sistema que registra queixas para o sistema público de saúde. O nosso objetivo é distribuir este sistema utilizando RMI [Microsystems, 2001]. A Figura 1 é um diagrama de classes UML [Booch et al., 1999] que mostra parte das classes do sistema Health Watcher. No diagrama estão representadas a classe fachada [Gamma et al., 1994], o ponto único de acesso ao sistema, um Servlet Java [Hunter and Crawford, 1998], que implementa a interface com o usuário, e neste caso é responsável por atualizar informações sobre uma queixa no sistema. Além disso, estão presentes classes que modelam as queixas ( ) do sistema e o responsável pela queixa ( ). A comunicação a ser distribuı́da é a entre a classe fachada e a interface com o usuário (servlets). Distribuição sem AOP Para implementar a distribuição no sistema sem utilizar AOP teremos de alterar uma série de classes. A Figura 2 mostra como o código de distribuição afeta o código fonte das classes do sistema. O código que está selecionado (por retângulos) nestas classes é responsável pela implementação da distribuição com RMI. Figura 2: Código fonte do sistema distribuı́do sem AOP. Note que o código de distribuição (selecionado por caixas) está entrelaçado com código funcional das classes e está espalhado por várias classes do sistema. Esta falta de modularidade torna o sistema difı́cil de manter e evoluir. Imagine que queiramos alterar o protocolo de comunicação, por exemplo, para CORBA [Orfali and Harkey, 1998], ou outro protocolo qualquer. Terı́amos que alterar diretamente várias classes do sistema. Distribuição com AOP Para distribuir o mesmo sistema utilizando AOP, terı́amos de definir um aspecto de distribuição que faz as alterações necessárias no sistema, para torná–lo distribuı́do. Lembre-se que AspectJ pode alterar a estrutura estática e dinâmica das classes de um sistema. A Figura 3 mostra o resultado do uso de AOP. Figura 3: Código fonte do sistema distribuido com AOP. Note que as classes do sistema foram preservadas. Além de não haver código de distribuição espalhado pelo sistema e entrelaçamento com código funcional, a alteração do sistema não é invasiva, ou seja, não é feita com alteração direta do código fonte. A mesma é feita automaticamente através da composição do sistema com o aspecto definido. Este processo é chamado de weaving, ou recomposição aspectual. A Figura 4 mostra o processo de recomposição. O weaver é um compilador que dado o código fonte de um sistema e um conjunto de aspectos gera uma versão do sistema com estes aspectos, como, por exemplo, o aspecto de distribuição. Para tornar o sistema distribuı́do utilizando outro protocolo, seria necessário implementar outro aspecto que utiliza o protocolo requerido e em seguida utilizar o weaver de AspectJ para gerar a versão do sistema distribuı́da com tal protocolo. Figura 4: Recomposição aspectual, ou weaving. Desta forma teremos a definição de um aspecto de distribuição que afeta as classes do sistema para implementar a sua distribuição com RMI, como mostrado no diagrama de classes na Figura 5. Figura 5: Diagrama de classe do sistema Health Watcher levando em conta o aspecto de distribuição. Observe que o aspecto de distribuição afeta a interface com o usuário, a fachada e os dados que são transmitidos entre eles. 3. AspectJ Nesta seção apresentamos a linguagem AspectJ [Kiczales et al., 2001], uma extensão orientada a aspectos, de propósito geral, da linguagem Java [Gosling et al., 2000]. 3.1. Anatomia de um aspecto A principal construção em AspectJ é um aspecto. Cada aspecto define uma função especı́fica que pode afetar várias partes de um sistema, como, por exemplo, distribuição. Um aspecto, como uma classe Java, pode definir membros (atributos e métodos) e uma hierarquia de aspectos, através da definição de aspectos especializados. Aspectos podem alterar a estrutura estática de um sistema adicionando membros (atributos, métodos e construtores) a uma classe, alterando a hierarquia do sistema, e convertendo uma exceção checada por uma não checada (exceção de runtime). Esta caracterı́stica de alterar a estrutura estática de um programa é chamada static crosscutting. Além de afetar a estrutura estática, um aspecto também pode afetar a estrutura dinâmica de um programa. Isto é possı́vel através da interceptação de pontos no fluxo de execução, chamados join points, e da adição de comportamento antes ou depois dos mesmos, ou ainda através da obtenção de total controle sobre o ponto de execução. Exemplos de join points são: invocação e execução de métodos, inicialização de objetos, execução de construtores, tratamento de exceções, acesso e atribuição a atributos, entre outros. Ainda é possı́vel definir um join point como resultado da composição de vários join points. Normalmete um aspecto define pointcuts, os quais selecionam join points e valores nestes join points e advices que definem o comportamento a ser tomado ao alcançar os join points definidos pelo pointcut. Nas seções seguintes, à medida que apresentamos as construções de AspectJ, exemplificamos as mesmas mostrando a implementação do aspecto de distribuição para o sistema Health Watcher. 3.2. Modelo de join point Um join point é um ponto bem definid