A Programação Orientada a Aspectos (AOP) é um paradigma que complementa a programação orientada a objetos, permitindo modularizar comportamentos transversais — como logging, controle de transações ou métricas de desempenho — sem alterar o código-fonte dos métodos originais. O Spring Framework oferece suporte nativo ao AOP, promovendo soluções não invasivas e altamente reutilizáveis.
Cenários Práticos do AOP
- Medição de tempo de execução de métodos
- Gerenciamento automático de transações
- Controle de acesso baseado em permissões
- Registro de auditoria e logs operacionais
Vantagens da Utilização do AOP no Spring
- Redução de código repetitivo: Evita a necessidade de criar proxies manualmente.
- Aplicação em massa: Um único aspecto pode ser aplicado a múltiplos beans simultaneamente.
- Integração nativa: Funcionalidades como transações são implementadas internamente via AOP.
Exemplo Prático: Monitoramento de Execução de Métodos
Para demonstrar o uso básico do AOP, vamos criar um cenário onde o tempo de execução de um método é registrado automaticamente.
Passo 1: Dependências Necessárias
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
Passo 2: Interface e Implementação do Serviço
public interface ProductService {
void register();
void updateStock();
}
@Repository
public class ProductServiceImpl implements ProductService {
@Override
public void register() {
System.out.println("Produto registrado.");
}
@Override
public void updateStock() {
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
System.out.println("Estoque atualizado.");
}
}
Passo 3: Classe de Notificação com Lógica Transversal
@Component
@Aspect
public class PerformanceMonitor {
@Pointcut("execution(* com.exemplo.service.ProductService.updateStock())")
private void monitorTarget() {}
@Around("monitorTarget()")
public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.nanoTime();
Object result = joinPoint.proceed();
long duration = System.nanoTime() - start;
System.out.println("Método " + joinPoint.getSignature().getName() +
" executado em " + duration / 1_000_000 + "ms");
return result;
}
}
Passo 4: Configuração do Contexto Spring
@Configuration
@ComponentScan("com.exemplo")
@EnableAspectJAutoProxy
public class AppConfig { }
Passo 5: Execução e Validação
public class MainApp {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
ProductService service = ctx.getBean(ProductService.class);
service.updateStock();
System.out.println("Classe real do bean: " + service.getClass().getName());
}
}
Conceitos Fundamentais do AOP
- Ponto de Junção (Join Point): Qualquer ponto de execução no programa, como chamadas de método.
- Ponto de Corte (Pointcut): Expressão que define quais pontos de junção serão interceptados.
- Notificação (Advice): Ação executada em determinado ponto de corte (antes, depois, em torno, etc.).
- Aspecto (Aspect): Módulo que combina ponto de corte e notificações.
- Objeto Alvo (Target): Objeto original antes da aplicação do proxy.
- Proxy: Objeto gerado dinamicamente pelo Spring para aplicar os aspectos.
Sintaxe de Expressões de Ponto de Corte
O formato padrão é: execution(modificadores? tipoRetorno pacote.Classe.método(parâmetros) exceções?)
Exemplos com Wildcards
// Qualquer método que comece com 'find' em qualquer subpacote de 'service'
execution(* com.exemplo.service..*.*find*(..))
// Qualquer classe que termine com 'Service' e seus métodos
execution(* *..*Service.*(..))
Tipos de Notificação
- @Before: Executa antes do método alvo.
- @After: Executa após o método, independentemente de exceções.
- @AfterReturning: Executa somente se o método retornar normalmente.
- @AfterThrowing: Executa somente se o método lançar uma exceção.
- @Around (mais poderoso): Envolve completamente o método, permitindo controle total sobre sua execução.
Exemplo de Notificação Circundante (@Around)
@Around("execution(* com.exemplo.service.*.*(..))")
public Object logAndMeasure(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("[INÍCIO] " + pjp.getSignature().toShortString());
long inicio = System.currentTimeMillis();
Object resultado = pjp.proceed(); // Invoca o método original
long duracao = System.currentTimeMillis() - inicio;
System.out.println("[FIM] Duração: " + duracao + "ms");
return resultado;
}
Notificações do tipo @Around exigem o parâmetro ProceedingJoinPoint e devem chamar pjp.proceed() para continuar a execução do método original. O retorno deve ser compatível com o tipo esperado pelo chamador.