Em 1997, no Xerox Palo Alto Research Center — o mesmo PARC que havia inventado a interface gráfica, o Ethernet e a impressora laser uma geração antes —, um grupo liderado por Gregor Kiczales publicou um paper na conferência ECOOP que propunha uma resposta sistemática ao problema descrito no conceito anterior. O título era Aspect-Oriented Programming, e os co-autores eram uma constelação que ainda hoje aparece em livros: John Lamping, Anurag Mendhekar, Chris Maeda, Cristina Lopes, Jean-Marc Loingtier e John Irwin. Cristina Lopes, brasileira radicada em Portugal antes da UC Irvine, escreveu uma das primeiras teses de doutorado sobre AOP no mesmo período.
A intuição central do paper é precisa: orientação a objetos decompôs sistemas em módulos verticais (classes, hierarquias, pacotes) que organizam bem o código que tem topologia hierárquica, mas falham nos concerns que atravessam a hierarquia. Esses concerns precisam de uma nova unidade de modularização — não classe, não método, não pacote, mas aspect. Um aspect captura, em um lugar único, o comportamento de um cross-cutting concern e a especificação de onde esse comportamento deve aparecer no resto do sistema. Uma ferramenta de weaving (tecimento) recombina os aspects com o código base no momento certo: compilação, carga ou execução.
Vinte e nove anos depois, AspectJ ainda existe como projeto Eclipse e tem usuários — mas ninguém mais discute "AOP versus OOP" como debate quente. O que aconteceu não foi fracasso, e sim absorção. A maior parte das ideias do paper de 1997 entrou em frameworks e linguagens sob outros nomes. Middleware HTTP, interceptors do gRPC, behaviors do MediatR, decorators do Python, source generators do .NET, geração de código do Go — todos descendem do vocabulário de Kiczales mesmo quando seus autores não o citam. Saber o original ajuda a reconhecer a mesma estrutura nos seus muitos descendentes.
Este conceito é deliberadamente teórico. Os conceitos seguintes voltam ao chão de fábrica — middleware, decorator, proxy dinâmico — mas vale primeiro entender o vocabulário canônico: sem ele, fica difícil ler literatura, comparar frameworks, ou conduzir uma discussão de design sem cair em jargão de ferramenta específica.
O vocabulário central — quatro termos que organizam tudo
O paper define quatro termos que viraram léxico padrão. Cada framework moderno usa nomes ligeiramente diferentes, mas a estrutura conceitual é a mesma — saber traduzir é metade do caminho.
Join point — ponto de execução onde aspect pode interagir
Join point é qualquer ponto bem-definido na execução do programa onde, em princípio, código adicional poderia ser injetado. Em AspectJ — o sabor mais expressivo —, são join points: chamada de método, execução de método, leitura/escrita de campo, criação de objeto, captura de exceção, inicialização estática. Note a generalidade: não é só "antes/depois de método", é qualquer transição observável da execução. Em modelos mais restritos (Spring AOP, Castle DynamicProxy) os join points se reduzem essencialmente a chamadas de métodos públicos via proxy.
Pensar em join points é pensar em onde o sistema tem pontos de costura possíveis. Um sistema com muitos join points acessíveis dá liberdade — e magia — ao autor de aspect; um sistema com poucos é mais simples de raciocinar e debugar. A tensão entre liberdade e disciplina aparece nessa decisão.
Pointcut — predicado que seleciona join points
Pointcut é uma expressão que descreve quais join points interessam ao aspect. É a parte declarativa do vocabulário AOP, e onde frameworks variam mais em poder expressivo. Em AspectJ, pointcuts podem combinar predicados estruturais (nome do método, classe, pacote, assinatura) com predicados dinâmicos (estado da chamada, valor de argumento, contexto de chamada). Um pointcut clássico:
// AspectJ: todos os métodos públicos de qualquer classe em br.app.service
pointcut servicePublicMethods():
execution(public * br.app.service..*.*(..));
Em frameworks modernos baseados em proxy ou middleware, o
pointcut é mais simples — geralmente "métodos com tal atributo"
ou "todas as requisições HTTP que casam com tal rota". Spring AOP
tem AspectJ-style pointcuts em runtime. .NET ActionFilters tem
pointcut implícito ("todo endpoint deste controller"). FastAPI
Depends não tem pointcut como linguagem — você
menciona o aspect explicitamente em cada handler que quer
aplicá-lo.
Advice — o código que roda nos pontos selecionados
Advice é o código a executar quando um join point que casa com um pointcut acontece. AspectJ define cinco tipos de advice, e três deles dominam:
Before advice — executa antes do join point. Útil para validação de pré-condição, logging de entrada, verificação de autorização. Não pode alterar o que o join point faz; só pode lançar exceção para abortar.
After advice — executa depois do join
point. Subdivide-se em after returning (depois de
retorno bem-sucedido) e after throwing (depois de
exceção). Útil para logging de saída, métricas de duração,
registro de auditoria.
Around advice — o mais poderoso e o mais
perigoso. Envolve a chamada do join point com código antes e
depois, e decide se chama o método original (via
proceed()) ou substitui por outro comportamento.
É o tipo que viabiliza retry, cache, transação, circuit breaker
— qualquer concern que precise interceptar e potencialmente
mudar a chamada. É também o tipo onde mais erros de aspect
acontecem, porque esquecer proceed() faz o método
original simplesmente nunca executar.
Aspect — a unidade que reúne pointcuts e advices
Aspect é o módulo. Um aspect agrupa pointcuts e advices
relacionados em torno de um cross-cutting concern. O aspect de
logging tem pointcut "todos os métodos de domínio" e advice
"registrar entrada e saída". O aspect de autorização tem
pointcut "todos os métodos com [Authorize]" e
advice "verificar permissão antes de prosseguir".
A virtude central da abstração é localidade. Antes de aspects, a política de logging era infigurável — espalhada por todas as classes. Com aspect, a política existe em um arquivo: você pode ler, mudar, testar como unidade. O custo é a indireção: ler a classe de domínio não te diz mais o que vai acontecer quando seus métodos forem chamados, porque pode haver aspects casando.
Os três tipos de weaving
Weaving é o processo de combinar o código base com os aspects de modo que, em tempo de execução, os advices entrem em ação nos join points apropriados. Há três momentos onde isso pode acontecer, e cada um tem trade-offs distintos. A escolha do momento define quase tudo sobre o framework: poder, custo, facilidade de debug, suporte a join points dinâmicos.
Compile-time weaving
O compilador transforma o código base e injeta as chamadas aos
advices durante a compilação. AspectJ tem o seu próprio
compilador (ajc), que aceita Java + sintaxe de
aspect e gera bytecode "tecido". Vantagens: o código gerado é
otimizável como qualquer Java; performance idêntica a chamada
direta; aspects funcionam mesmo em código que nunca passa por
framework de runtime.
Desvantagens: exige toolchain especial; código tecido é difícil de debugar (você está na linha 14 de um aspect que foi injetado na linha 23 do método X); não dá para tecer em bibliotecas de terceiros sem recompilação. AspectJ compile-time foi forte em torno de 2003–2008 e perdeu tração à medida que o ecossistema preferiu opções menos invasivas.
Load-time weaving (LTW)
O bytecode é tecido na hora de o classloader carregar a classe.
Em Java, isso é feito via java agent que se conecta à
JVM com a flag -javaagent. AspectJ tem suporte
LTW; Hibernate usa LTW para enhancement de entidades; alguns
profilers (YourKit, JProfiler) usam o mesmo mecanismo.
Vantagem: pode tecer em qualquer classe que será carregada,
inclusive de bibliotecas, sem recompilação. Desvantagens:
startup mais lento (o agent inspeciona cada classe);
configuração de produção mais frágil (esquecer o
-javaagent faz aspects sumirem silenciosamente);
depuração ainda mais difícil. Mesmo dilema do compile-time, com
a vantagem de ser plug-and-play.
Runtime weaving via proxy
A escolha do mainstream moderno. Em vez de modificar bytecode, o framework gera em runtime um proxy dinâmico que embrulha o objeto original. As chamadas chegam ao proxy, ele executa os advices, e (se for around advice) delega para o objeto real. Spring AOP usa essa técnica desde 2003 (JDK Proxy para interfaces, CGLIB para classes); Castle DynamicProxy no .NET faz o mesmo desde antes; Python e Ruby fazem variantes via metaclasses.
Vantagem: nenhuma toolchain especial, debug normal (o stack
trace mostra o proxy, mas as linhas do método original são
reais), funciona com qualquer container de DI. Desvantagens
reais: só intercepta join points de chamada de método via
proxy — chamadas internas (this.outroMetodo())
passam direto, porque this é o objeto real, não o
proxy. Esse é um dos bugs mais comuns em AOP-via-proxy, e
Spring AOP convive com ele há vinte anos.
Source generation — o sucessor moderno
Não estava no paper original, mas merece menção porque é a
forma mais limpa em 2026 de obter os benefícios do compile-time
weaving sem AspectJ. Roslyn Source Generators (.NET 5+, 2020),
annotation processors em Java, macros em Rust, go
generate em Go — todos permitem que código adicional
seja gerado em tempo de compilação a partir de marcações no
código fonte. Microsoft.Extensions.Logging usa
source generator para gerar logging boilerplate de alta
performance; MediatR considera source generator para handlers.
A diferença para AspectJ é estética: a magia é explícita —
você vê o código gerado no arquivo gerado — em vez de oculta.
Um aspect concreto em AspectJ
Para sentir o vocabulário em código, vale ver um aspect AspectJ completo. O exemplo abaixo registra a duração de toda execução de método em pacotes de serviço, com around advice sendo a mecânica.
// br/app/aspects/PerformanceAspect.aj
package br.app.aspects;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Aspect
public class PerformanceAspect {
private static final Logger log = LoggerFactory.getLogger(PerformanceAspect.class);
// pointcut: execução de qualquer método em br.app.service.*
@Around("execution(* br.app.service..*.*(..))")
public Object measure(ProceedingJoinPoint pjp) throws Throwable {
long started = System.nanoTime();
try {
return pjp.proceed(); // chama o método original
} finally {
long durationMs = (System.nanoTime() - started) / 1_000_000;
String sig = pjp.getSignature().toShortString();
log.info("method={} duration_ms={}", sig, durationMs);
}
}
}
Cada elemento do vocabulário aparece no código: a anotação
@Aspect declara que a classe é um aspect; o
@Around("execution(...)") contém pointcut + tipo de
advice em uma única declaração; o método measure é
o advice; o pjp.proceed() chama o join point
original; a entrada e saída do try/finally
delimitam o "antes" e "depois" relativos ao join point.
Note o que não aparece: nenhum dos métodos do pacote
br.app.service precisa saber que o aspect existe.
Ali está a virtude da AOP — e também o seu risco. Um leitor que
olhe só para o serviço não vê o aspect; pode passar tempo
tentando entender por que há logs sendo emitidos sem que o
código local os emita.
O aspect roda mas não aparece. Em Spring AOP via proxy, se um
método chama outro do mesmo objeto via this.outro(),
o segundo não passa pelo proxy e nenhum advice atua nele. O
sintoma é "minha anotação @Transactional está
ali, mas a transação não abre". A causa é arquitetural: AOP
via proxy só intercepta o que entra pelo proxy. Solução comum:
injetar o serviço em si mesmo, ou refatorar para chamada
externa, ou usar AspectJ load-time/compile-time. Toda
biblioteca AOP-via-proxy tem essa pegadinha — saber que existe
já economiza horas.
O mesmo aspect em três sabores modernos
Para ver como AspectJ se traduz em ferramentas modernas, considere o mesmo concern — medir tempo de execução de operações de domínio — em C# (com Castle DynamicProxy), Python (com decorator) e Go (com middleware funcional).
using Castle.DynamicProxy;
public class PerformanceInterceptor : IInterceptor
{
private readonly ILogger _log;
public PerformanceInterceptor(ILogger<PerformanceInterceptor> log) => _log = log;
public void Intercept(IInvocation invocation)
{
var sw = Stopwatch.StartNew();
try
{
invocation.Proceed(); // equivalente a pjp.proceed()
}
finally
{
_log.LogInformation("method={Method} duration_ms={Ms}",
invocation.Method.Name, sw.ElapsedMilliseconds);
}
}
}
// registro (uma vez, em DI)
services.AddScoped<IPedidoService, PedidoService>();
services.AddSingleton<PerformanceInterceptor>();
// proxy gera-se automaticamente envolvendo PedidoService.
A classe IInterceptor equivale ao around advice
de AspectJ. O proxy gerado em runtime entrega cada chamada de
método público para o Intercept, que decide
chamar (ou não) invocation.Proceed(). Pointcut é
implícito: "todos os métodos do tipo registrado com
interceptor".
import time
import logging
from functools import wraps
log = logging.getLogger(__name__)
def measured(fn):
@wraps(fn)
async def wrapper(*args, **kwargs):
started = time.perf_counter()
try:
return await fn(*args, **kwargs)
finally:
elapsed_ms = (time.perf_counter() - started) * 1000
log.info(
"method=%s duration_ms=%.2f",
fn.__qualname__, elapsed_ms,
)
return wrapper
class PedidoService:
@measured
async def criar(self, cmd: CriarPedidoCmd) -> Pedido:
...
Decorator Python é around advice no formato funcional: você
recebe a função, retorna outra que envolve a original. Não há
pointcut declarativo — você marca cada função interessada com
@measured. É o caminho mais explícito,
intencionalmente — Python prefere magia visível.
package telemetry
import (
"context"
"log/slog"
"time"
)
// Measured embrulha qualquer função "use case" com medição.
func Measured[Req any, Res any](
name string,
fn func(context.Context, Req) (Res, error),
) func(context.Context, Req) (Res, error) {
return func(ctx context.Context, req Req) (Res, error) {
started := time.Now()
res, err := fn(ctx, req)
slog.InfoContext(ctx, "method executed",
"name", name,
"duration_ms", time.Since(started).Milliseconds(),
"ok", err == nil,
)
return res, err
}
}
// uso
criar := telemetry.Measured("pedido.criar", uc.CriarPedido)
pedido, err := criar(ctx, cmd)
Go não tem AOP no sentido de Kiczales — não tem framework de aspect mainstream, e a comunidade evita propositadamente. Em vez disso, escreve-se higher-order function que recebe a função "base" e retorna uma versão envolvida. Conceitualmente é o mesmo around advice, mas você compõe explicitamente em vez de declarar pointcut. Não há magia — e isso é a feature principal.
O que sobreviveu, o que foi descartado
AspectJ continua mantido em 2026 sob a Eclipse Foundation, mas raramente é a primeira escolha em projetos novos — mesmo em Java, onde nasceu. O que aconteceu no caminho merece análise, porque ajuda a entender por que frameworks modernos parecem-se tanto com AspectJ e tão diferentes.
Sobreviveu o vocabulário. Pointcut, advice,
aspect, weaving — esses termos estão em todo livro de AOP, em
documentação de Spring, em discussões de design. Mesmo que
ninguém escreva mais execution(* br.app..*.*(..))
no dia a dia, o modelo mental de "selecionar pontos
interceptáveis e injetar comportamento" segue válido.
Sobreviveu a ideia de modularizar concerns. Middleware no ASP.NET Core, behaviors do MediatR, interceptors do gRPC, decorators do Python, dependencies do FastAPI — todos são variações do conceito original. O que mudou foi a interface: em vez de linguagem dedicada de pointcut, frameworks usam composição explícita ou anotação no código.
Foram descartadas as linguagens de pointcut
complexas. AspectJ permite pointcuts surpreendentemente
poderosos — capturar todas as chamadas de qualquer método cujo
nome bate em padrão, em qualquer pacote, com qualquer assinatura,
filtrando por tipo de exception thrown. A consequência: quase
sempre que se escrevia pointcut sofisticado, ninguém entendia
mais o sistema. A frase "magia do AspectJ" virou xingamento na
comunidade, e novos frameworks evitam deliberadamente esse
poder. ASP.NET Core middleware vai onde você o coloca; FastAPI
Depends aparece onde você o menciona.
Foi descartada a interceptação de qualquer coisa. AspectJ pode interceptar leitura de campo, execução de loop, captura de exception. Frameworks modernos restringem deliberadamente os join points possíveis: middleware só atua entre request e handler; interceptor de método só atua antes/depois/em torno de chamada de método público. O custo de abrir mais join points é exponencial em complexidade de debug.
Quanto mais expressiva é a linguagem de pointcut, mais difícil é entender o sistema sem rodar. A heurística que veio dos anos 2000 e ainda vale: prefira aspects com pointcut explícito (anotação ou registro programático em DI) sobre aspects com pointcut declarativo amplo. Ler o método de domínio deve, na maioria dos casos, te dizer pelo menos quais aspects o afetam — pelos atributos, pela injeção, pelo registro. Quando precisa rodar o sistema para descobrir o que executa antes do método, o nível de magia ultrapassou o útil.
Por que AOP "puro" perdeu — e o que ganhou no lugar
Cristina Lopes escreveu em 2014 um ensaio retrospectivo chamado Aspect-Oriented Programming: A Historical Perspective, onde argumenta que AOP teve papel histórico claro: nomeou o problema dos cross-cutting concerns, desenvolveu o vocabulário canônico, e produziu ferramentas que provaram que era possível modularizar o que parecia impossível. Mas o sucesso comercial em massa não veio porque AspectJ pediu da indústria custos que ela não estava disposta a pagar — toolchain especial, debugger especial, mudança de mentalidade no time inteiro.
O que ganhou no lugar foi um ecossistema de soluções pontuais, cada uma resolvendo uma fatia bem-definida do problema sem o framework total. Spring AOP (proxy) para o caso 80%; AspectJ (compile-time) para os 20% que precisavam mais. ASP.NET Core middleware para HTTP; ActionFilters para controllers; behaviors MediatR para use cases. FastAPI Dependencies para HTTP; decorators para tudo o resto. Em Go, geração de código quando absolutamente preciso, função-que-retorna-função para o resto.
Esse padrão — "muitas ferramentas pequenas, cada uma na sua camada" — é o que se vê hoje em qualquer aplicação corporativa bem desenhada. Não é menos AOP do que AspectJ era; é AOP com escolhas mais conservadoras de poder, calibradas para que o time consiga ler o sistema sem ferramenta especial. Os conceitos seguintes do módulo destrincham cada uma dessas ferramentas no detalhe.
Por que importa para a sua carreira
Em 2026, quase ninguém vai te contratar para "escrever AspectJ". Mas em entrevista de design de sistema, quando aparece a pergunta "como você organizaria logging, autenticação, retry e transação numa aplicação grande?", a resposta forte usa o vocabulário de Kiczales mesmo que você nunca cite o paper: "esses concerns têm topologia cross-cutting; eu trataria cada um como aspect, mas escolheria para cada um a forma de weaving apropriada — middleware HTTP para os de borda, behavior de pipeline para os de aplicação, decorator/proxy para os de infraestrutura". Quem articula assim demonstra que entendeu o problema na profundidade certa, e sabe escolher ferramenta apropriada em vez de aplicar a mesma a tudo. Em revisões de código, o vocabulário ajuda a discutir aspects sem cair em "é assim que o framework funciona" — quando a equipe consegue dizer "esse pointcut está amplo demais, vamos restringir" ou "esse advice tem que ser around, não before", a discussão fica precisa.
Como praticar
- Tradução AspectJ → framework moderno. Pegue três aspects clássicos da literatura AspectJ — logging, cache e autorização. Para cada um, escreva: o pointcut em sintaxe AspectJ; o equivalente em Spring AOP via anotação; o equivalente em ASP.NET Core (middleware, ActionFilter, ou behavior MediatR — escolha o mais apropriado e justifique); o equivalente em FastAPI ou Go. Compare a forma como cada framework expressa "onde o aspect se aplica". Onde o framework esconde, onde explicita?
- Implementação around advice manual. Em cada uma das três linguagens (C#, Python, Go), escreva um wrapper de função que mede tempo de execução. Em C#, faça duas versões: uma com Castle DynamicProxy e outra com método de extensão funcional. Em Python, com decorator. Em Go, com higher-order function. Documente os trade-offs: legibilidade, magia, performance, debug. Esse exercício torna concreto o que cada estilo paga e ganha.
- Anatomia de um framework. Pegue um framework de AOP que você usa indiretamente (Spring AOP, ASP.NET Core middleware, MediatR behaviors, FastAPI Dependencies) e identifique no código fonte cada um dos quatro conceitos: onde está representado o "join point", onde está o "pointcut", onde está o "advice", onde acontece o "weaving". Em frameworks modernos os termos não aparecem com esses nomes — mas a estrutura é sempre a mesma. Esse é o exercício que consolida o vocabulário.
Referências para aprofundar
- paper Aspect-Oriented Programming — Gregor Kiczales, John Lamping, Anurag Mendhekar, Chris Maeda, Cristina Lopes, Jean-Marc Loingtier, John Irwin (ECOOP, 1997).
- paper An Overview of AspectJ — Gregor Kiczales, Erik Hilsdale, Jim Hugunin, Mik Kersten, Jeffrey Palm, William Griswold (ECOOP, 2001).
- livro AspectJ in Action (2ª ed.) — Ramnivas Laddad (Manning, 2009).
- livro Aspect-Oriented Software Development — Robert Filman, Tzilla Elrad, Siobhán Clarke, Mehmet Akşit (Addison-Wesley, 2004).
- livro Spring in Action (6ª ed.) — Craig Walls (Manning, 2022).
- artigo Aspect-Oriented Programming: A Historical Perspective — Cristina Videira Lopes (2014).
- artigo The History of AOP — Adrian Colyer (blog, 2003 e seguintes).
- paper Demarcation of Concerns — Awais Rashid & Alessandro Garcia (TAOSD, 2008).
- docs The AspectJ Programming Guide.
- docs Castle DynamicProxy Documentation.
- docs Roslyn Source Generators.
- vídeo AOP and AspectJ — Gregor Kiczales (palestra na OOPSLA, 2006).