Introdução ao AspectJ
*1. Introdução *
Este artigo é uma* introdução rápida e prática ao AspectJ. *
Primeiro, mostraremos como habilitar a programação orientada a aspectos e, em seguida, focaremos na diferença entre a tecelagem em tempo de compilação, pós-compilação e tempo de carregamento.
Vamos começar com uma breve introdução à programação orientada a aspectos (AOP) e noções básicas do AspectJ.
===* 2. Visão geral *
AOP é um paradigma de programação que visa aumentar a modularidade, permitindo a separação de preocupações transversais. Isso é feito adicionando comportamento adicional ao código existente sem modificação do próprio código. Em vez disso, declaramos separadamente qual código deve modificar.
AspectJ implementa preocupações e a tecelagem de interesses transversais usando extensões da linguagem de programação Java.
===* 3. Dependências do Maven *
O AspectJ oferece bibliotecas diferentes, dependendo de seu uso. Nós podemos encontrar dependências do Maven no grupo org.aspectj no repositório do Maven Central.
Neste artigo, focamos nas dependências necessárias para criar aspectos e Weaver usando os Weavers em tempo de compilação, pós-compilação e tempo de carregamento.
====* 3.1 AspectJ Runtime *
Ao executar um programa AspectJ, o caminho da classe deve conter as classes e os aspectos junto com a biblioteca de tempo de execução do AspectJ _aspectjrt.jar: _
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
Essa dependência está disponível em Maven Central.
====* 3.2 AspectJWeaver *
Além da dependência de tempo de execução do AspectJ, também precisamos incluir o aspectjweaver.jar para apresentar conselhos à classe Java no tempo de carregamento:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
A dependência também está disponível em Maven Central.
===* 4. Criação de aspecto *
O AspectJ fornece uma implementação de AOP e possui* três conceitos principais: *
-
Ponto de junção
-
Pointcut
-
Adendo
Demonstraremos esses conceitos criando um programa simples para validar o saldo de uma conta de usuário.
Primeiro, vamos criar uma classe Account com um determinado saldo e um método para retirar:
public class Account {
int balance = 20;
public boolean withdraw(int amount) {
if (balance < amount) {
return false;
}
balance = balance - amount;
return true;
}
}
Criaremos um arquivo AccountAspect.aj para registrar informações da conta e validar o saldo da conta (observe que os arquivos AspectJ terminam com uma extensão de arquivo “.aj”):
public aspect AccountAspect {
final int MIN_BALANCE = 10;
pointcut callWithDraw(int amount, Account acc) :
call(boolean Account.withdraw(int)) && args(amount) && target(acc);
before(int amount, Account acc) : callWithDraw(amount, acc) {
}
boolean around(int amount, Account acc) :
callWithDraw(amount, acc) {
if (acc.balance < amount) {
return false;
}
return proceed(amount, acc);
}
after(int amount, Account balance) : callWithDraw(amount, balance) {
}
}
Como podemos ver, adicionamos um pointcut ao método retirada e criamos três visos que se referem ao pointcut definido.
Para entender o seguinte, apresentamos as seguintes definições:
-
Aspecto : uma modularização de uma preocupação que atravessa vários objetos. Cada aspecto se concentra em uma funcionalidade específica de corte transversal
-
Ponto de junção : um ponto durante a execução de um script, como a execução de um método ou acesso à propriedade
-
Conselho : ação executada por um aspecto em um ponto de junção específico
-
Pointcut : uma expressão regular que corresponde aos pontos de junção. Um aviso é associado a uma expressão pointcut e é executado em qualquer ponto de junção que corresponda ao pointcut
Para mais detalhes sobre esses conceitos e suas semânticas específicas, consulte o seguinte link.
Em seguida, precisamos tecer os aspectos em nosso código. As seções abaixo abordam três tipos diferentes de tecelagem: tecelagem em tempo de compilação, tecelagem pós-compilação e tecelagem em tempo de carga no AspectJ.
*5. Tecelagem em tempo de compilação *
A abordagem mais simples da tecelagem é a tecelagem em tempo de compilação. Quando temos o código-fonte do aspecto e o código em que estamos usando os aspectos, o compilador AspectJ compila a partir da origem e produz um arquivo de classe tecida como saída. Posteriormente, após a execução do seu código, a classe de saída do processo de tecelagem é carregada na JVM como uma classe Java normal.
Podemos fazer o download das Ferramentas de desenvolvimento do AspectJ, pois inclui um compilador AspectJ incluído. Um dos recursos mais importantes do AJDT é uma ferramenta para a visualização de preocupações de corte transversal, que é útil para depurar uma especificação de corte de ponto. Podemos visualizar o efeito combinado mesmo antes da implantação do código.
Usamos o AspectJ Maven Plugin do Mojo para integrar aspectos do AspectJ em nossas classes usando o compilador AspectJ.
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8 </encoding>
</configuration>
<executions>
<execution>
<goals>
<!-- use this goal to weave all your main classes -->
<goal>compile</goal>
<!-- use this goal to weave all your test classes -->
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
Para obter mais detalhes sobre a referência de opção do compilador AspectJ, consulte o seguinte link.
Vamos adicionar alguns casos de teste para nossa classe de conta:
public class AccountTest {
private Account account;
@Before
public void before() {
account = new Account();
}
@Test
public void given20AndMin10_whenWithdraw5_thenSuccess() {
assertTrue(account.withdraw(5));
}
@Test
public void given20AndMin10_whenWithdraw100_thenFail() {
assertFalse(account.withdraw(100));
}
}
Quando executamos os casos de teste, o texto abaixo que é mostrado no console significa que tecemos com sucesso o código fonte:
[INFO] Join point 'method-call
(boolean com..aspectj.Account.withdraw(int))' in Type
'com..aspectj.test.AccountTest' (AccountTest.java:20)
advised by around advice from 'com..aspectj.AccountAspect'
(AccountAspect.class:18(from AccountAspect.aj))
[INFO] Join point 'method-call
(boolean com..aspectj.Account.withdraw(int))' in Type
'com..aspectj.test.AccountTest' (AccountTest.java:20)
advised by before advice from 'com..aspectj.AccountAspect'
(AccountAspect.class:13(from AccountAspect.aj))
[INFO] Join point 'method-call
(boolean com..aspectj.Account.withdraw(int))' in Type
'com..aspectj.test.AccountTest' (AccountTest.java:20)
advised by after advice from 'com..aspectj.AccountAspect'
(AccountAspect.class:26(from AccountAspect.aj))
2016-11-15 22:53:51 [main] INFO com..aspectj.AccountAspect
- Balance before withdrawal: 20
2016-11-15 22:53:51 [main] INFO com..aspectj.AccountAspect
- Withdraw ammout: 5
2016-11-15 22:53:51 [main] INFO com..aspectj.AccountAspect
- Balance after withdrawal : 15
2016-11-15 22:53:51 [main] INFO com..aspectj.AccountAspect
- Balance before withdrawal: 20
2016-11-15 22:53:51 [main] INFO com..aspectj.AccountAspect
- Withdraw ammout: 100
2016-11-15 22:53:51 [main] INFO com..aspectj.AccountAspect
- Withdrawal Rejected!
2016-11-15 22:53:51 [main] INFO com..aspectj.AccountAspect
- Balance after withdrawal : 20
===* 6. Tecelagem pós-compilação *
A tecelagem pós-compilação (também chamada de tecelagem binária) é usada para tecer arquivos de classe e arquivos JAR existentes. Como na tecelagem em tempo de compilação, os aspectos usados para tecer podem estar na forma de origem ou binária e podem ser tecidos por aspectos.
Para fazer isso com o AspectJ Maven Plugin do Mojo, precisamos configurar todos os arquivos JAR que gostaríamos de tecer na configuração do plugin:
<configuration>
<weaveDependencies>
<weaveDependency>
<groupId>org.agroup</groupId>
<artifactId>to-weave</artifactId>
</weaveDependency>
<weaveDependency>
<groupId>org.anothergroup</groupId>
<artifactId>gen</artifactId>
</weaveDependency>
</weaveDependencies>
</configuration>
Os arquivos JAR que contêm as classes a serem tecidas devem ser listados como + <dependências/> +
no projeto Maven e listados como + <weaveDependencies/> +
no + <configuração> +
do AspectJ Maven Plugin.
===* 7. Tecelagem em tempo de carga *
A tecelagem em tempo de carregamento é simplesmente tecelagem binária adiada até o ponto em que um carregador de classes carrega um arquivo de classe e define a classe para a JVM.
Para suportar isso, são necessários um ou mais "carregadores de classe de tecelagem". Elas são fornecidas explicitamente pelo ambiente de tempo de execução ou ativadas usando um "agente de tecelagem".
====* 7.1 Habilitando a tecelagem em tempo de carregamento *
A tecelagem do tempo de carregamento do AspectJ pode ser ativada usando o agente AspectJ que pode se envolver no processo de carregamento da classe e tecer qualquer tipo antes de serem definidos na VM. Especificamos a opção javaagent como JVM ` + -javaagent: pathto/aspectjweaver.jar + ` ou usando o plug-in Maven para configurar o javaagent:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.10</version>
<configuration>
<argLine>
-javaagent:"${settings.localRepository}"/org/aspectj/
aspectjweaver/${aspectj.version}/
aspectjweaver-${aspectj.version}.jar
</argLine>
<useSystemClassLoader>true</useSystemClassLoader>
<forkMode>always</forkMode>
</configuration>
</plugin>
====* 7.2 Tecelão de configuração *
O agente de tecelagem em tempo de carregamento do AspectJ é configurado pelo uso de arquivos aop.xml. Ele procura um ou mais arquivos aop.xml no caminho de classe no diretório META-INF e agrega o conteúdo para determinar a configuração do tecelão.
Um arquivo aop.xml contém duas seções principais:
-
Aspectos : define um ou mais aspectos do tecelão e controla quais aspectos devem ser utilizados no processo de tecelagem. O elemento aspects pode opcionalmente conter um ou mais elementos include e exclude (por padrão, todos os aspectos definidos são usados para tecer)
-
Tecelão : define as opções de tecelão para o tecelão e especifica o conjunto de tipos que devem ser tecidos. Se nenhum elemento de inclusão for especificado, todos os tipos visíveis ao tecelão serão tecidos
Vamos configurar um aspecto para o tecelão:
<aspectj>
<aspects>
<aspect name="com..aspectj.AccountAspect"/>
<weaver options="-verbose -showWeaveInfo">
<include within="com..aspectj.*"/>
</weaver>
</aspects>
</aspectj>
Como podemos ver, configuramos um aspecto que aponta para AccountAspect, e apenas o código-fonte no pacote com..aspectj será tecido pelo AspectJ.
*8. Anotando aspectos *
Além do estilo familiar de declaração de aspecto baseado em código AspectJ, o AspectJ 5 também suporta um estilo de declaração de aspecto baseado em anotação. Chamamos informalmente o conjunto de anotações que suportam esse estilo de desenvolvimento de anotações “_ @ AspectJ_”.
Vamos criar uma anotação:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Secured {
public boolean isLocked() default false;
}
Usamos a anotação _ @ Secured_ para ativar ou desativar um método:
public class SecuredMethod {
@Secured(isLocked = true)
public void lockedMethod() {
}
@Secured(isLocked = false)
public void unlockedMethod() {
}
}
Em seguida, adicionamos um aspecto usando o estilo de anotação AspectJ e verificamos a permissão com base no atributo da anotação @Secured:
@Aspect
public class SecuredMethodAspect {
@Pointcut("@annotation(secured)")
public void callAt(Secured secured) {
}
@Around("callAt(secured)")
public Object around(ProceedingJoinPoint pjp,
Secured secured) throws Throwable {
return secured.isLocked() ? null : pjp.proceed();
}
}
Para mais detalhes sobre o estilo de anotação AspectJ, podemos verificar o seguinte link://eclipse.org/aspectj/doc/released/adk15notebook/annotations-aspectmembers.html [link].
Em seguida, tecemos nossa classe e aspecto usando o tecelão de tempo de carregamento e colocamos aop.xml na pasta META-INF:
<aspectj>
<aspects>
<aspect name="com..aspectj.SecuredMethodAspect"/>
<weaver options="-verbose -showWeaveInfo">
<include within="com..aspectj.*"/>
</weaver>
</aspects>
</aspectj>
Por fim, adicionamos teste de unidade e verificamos o resultado:
@Test
public void testMethod() throws Exception {
SecuredMethod service = new SecuredMethod();
service.unlockedMethod();
service.lockedMethod();
}
Quando executamos os casos de teste, podemos verificar a saída do console para verificar se tecemos com sucesso nosso aspecto e classe no código-fonte:
[INFO] Join point 'method-call
(void com..aspectj.SecuredMethod.unlockedMethod())'
in Type 'com..aspectj.test.SecuredMethodTest'
(SecuredMethodTest.java:11)
advised by around advice from 'com..aspectj.SecuredMethodAspect'
(SecuredMethodAspect.class(from SecuredMethodAspect.java))
2016-11-15 22:53:51 [main] INFO com..aspectj.SecuredMethod
- unlockedMethod
2016-11-15 22:53:51 [main] INFO c.b.aspectj.SecuredMethodAspect -
public void com..aspectj.SecuredMethod.lockedMethod() is locked
9. Conclusão
Neste artigo, abordamos conceitos introdutórios sobre o AspectJ. Para obter detalhes, consulte a AspectJ página inicial.
Você pode encontrar o código fonte deste artigo over no GitHub.