Domine a Programação Orientada a Aspectos com Spring AOP

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.

Tags: Spring AOP java SpringFramework AspectJ

Publicado em 8-20 02:53