Aspectos para Leigos · Aspectos para Leigos ... para sua própria capacidade intelectual e se...

11
56 Programação Orientada a Aprenda a dar os primeiros passos no paradigma orientado a aspectos usando o AspectJ com anotações. Aspectos para Leigos Quando se começou a falar de programação orientada a objetos, muitos ficaram com medo, porém outros deram um passo à frente e aprenderam uma melhor forma de desenvolver software. Hoje vem se falando cada vez mais sobre orientação a aspectos e este é o momento em que cada um deve decidir se será um dos que ficarão com medo ou dos que darão o próximo passo! Este artigo irá apresentar os primeiros passos da programação orientada a aspectos de uma forma bem “palatável” para desenvolvedores com pouca experiência. Eduardo Guerra ([email protected] / Twitter @emguerra) é desenvolvedor de frameworks, pesquisador em design de software, editor-chefe da revista MundoJ e professor do ITA, onde concluiu sua graduação, mestrado e doutorado. Possui diversas certificações da plataforma Java e experiência como arquiteto de software nas plataformas Java SE, Java EE e Java ME. Participou de projetos open-source, como SwingBean e JColtrane, e acredita que um bom software se faz mais com criatividade do que com código. uitas pessoas “se arrepiam” ao ouvirem falar de orientação a aspectos, como se fosse uma técnica de programação utilizada apenas por aqueles programa- dores que só trabalham no porão de casa e usam uma capa preta. Pois saibam que o mesmo ocorria quando a orientação a objetos estava surgindo. Inclusive, muita gente tem medo de aprender orientação a objetos até hoje! Neste momento, você pode estar se perguntando: “mas a programação orientada a aspectos é tão complicada assim?”. Na verdade ela não é complicada, porém introduz novos conceitos que precisam ser compreendidos antes da sua utilização. Ela muda a forma de se pensar em software e, na verdade, é nessa mudança de paradigma onde está a real dificuldade. Gostaria de pedir aos leitores que dêem um voto de confiança para sua própria capacidade intelectual e se desfaçam de qualquer “pré-conceito” que possuam sobre esse novo paradigma antes de começarem a ler o artigo. Não tenham medo! O objetivo deste artigo é fazer uma introdução “gentil” à orientação a aspectos de forma a ser acessível a desenvolvedores menos experientes. Será utilizado o AspectJ como base para o artigo, porém apenas sua sintaxe baseada em anotações será abordada. O artigo irá começar levando o leitor para um passeio pelos principais conceitos desse novo paradigma e finalizará mostrando um exemplo de utilização. De Paradigma em Paradigma Antes da programação estruturada, os softwares eram criados através de grandes blocos de código. Quando era necessário exe- cutar uma determinada rotina, pulava-se para a linha de código onde ela começava e no final de sua execução pulava-se de volta para a linha posterior a sua chamada. Para fazer esse pulo entre as linhas era utilizado o famoso comando goto. O mesmo recurso era utilizado quando se precisava de uma condicional ou de uma iteração. Com a evolução natural do software, estes pulos entre linhas criavam um verdadeiro “caminho de minhoca” e daí surgia o então apelidado “código-espaguete”. Era muito difícil isolar e dividir partes do software, principalmente porque os gotos po- deriam desviar o fluxo de execução para absolutamente qualquer linha de código. Programação estruturada A programação estruturada incluiu novos conceitos que au- xiliavam na separação e estruturação da lógica de execução do software. As estruturas de controle, como if, switch, for, while e do-while, introduziram uma forma mais organizada de executar comandos condicionais e iterações. Com o goto, por exemplo, era : : www.mundoj.com.br : :

Transcript of Aspectos para Leigos · Aspectos para Leigos ... para sua própria capacidade intelectual e se...

56

Programação Orientada a

Aprenda a dar os primeiros passos no paradigma orientado

a aspectos usando o AspectJ com anotações.

Aspectos para Leigos

Quando se começou a falar de programação orientada a objetos, muitos ficaram com medo, porém outros deram um passo à frente e aprenderam uma melhor forma de desenvolver software. Hoje vem se falando cada vez mais sobre orientação a aspectos e este é o momento em que cada um deve decidir se será um dos que ficarão com medo ou dos que darão o próximo passo! Este artigo irá apresentar os primeiros passos da programação orientada a aspectos de uma forma bem “palatável” para desenvolvedores com pouca experiência.

Eduardo Guerra

([email protected] / Twitter @emguerra) é desenvolvedor de frameworks, pesquisador em design de software, editor-chefe da revista MundoJ e professor do ITA, onde concluiu sua graduação, mestrado e doutorado. Possui diversas certificações da plataforma Java e experiência como arquiteto de software nas plataformas Java SE, Java EE e Java ME. Participou de projetos open-source, como SwingBean e JColtrane, e acredita que um bom software se faz mais com criatividade do que com código.

uitas pessoas “se arrepiam” ao ouvirem falar de orientação a aspectos, como se fosse uma técnica de programação utilizada apenas por aqueles programa-

dores que só trabalham no porão de casa e usam uma capa preta. Pois saibam que o mesmo ocorria quando a orientação a objetos estava surgindo. Inclusive, muita gente tem medo de aprender orientação a objetos até hoje! Neste momento, você pode estar se perguntando: “mas a programação orientada a aspectos é tão complicada assim?”. Na verdade ela não é complicada, porém introduz novos conceitos que precisam ser compreendidos antes da sua utilização. Ela muda a forma de se pensar em software e, na verdade, é nessa mudança de paradigma onde está a real dificuldade.

Gostaria de pedir aos leitores que dêem um voto de confiança para sua própria capacidade intelectual e se desfaçam de qualquer “pré-conceito” que possuam sobre esse novo paradigma antes de começarem a ler o artigo. Não tenham medo! O objetivo deste artigo é fazer uma introdução “gentil” à orientação a aspectos de forma a ser acessível a desenvolvedores menos experientes. Será utilizado o AspectJ como base para o artigo, porém apenas sua sintaxe baseada em anotações será abordada. O artigo irá começar levando o leitor para um passeio pelos principais conceitos desse novo paradigma e finalizará mostrando um exemplo de utilização.

De Paradigma em Paradigma

Antes da programação estruturada, os softwares eram criados através de grandes blocos de código. Quando era necessário exe-cutar uma determinada rotina, pulava-se para a linha de código onde ela começava e no final de sua execução pulava-se de volta para a linha posterior a sua chamada. Para fazer esse pulo entre as linhas era utilizado o famoso comando goto. O mesmo recurso era utilizado quando se precisava de uma condicional ou de uma iteração. Com a evolução natural do software, estes pulos entre linhas criavam um verdadeiro “caminho de minhoca” e daí surgia o então apelidado “código-espaguete”. Era muito difícil isolar e dividir partes do software, principalmente porque os gotos po-deriam desviar o fluxo de execução para absolutamente qualquer linha de código.

Programação estruturada

A programação estruturada incluiu novos conceitos que au-xiliavam na separação e estruturação da lógica de execução do software. As estruturas de controle, como if, switch, for, while e do-while, introduziram uma forma mais organizada de executar comandos condicionais e iterações. Com o goto, por exemplo, era

: : www.mundoj.com.br : :

57

possível o equivalente a saltar para o meio de um bloco if ou whi-le, gerando muitas vezes erros difíceis de serem detectados. Outro conceito importante que foi introduzido na programação estru-turada foram as funções e procedimentos, que permitiram uma divisão da lógica do software. Com esse recurso é possível dividir o código em pedaços que lidam com responsabilidades diferentes em funções, organizando melhor o software internamente. Esses novos conceitos introduzidos permitem que uma equipe trabalhe simultaneamente em partes diferentes do software e facilita que as funções desenvolvidas possam ser reutilizadas dentro do software ou mesmo por aplicações diferentes.

Curiosidade:

o comando goto foi mantido em algumas linguagens estru-turadas como C. Apesar desse comando não existir em Java, goto é uma palavra reservada da linguagem!

Para uma boa compreensão deste artigo é necessário que o leitor possua certa familiaridade em como implementar esses conceitos da orientação a objetos na linguagem Java, como, por exemplo, estender uma classe e criar e implementar interfaces.

Programação orientada a objetos

Programação orientada a aspectos

Enquanto a programação estruturada buscou estruturar a lógica do software, pode-se dizer que a programação orientada a objetos organizou os conceitos e os dados. Os módulos de um software orientado a objetos são as classes, que podem possuir compor-tamento e estado, representados, respectivamente, por métodos e atributos. A ideia é que uma classe represente um conceito, o qual possui informações associadas e ações relacionadas com essas informações. Em um sistema que possui uma classe para representar um carro, a potência do motor seria uma informação e “acelerar” seria uma ação. Uma classe representa um conceito abstrato que pode ser instanciado e essas instâncias são os objetos. Esses novos conceitos, classes e objetos são os elementos centrais no paradigma orientado a objetos.

Se pudesse escolher apenas uma palavra para definir a orientação a objetos, essa palavra seria “abstração”. A classe representa uma abstração dos objetos. Essa abstração pode ser trabalhada em diversos níveis através da herança, que permite que um conceito possa ser especializado ou generalizado. Retornando ao exemplo da classe “carro”, “veículo” seria um exemplo de generalização desse conceito e “utilitário” seria o exemplo de uma especialização.

Porém não apenas os conceitos podem ser abstraídos, os compor-tamentos também podem! A ideia é que um pedaço de código que utilize uma determinada classe não precise saber seus detalhes de implementação dependa apenas da interface utilizada para essa interação. Esse conceito é conhecido como encapsulamento e em Java é implementado através das interfaces. A ideia é esconder o estado interno da classe e prover a modificação e acesso aos mesmos apenas através dos métodos. Voltando mais uma vez no exemplo da classe “carro”, ao chamar o método “acelerar()” o motorista não precisa entender como ocorre a combustão, o movimento dos pistões, da embreagem e de todas as engrenagens existentes até chegar à roda, quer apenas que o carro comece a ganhar velocidade.

Um conceito final, porém não menos importante, da orientação a objetos é o polimorfismo. O significado da palavra “polimor-fismo” é “múltiplas formas” e representa o conceito de que um

determinado objeto pode assumir várias formas. Explicando me-lhor, um objeto pode assumir todas as formas de suas abstrações. Um objeto “utilitário”, por exemplo, poderia assumir a forma de “carro” e “veículo”. Sendo assim, ele poderia ser passado para um método que esperasse receber um “veículo” como parâmetro. Em outras palavras, o polimorfismo permite que as abstrações defi-nidas através de classes, herança e interfaces sejam efetivamente utilizadas pelos clientes delas.

Todos esses novos conceitos introduzidos pela programação orientada a objetos trouxeram uma nova filosofia para o desenvol-vimento de software. A orientação a objetos facilitou ainda mais a modularização da aplicação. Com a divisão correta de responsa-bilidades e o uso das técnicas adequadas para o desacoplamento entre as classes, é possível minimizar os impactos da alteração de uma classe no resto do sistema. A reutilização de código também pode ser maior, pois o uso correto das abstrações tornam o com-portamento de partes do código mais flexíveis, possibilitando sua adaptação para diversos ambientes. Com base nesses conceitos é que surgiram os frameworks, nos quais é possível se reutilizar não apenas o código, mas também o design.

Infelizmente a orientação a objetos sozinha não resolveu todos os problemas e em algumas situações ela ainda falha em modularizar certos tipos de funcionalidades. Para exemplificar essa questão, será apresentado o exemplo clássico do código do Tomcat (que é sempre apresentado em artigos e apresentações sobre aspectos), exemplificando uma função com boa modularidade e outra cujo código está espalhado pelas classes. A figura 1 mostra em verme-lho as linhas de código referentes à funcionalidade de verificação de padrões de URL no código do Tomcat. É possível observar que elas estão concentradas em duas classes da aplicação, ficando iso-lado das outras classes. A figura 2 já apresenta as linhas de código referentes ao registro de auditoria e é claramente perceptível como ele se encontra espalhado por todas as classes.

58

: : www.mundoj.com.br : :

Este artigo procura utilizar a terminologia oficial em portu-guês para os termos a orientação a aspectos. Essa termino-logia pode ser encontrada no endereço http://wiki.dcc.ufba.br/AOSDbr/TermosEmPortugues. Para ajudar os leitores que quiserem se aprofundar no tema, também serão apresenta-dos os nomes em inglês.

Esse é um problema conhecido como espalhamento de código (code scattering), no qual o código que implementa uma deter-minada funcionalidade da aplicação está espalhado por várias classes. Os tipos de interesses que possuem essas características são chamados de transversais (crosscutting), pois eles entrecortam a estrutura da aplicação afetando diversas classes. Os requisitos não-funcionais costumam ser bons exemplos de interesses trans-versais. O exemplo clássico são as funcionalidades de auditoria, chamadas mais informalmente de logging. Normalmente deve-se fazer o registro de auditoria em vários pontos da aplicação. Por mais que se isole em algumas classes a funcionalidade central, essa classe ainda precisará ser chamada em diversos outros locais, deixando-a espalhada. Esse tipo de interesse é muito difícil de ser modularizado apenas com os conceitos da orientação a objetos.

Outro problema provocado por essa falta de modularização dos interesses transversais se chama emaranhamento de código (code tangling). Esse emaranhamento ocorre quando um determinado método possui mais linhas para executar tarefas periféricas do que para sua responsabilidade principal. Dentro dessas atividades periféricas podem ser incluídas coisas como obtenção de recursos, gerenciamento de transações, verificações de segurança, validação de parâmetros etc. A existência desse emaranhamento de código dificulta sua reutilização em um contexto diferente. Imagine, por exemplo, um método que recebe um login e uma senha para veri-ficar se confere com o que está no banco de dados e grava no log se o usuário conseguiu se autenticar na aplicação. Esse método não poderia ser utilizado para verificar a senha anterior em uma troca de senha, pois o que seria gravado no log não condiz com sua utilização.

O objetivo da programação orientada a aspetos é solucionar esses problemas de emaranhamento e espalhamento de código. Os aspectos são componentes de software que conseguem mo-dularizar interesses transversais da aplicação e com isso permitir uma melhor divisão das funcionalidades do sistema. Os aspectos conseguem interceptar a execução do programa em pontos como a invocação de um método, o acesso a um atributo ou o lança-mento de uma exceção. Nesses pontos é possível inserir novos comportamentos, como o log, por exemplo! Com isso os interes-ses transversais são implementados apenas nos aspectos, porém podem ser inseridos em diversos pontos da aplicação.

Vale ressaltar que a orientação a aspectos adiciona novos conceitos na forma de programar que não substituem os já existentes. Da mesma forma que a orientação a objetos somou novos conceitos aos que existiam na programação estruturada, a orientação a as-pectos adiciona novos conceitos ao que existem na orientação a

objetos. Sendo assim, fique tranquilo que o conhecimento que você já possui não será perdido!

Não se preocupe se não entendeu direito como os aspectos fun-cionam, pois isso será melhor explicado nas próximas seções. O importante no momento é o leitor ter compreendido o que são interesses transversais e que a programação orientada a objetos falha em modularizá-los. A seguir é apresentado um exemplo que torna mais palpável essa diferença entre os paradigmas.

Exemplo de diferença entre os paradigmas

O exemplo utilizado será um jogo de damas. Neste jogo é preciso possuir uma lógica que inicialize o jogo e outra que verifique se algum dos jogadores o venceu. Adicionalmente é preciso ter a implementação do movimento de uma peça e de uma peça “comendo” a outra. Além disso, não deve se esquecer que uma peça pode se transformar em uma dama, o que muda a forma como ela anda e “come” peças adversárias. A figura 3 mostra as diferenças na implementação do problema pelos três paradigmas. A programação estruturada dividiria o problema em apenas uma dimensão através das funções. Seria preciso manter a estrutura de dados do jogo em uma variável global, ou passá-la como parâme-tro para cada função. Quando o jogador realizasse uma jogada, seria preciso executar comandos condicionais para verificar se a peça é normal ou uma dama, para saber qual das funções invocar.

No caso da programação orientada a objetos, a divisão seria em duas dimensões. Primeiro os conceitos seriam estruturados em classes e depois os comportamentos em métodos dentro delas. O conceito de peça poderia ser abstraído em uma classe ou interface. Dessa forma, a abstração seria especializada por classes represen-tando a dama e a peça regular e o polimorfismo utilizado para que o jogo não precisasse saber o tipo da peça escolhida.

Apesar da melhor estrutura, algumas funcionalidades ainda fica-riam espalhadas pelo código. Após “comer” uma pedra ou andar com uma peça, seriam necessárias ações, como, por exemplo, verificar se o jogo terminou e formar dama se for aplicável. Por mais que fossem criados métodos que encapsulassem essa lógica, eles ainda precisariam ser chamados em vários pontos do código. O método responsável por comer uma peça, por exemplo, não deveria ser responsável por verificar o final do jogo ou formar uma dama. Esses interesses são transversais a ele!

Uma abordagem orientada a aspectos na resolução desse proble-ma criaria aspectos que encapsulariam essas funcionalidades. Os aspectos interceptariam o término dos métodos para andar com peças e para comer peças do adversário e executaria a lógica para criação de damas e verificação de vencedor de forma transparen-te. Dessa forma, teríamos uma terceira dimensão na divisão do software, ortogonal às outras duas dimensões e responsável por modularizar esses interesses transversais.

59

Conceitos da orientação a aspectos

Na seção anterior, foi mostrada a evolução que ocorreu entre a programação estruturada e a orientação a objetos, ressaltando quais os conceitos que foram introduzidos na mudança de para-digma. Apesar da grande evolução em termos de reúso, também foi dito que a orientação a objetos falha em modularizar os inte-resses transversais, sendo esse o principal objetivo da orientação a aspectos. Esta seção irá explorar mais a fundo os novos conceitos introduzidos na programação orientada a aspectos.

O AspectJ é a implementação mais popular e completa de aspec-tos para a linguagem Java, sendo talvez a implementação mais completa também em relação a todas as linguagens. Ele é mantido pela Eclipse Foundation e pode ser adquirido no endereço http://www.eclipse.org/aspectj/. O plugin AJDT (AspectJ Development Tools) fornece um excelente suporte para aspectos no IDE Eclipse. Apesar dos conceitos serem gerais a qualquer implementação da orientação a aspectos, eles serão exemplificados usando como referência a implementação do AspectJ.

O AspectJ suporta duas sintaxes distintas. A linguagem original utilizada pelo AspectJ possui uma sintaxe própria, sendo que, por exemplo, um aspecto era definido por “public aspect Aspecto”. Em versões mais recentes, outra sintaxe baseada em anotações também foi disponibilizada. Nem tudo que é possível fazer com a sintaxe original é possível fazer com a sintaxe baseada em anota-ções. Neste artigo, para evitar o impacto de se precisar aprender uma nova linguagem, será utilizada apenas a abordagem baseada em anotações. Porém é importante que o leitor saiba da existência das duas e que a sintaxe original ainda é mais poderosa do que a que será apresentada.

Além do AspectJ existem outras implementações de aspectos para a linguagem Java. O Spring AOP era uma implemen-tação de aspectos dentro do framework Spring, porém nas versões mais recentes foi substituído pelo próprio AspectJ. Outra implementação de aspectos é o JBoss AOP (http://www.jboss.org/jbossaop), o qual é utilizado dentro do JBoss Application Server para implementação de algumas das fun-cionalidades do container.

Apesar da linguagem Ruby possuir características dinâmicas, os conceitos da programação orientada a aspectos podem ajudar a estruturar melhor a aplicação. O Aquarium (http://aquarium.rubyforge.org/) é um framework que implementa a programação orientada a aspectos para a linguagem Ruby.

Algumas pessoas consideram frameworks e APIs que dão suporte a criação interceptadores, como os Interceptors do EJB3, implementações da orientação a aspectos. Apesar dos interceptors poderem ser utilizados para modularizar interesses transversais, eles não possuem os mesmos con-ceitos, como pontos e conjuntos de junção, da orientação a aspectos. Espero que com a leitura deste artigo, seja possível entender a diferença entre as abordagens.

Ponto de junção (join point)

Os pontos de junção representam os pontos da aplicação onde novos comportamentos podem ser adicionados pelos aspectos. O mais comum é a execução de um método, porém existem outros como a criação de um objeto, o lançamento de uma exceção e o acesso a um atributo.

A figura 4 ilustra alguns desses pontos de junção em um pedaço de código-fonte. Perceba que esses pontos de junção podem estar ligados a uma classe, a um método ou a um atributo. A criação de uma classe está ligada à classe criada e o lançamento de uma exceção à classe da exceção lançada. Exemplos de pontos de jun-ção relacionados a um atributo são a modificação e o acesso a ele. Em relação a métodos, a inserção de comportamento pode ser no ponto onde ele é invocado ou na sua execução, sendo que nesse caso o aspecto irá agir no corpo do método.

Nem todas as implementações de aspectos suportam todos os tipos de pontos de junção. O Spring AOP, por exemplo, não suportava os pontos de junção relacionados com atributos sob a alegação que isso quebraria o encapsulamento. O próprio AspectJ não suporta alguns tipos de pontos de junção dependendo da for-ma como é feito o Weaving (não tem problema se não sabe o que é isso, pois ainda será explicado nas próximas seções).

60

: : www.mundoj.com.br : :

Conjunto de junção (pointcut)

Adendos (Advice)

Um conjunto de junção é um conjunto de pontos de junção no qual um determinado aspecto irá inserir um novo comportamen-to. Aprender a definir de forma precisa o conjunto de junção é muito importante, pois ele define os pontos onde o aspecto deve atuar. Um erro na definição desse critério pode fazer o aspecto atuar em um ponto onde não deveria ou deixar de inserir seu comportamento em um ponto importante.

Os conjuntos de junção podem ser formados a partir de diferen-tes critérios, como está exemplificado na figura 5. Ela apresenta uma classe na qual os métodos formam conjuntos por diferentes critérios. O conjunto em vermelho engloba todos os métodos que retornam void e o conjunto em verde, todos os métodos que não recebem parâmetros. Qualquer outra informação pode ser utili-zada como critério para a formação do conjunto de junção, in-cluindo o nome do pacote, o nome da classe, o nome do método, classes que estende, interfaces que implementa, tipos do retorno, tipos dos parâmetros etc.

O AspectJ possui uma linguagem que permite expressar esse con-junto de junção. Não está no escopo deste artigo explicar toda a linguagem de definição de conjuntos de junção, porém alguns exemplos serão apresentados. Abaixo está a definição dos dois conjuntos de junção apresentados na figura 5:

execution(void Numero*.*(..))

execution(* Numero*.*())

execution(@br.mundoj.DataModification * br.mundoj.*.*(..))

execution(* (@br.mundoj.Secured br.mundoj.*).*(..))

A primeira palavra na definição do conjunto de junção representa o tipo do conjunto de junção que será utilizado. Nos exemplos apresentados, os dois conjuntos representam apenas a execução

de métodos. O símbolo “*” é utilizado quando qualquer valor pode ser aceito. Por exemplo, no segundo conjunto de junção qualquer retorno será aceito. O asterisco também pode ser usado para definir padrões de nome, como nos dois exemplos, onde se-rão aceitos métodos de classes cujo nome começa com “Numero”. O primeiro exemplo aceita métodos com qualquer quantidade de parâmetros e utiliza “..” para definir isso.

Uma forma simples de selecionar um conjunto de junção é através de anotações. Muitas vezes é complicado escolher um padrão de nomenclatura ou algum critério para selecionar as classes ou mé-todos que precisam ser interceptados. Nesse caso, é possível criar uma anotação e utilizá-la como referência para a configuração do conjunto de junção. Abaixo seguem respectivamente os exemplos de definição de conjuntos de junção para métodos com a anotação @DataModification e para métodos de classes com a anotação @Secured:

A linguagem para expressar pontos de junção do AspectJ é bem poderosa, suportando uma quantidade muito grande de recursos. O artigo irá apresentar alguns exemplos com os usos mais comuns dessa linguagem, porém não irá entrar em detalhes em relação a todos os recursos da mesma.

Os adendos representam o comportamento que será adicionado pelos aspectos nos conjuntos de junção. Esse comportamento pode ser adicionado antes, depois ou em torno da execução do

61

ponto de junção. No caso da execução antes ou depois, o ponto de junção sempre será executado e o aspecto não possui contro-le sobre o mesmo. No caso do adendo em torno da execução, a chamada do ponto de junção interceptado ficará sob o controle do adendo. Ele pode ser chamado normalmente, pode não ser chamado ou pode ser chamado mais de uma vez.

A Listagem 1 apresenta o exemplo de um aspecto com adendos que registram no console o inicio e o final da execução de todos os métodos de classes do pacote “br.mundoj”. Como é a primeira vez que alguns leitores estão entrando em contato com um aspecto, será explicado passo-a-passo cada parte do código. Vale a pena lembrar que a sintaxe do AspectJ utilizada neste artigo é 100% baseada em anotações e, apesar dos novos conceitos, não introduz nenhum elemento novo na linguagem Java.

Para ser um aspecto, a classe precisa possuir a anotação @Aspect. Os adendos dentro do aspecto são representados através de mé-

todos, que precisam ser anotados com uma das anotações @Be-fore, @Around, @After, @AfterReturning ou @AfterThrowing. A Listagem 1 apresenta apenas exemplos do @Before e @After, que obviamente marcam adendos a serem executados, respectivamen-te, antes e depois do ponto de junção. Observe que cada anotação recebe como parâmetro a expressão do conjunto de junção onde o adendo deve atuar. O parâmetro recebido pelo método, da classe JoinPoint, é opcional e permite recuperar informações a respeito do ponto de junção onde o aspecto está atuando. No exemplo, a assinatura do método é recuperada, porém outras informações como a classe, o objeto e os parâmetros também podem ser obti-dos separadamente.

Depois de criado o aspecto não é preciso fazer ou configurar mais nada para ele agir. É só rodar o projeto para ver os adendos sendo executados nos pontos da aplicação definidos pelo conjunto de junção.

Criando projetos e executando aplicações com AspectJ

Para rodar um projeto que usa o AspectJ usando o plugin AJDT é muito simples. Na hora de criar um projeto, ao invés de criar um projeto Java comum, crie um projeto do tipo “As-pectJ Project”.

Se você já criou o projeto, outra opção é clicar no mesmo com o botão direito nele e escolher “Configure > Convert to AspectJ Project”.

Depois de ver os aspectos em ação pela primeira vez, talvez sua reação seja algo como: “Mas como isso funciona?”. Na verdade em algum momento é feito o chamado “Weaving”, que adiciona o comportamento dos adendos na aplicação. Continue lendo o artigo que isso também será explicado mais a frente.

Para executar o projeto com os aspectos, basta escolher “Run as > AspectJ/Java Application”.

62

: : www.mundoj.com.br : :

Listagem 1. Exemplos de adendos do tipo @Before e @After. Listagem 2. Exemplo de adendo do tipo @Around.

@Aspect

public class AspectoRegistro{

@Before(“execution(* br.mundoj.*.*(..))”)

public void imprimirConsoleAntes(JoinPoint jp) {

System.out.println(“Iniciando “ + jp.getSignature() + “ as “

+ System.currentTimeMillis());

}

@After(“execution(* br.mundoj.*.*(..))”)

public void imprimirConsoleDepois(JoinPoint jp) {

System.out.println(“Finalizando “ + jp.getSignature() + “ as “

+ System.currentTimeMillis());

}

}

@Aspect

public class AspectoExemplo {

@Around(“execution(* br.mundoj.*.*(..))”)

public Object imprimirConsole(ProceedingJoinPoint jp) throws Throwable{

System.out.println(“Iniciando “ + jp.getSignature() + “ as “

+ System.currentTimeMillis());

Object returned = jp.proceed();

System.out.println(“Finalizando “ + jp.getSignature() + “ as “

+ System.currentTimeMillis());

return returned;

}

}Os adendos que atuam em torno do ponto de junção são um pouco diferentes. A Listagem 2 apresenta um exemplo de códi-go similar a Listagem 1, porém utilizando um adendo do tipo @Around. A principal diferença é que a invocação do ponto de junção que está sendo interceptado fica sob o controle do aspecto. Dessa forma, ele pode, por exemplo, mudar os parâmetros pas-sados para o método, alterar o retorno ou mesmo nem invocar o método interceptado.

Na Listagem 2 é possível notar várias diferenças na assinatura do método que recebe o adendo. A primeira diferença é o parâmetro recebido, que é do tipo ProceedingJoinPoint. Essa interface possui um método chamado proceed() que invoca o ponto de junção interceptado de forma explícita no corpo do adendo. Quando o método proceed() é chamado sem parâmetros, os parâmetros originais da invocação do ponto de junção são mantidos. Porém ele também pode ser invocado passando um array de objetos com os parâmetros que devem ser passados ao método interceptado. É importante ressaltar que o método proceed() não precisa obri-gatoriamente ser invocado ou mesmo pode ser invocado mais de uma vez. Tudo fica sob o controle do aspecto!

Outras diferenças podem ser notadas pela assinatura do método, como o fato de ele precisar lançar throwable visto que o ponto de junção pode lançar alguma exceção ou erro. Outra diferença é que o método precisa retornar Object ao invés de void, como nos casos do @Before e @After. Sendo assim, o que for retornado pelo adendo do tipo @Around será substituído pelo que for retornado pelo ponto de junção. No caso da Listagem 2, como o retorno não precisa ser modificado, o retorno do método proceed() é armaze-nado em uma variável e retornado pelo adendo.

As anotações @AfterReturning e @AfterThrowing são na verdade tipos especiais de adendos do tipo @After. A diferença é que o adendo somente será executado caso, respectivamente, o método retorne com sucesso ou jogue uma exceção.

Declaração intertipo (inter-type declaration)Aspecto (aspect)

Uma declaração intertipo é quando um aspecto faz declarações para outro tipo, podendo esse ser uma classe, uma interface ou

mesmo um aspecto. Esse recurso permite inserir novos membros, como métodos e atributos, modificar a hierarquia de tipos e intro-duzir novas anotações. Esse tipo de declaração intertipo, também conhecida como introduction, é um tipo de característica estática transversal que pode ser adicionada por um aspecto. Existem outros tipos de modificações estáticas que não serão abordadas, como o “amaciamento” de exceções (como uma forma de lidar com exceções checadas de uma forma transversal) e a declaração de erros caso certa situação seja detectada.

Pode parecer estranho que a introdução de novos membros nas classes possa fazer parte de um interesse transversal, porém, mui-tas vezes, certas informações e métodos são necessários em diver-sas classes por questões transversais. Imagine, por exemplo, que várias classes necessitem que suas instâncias armazenem a data de sua última modificação. É possível criar um adendo que atue nos métodos setters, porém onde essa informação será armazenada? Como ela seria recuperada?

É possível usar o recurso da declaração intertipo para inserir em todas as classes em que isso for necessário, um campo com a data da modificação e um método para recuperá-la. Dessa forma, essa declaração seria parte do aspecto que lidaria com o interesse transversal de armazenamento da data da última modificação de um objeto.

Por ser um artigo introdutório, este artigo não irá entrar em de-talhes a respeito de como implementar as declarações intertipos. Porém, para o leitor que está entrando em contato com o conceito de aspectos pela primeira vez, é importante saber que eles existem e que esse tipo de declaração estática pode fazer parte da imple-mentação de um interesse transversal.

O aspecto é o módulo da aplicação em que um interesse trans-versal é modularizado. Ele pode conter métodos e atributos como uma classe, porém também pode conter conjuntos de junção,

63

adendos e declarações intertipos. Da mesma forma que se deve procurar encapsular apenas uma responsabilidade por classe, deve-se também procurar encapsular apenas um interesse trans-versal por aspecto.

As Listagens 1 e 2 apresentaram exemplos de adendos em que o conjunto de junção é declarado junto com adendo. Dentro de um aspecto, também é possível declarar o conjunto de junção separa-damente e referenciá-lo em outros pontos do aspecto. A Listagem 3 apresenta um código equivalente ao da Listagem 1, em que o conjunto de junção é declarado separadamente e referenciado nos adendos.

Essa declaração separada do conjunto de junção permite o reapro-veitamento da definição em vários pontos do aspecto. Isso tam-bém possibilita a declaração do conjunto de junção como abstra-to, de forma que ele só seja definido em um aspecto que estenda o original. Isso permite que um aspecto abstrato seja especializado por uma aplicação para agir apenas nos pontos desejados. Assim funcionam os frameworks orientados a aspectos!

Listagem 3. Exemplo de definição do conjunto de junção separado dos adendos.

@Aspect

public class AspectoExemplo {

@Pointcut(“execution(* br.mundoj.*.*(..))”)

public void pacoteMundoj(){}

@Before(“pacoteMundoj()”)

public void imprimirConsoleAntes(JoinPoint jp) {

System.out.println(“Iniciando “ + jp.getSignature() + “ as “

+ System.currentTimeMillis());

}

@After(“pacoteMundoj()”)

public void imprimirConsoleDepois(JoinPoint jp) {

System.out.println(“Finalizando “ + jp.getSignature() + “ as “

+ System.currentTimeMillis());

}

}

Uma característica importante de um aspecto é a inconsci-ência (obliviousness) da classe em relação à sua existência. A classe que está sendo interceptada pelo aspecto não precisa e não deve ter nenhum indício do interesse transversal modu-larizado por ele. Essa inconsciência da classe é o que permite a real separação de interesses na aplicação.

Combinação (weaving)

Talvez os leitores que criaram e executaram os exemplos apre-sentados até agora estejam se perguntando: “Mas como isso tudo funciona?”. É na combinação, ou weaving, que toda a mágica acontece! É nesse ponto que as classes e os aspectos são combi-nados, formando uma aplicação resultante da inserção dos inte-resses transversais dos aspectos nas classes. A figura 6 representa

graficamente esse conceito. A palavra weaving pode ser traduzida diretamente para a palavra entrelaçar, que passa a ideia de fios sendo combinados de forma ortogonal. Isso é uma metáfora para a combinação que existe entre os interesses das classes e dos as-pectos, que são transversais uns aos outros.

Figura 6. A combinação entre classes e aspectos.

Se você chegou a executar os exemplos dentro de um projeto de aspectos dentro do Eclipse, a combinação aconteceu em tempo de compilação. Nesse caso, o AspectJ utiliza um compilador especial para transformar o código-fonte em bytecode já afetado pelo o comportamento dos aspectos. Essa alternativa é adequada quando se tem controle sobre todo o código-fonte no qual se deseja que os aspectos ajam.

Outra opção é atuar direto no bytecode compilado, ou seja, nos arquivos .class gerados na compilação dos arquivos .java. Isso pode ser feito estaticamente incluindo nos passos de construção da aplicação, a configuração para que a combinação atue depois da compilação nos arquivos desejados. Outra alternativa é fazer a combinação no momento do carregamento das classes. Dessa forma, configura-se um agente na máquina virtual que intercepta o carregamento das classes e faz as transformações adequadas no bytecode das classes. A combinação em cima do bytecode é adequada quando o aspecto deve também atuar em classes das quais não se possui o código-fonte. Quando o aspecto deve atuar em classes geradas em tempo de execução, a combinação no car-regamento é certamente a melhor alternativa.

Ainda existe uma terceira opção que é a combinação feita em tem-po de execução. O Spring suporta a combinação feita em tempo de execução com a sintaxe de anotações do AspectJ. São utilizados proxies dinâmicos para suportar essa funcionalidade e apenas os objetos que são criados pelo Spring podem ser afetados pelos aspectos. Além disso ainda existem outras limitações em termos de funcionalidades: vários pontos de junção não são suportados, alguns critérios para definição de conjuntos de junção não funcio-nam e grande parte das transformações de declarações intertipos também não podem ser utilizadas.

A grande vantagem dessa combinação em tempo de execução é sua integração com um framework extremamente popular como o Spring. Isso facilita a introdução do conceito da orientação a aspectos em projetos que utilizem o Spring sem a alteração do ambiente de desenvolvimento ou da arquitetura da aplicação. Talvez essa seja uma excelente alternativa para empresas que têm receio na adoção de novas abordagens e para os desenvolvedores irem se acostumando aos poucos com o conceito.

64

: : www.mundoj.com.br : :

Recomendo a todos que estiverem iniciando com aspectos que dêem seus primeiros passos com a combinação em tem-po de compilação através do plugin do AspectJ para Eclipse. Talvez porque o AspectJ é mantido pela própria Eclipse Foundation, o plugin para o desenvolvimento de aspectos é excelente. Além de compilar e executar uma aplicação com aspectos de forma totalmente transparente, o plugin ainda possui outros recursos, como a validação das expressões de ponto de junção e a visualização dos pontos onde os aspec-tos irão atuar.

Existem vários detalhes a respeito da combinação que certamen-te devem ser do conhecimento de um arquiteto que for adotar aspectos em um projeto sob sua responsabilidade, porém esses detalhes fogem ao escopo deste artigo. Espera-se que nesse ponto o leitor entenda que precisa existir essa etapa em que os aspectos são combinados com as classes e que essa combinação pode ser feita em diversos momentos.

Exemplo de uso de aspectos

Como parte final do artigo, será mostrado um exemplo simples de um aspecto que utiliza os conceitos apresentados neste artigo. Vou fugir do exemplo básico para a orientação a aspectos que são as funcionalidades de registros de auditoria (que foi inclu-sive usada nos exemplos da seção anterior). O artigo também irá fugir de exemplos mais complexos, que envolvem reflexão e funcionalidades mais avançadas do AspectJ. Será mostrado um aspecto que intercepta a chamada de um método e o executa de forma assíncrona, ou seja, em uma thread diferente. A ideia desse aspecto é encapsular a criação de uma nova thread na execução de um método.

O primeiro passo é a definição de qual será o conjunto de junção no qual o aspecto deverá agir. Inicialmente deseja-se que o aspec-to aja somente em classes da aplicação, que aqui no exemplo estão sempre dentro do pacote “br.mundoj”. Outro fator importante diz respeito ao retorno do método, que não faz sentido ser diferente de “void”. Se o método irá executar em paralelo com quem o chamou, ele não pode retornar um valor do qual depende a exe-cução da classe cliente. Apesar da restrição de pacote e retorno, nem todos os métodos com essa assinatura devem ser executados assincronamente.

Uma opção seria utilizar um padrão de nomenclatura para os métodos que precisam ser assíncronos. Outra opção, que será utilizada no exemplo, é criar uma anotação para marcar os méto-dos onde o aspecto deve agir. A Listagem 4 mostra a definição da anotação @Asynchronous.

Outro ponto importante é definir o tipo de adendo que será uti-lizado. O principal ponto a ser considerado nesse caso é que será preciso interferir na execução do método, criando uma thread e invocando-o dentro dela. Dessa forma, adendos do tipo After e Before não são adequados, restando o adendo do tipo Around que pode assumir o controle da execução do método. A Listagem 5 apresenta o código do aspecto descrito.

Listagem 5. Aspecto para definição assíncrona de métodos.

@Aspect

public class AsyncExecutionAspect {

@Around(“execution(@br.mundoj.Asynchronous void br.mundoj.*.*(..))”)

public void executeInNewThread(final ProceedingJoinPoint jp){

new Thread(

new Runnable() {

@Override

public void run() {

try {

jp.proceed();

} catch (Throwable e) {

e.printStackTrace();

}

}

}

).start();

}

}

65

Listagem 6. Classe para verificar o comportamento do aspecto assíncrono.

public class TesteAsync {

@Asynchronous

public void asyncMethod(){

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

public void syncMethod(){

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

public static void main(String[] args){

TesteAsync teste = new TesteAsync();

teste.asyncMethod();

teste.syncMethod();

}

}

Após olhar o código com cuidado, talvez o leitor esteja se per-guntando como vamos saber qual método executou antes ou depois. Uma opção seria adicionar no início e no final de cada método uma chamada para imprimir no console o momento que o método executou. Porém, como este é um artigo sobre aspectos, ficaria muito feio não modularizarmos um interesse transversal como esse. Inclusive vamos utilizar o aspecto já pronto que foi apresentado na Listagem 2 (sem nenhum alteração). Ao executar-mos a aplicação com os dois aspectos, obtermos a seguinte saída no console:

Listagem 4. Anotação para marcar os métodos que devem ser assíncronos.

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

@Documented

public @interface Asynchronous {

}

A classe da Listagem 6 foi criada para verificarmos a execução do aspecto. Nessa classe temos o método asyncMethod(), que possui a anotação para ser executado de forma assíncrona, e o método syncMethod(), que não deve ser interceptado pelo aspecto. É utilizada uma chamada a Thread.sleep() para simular a execução de uma tarefa demorada em ambos. O método main() cria uma instância da classe e executa primeiro asyncMethod() e em segui-da syncMethod(). O comportamento esperado é que o método assíncrono inicie sua execução e retorne o controle para o método main(). Em seguida, o método síncrono irá executar na mesma thread do método main(), que ficará aguardando. Sendo assim, como a execução de asyncMethod() demora 2s e a de syncMe-thod() apenas 1s, a execução do método assíncrono deve terminar depois do término do método main().

66

: : www.mundoj.com.br : :

Listagem 7. Definição da precedência entre os aspectos.

@Aspect

@DeclarePrecedence(“br.mundoj.AsyncExecutionAspect,

br.mundoj.AspectoExemplo”)

public class Precendence {}

Iniciando void br.mundoj.TesteAsync.main(String[]) as 1296322243562

Iniciando void br.mundoj.TesteAsync.asyncMethod() as 1296322243564

Finalizando void br.mundoj.TesteAsync.asyncMethod() as 1296322243567

Iniciando void br.mundoj.TesteAsync.syncMethod() as 1296322243567

Finalizando void br.mundoj.TesteAsync.syncMethod() as 1296322244567

Finalizando void br.mundoj.TesteAsync.main(String[]) as 1296322244567

Iniciando void br.mundoj.TesteAsync.main(String[]) as 1296321274330

Iniciando void br.mundoj.TesteAsync.syncMethod() as 1296321274333

Iniciando void br.mundoj.TesteAsync.asyncMethod() as 1296321274333

Finalizando void br.mundoj.TesteAsync.syncMethod() as 1296321275333

Finalizando void br.mundoj.TesteAsync.main(String[]) as 1296321275333

Finalizando void br.mundoj.TesteAsync.asyncMethod() as 1296321276333

Ao analisarmos a saída do console podemos observar que o resul-tado não é bem o que esperávamos, pois queríamos ver a finali-zação de asyncMethod() depois do término do método main(). Se observarmos o tempo de início e final do método asyncMethod(), veremos que ele executou de forma muito rápida. Como sabe-mos que sua execução deveria demorar pelo menos 2s, pode-se concluir que seu conteúdo está sendo executado em uma thread separada.

Na verdade, o que causou o comportamento inesperado foi o fato do aspecto que imprime no console executar antes do aspecto que inicia uma nova thread. Como inverter isso? Aproveito a opor-tunidade para introduzir o conceito de precedência de aspectos, que na verdade é uma forma de definir qual deve ser a ordem de execução deles. Como a precedência precisa referenciar os dois aspectos, normalmente opto por defini-la em um aspecto a parte.

Para definir essa precedência deve-se utilizar a anotação @Decla-rePrecedence, passando como parâmetro uma string que contém os nomes dos aspectos separados por vírgula, na ordem em que devem ser executados. A Listagem 7 apresenta a definição da pre-cedência para o exemplo, configurando o AsyncExecutionAspect para executar antes do AspectoExemplo.

Após a definição da precedência, a saída do console é alterada. Agora se pode observar que a finalização do método asyncMe-thod() ocorre depois do final do método main(), conforme era esperado. Segue a nova saída do console:

aspectj/

-

Referências

Para Saber Mais

Na edição 29, o artigo “Modelando Transações: de Facade a AspectJ” apresentou uma alternativa para gerenciamen-to de transações utilizando aspectos.

Na edição 30, o artigo “Explorando a Manipulação de Bytecode com o ASM” apresenta como o bytecode de uma classe pode ser manipulado estaticamente e no momento de seu carregamento. A partir deste artigo é possível en-tender melhor algumas técnicas que o AspectJ utiliza para fazer a combinação (weaving).

Na edição 32, o artigo “Proxies Estáticos e Dinâmicos” apresentou o uso de aspectos como uma alternativa a criação de proxies em aplicações.

Considerações finais

Este artigo teve como objetivo apresentar o paradigma de pro-gramação orientado a aspectos. O artigo não teve a ambição de se aprofundar no assunto nem nas funcionalidades do AspectJ. Caso o leitor tenha compreendido os conceitos principais desse paradigma, diria que o objetivo foi alcançado. Além disso, ainda

GUJ – Discussões sobre o tema do artigo e assuntos relacionados

Discuta este artigo com 100 mil outros desenvolvedores em www.guj.com.br/MundoJ

foi dada uma introdução à sintaxe baseada em anotações do As-pectJ e mostrado como os conceitos desse novo paradigma são implementados. Fechando o artigo, foi mostrado um exemplo no qual ocorre não somente a interação entre classes e aspectos, como também entre aspectos.

A maior dificuldade na utilização da orientação a aspectos não está na utilização dos frameworks ou ferramentas, mas sim na identificação dos cenários onde os aspectos devem ser utilizados. É preciso mudar a forma de pensar e adicionar mais uma dimen-são na modularização da aplicação para que seja possível separar os interesses transversais. Hoje, o AspectJ é uma implementação muito madura da orientação a aspectos e certamente não traria riscos tecnológicos para uma aplicação que o utilizasse. O que está faltando amadurecer são as pessoas!

E você? Já está preparado para o futuro?