Proxies Dinâmicos em Java
Crie implementações proxy de interfaces em tempo de execução em Java com java.lang.reflect.Proxy e InvocationHandler.
Um proxy dinâmico é um objeto que implementa uma ou mais interfaces, mas cujas chamadas de método são roteadas — em tempo de execução — através de um único handler que você escreve. A JVM sintetiza a classe proxy dinamicamente; você nunca escreve a implementação. Este é o canto mais poderoso do java.lang.reflect, e é como AOP, logging transparente, lazy loading, stubs RPC e bibliotecas de mock funcionam. Este capítulo mostra como Proxy.newProxyInstance e InvocationHandler se encaixam, e o que eles podem e não podem fazer.
As duas peças: Proxy e InvocationHandler
Um proxy dinâmico precisa de três entradas:
- Um class loader (onde definir a classe sintetizada).
- Um array de interfaces que o proxy implementará.
- Um
InvocationHandler— o único método que recebe cada chamada.
InvocationHandler handler = (proxy, method, args) -> {
// called for EVERY method invoked on the proxy
return ...; // becomes the method's return value
};
MyService svc = (MyService) Proxy.newProxyInstance(
MyService.class.getClassLoader(),
new Class<?>[]{ MyService.class },
handler);svc agora é um objeto real implementando MyService. Chamar svc.doThing(x) não executa nenhum corpo de doThing — não existe nenhum — chama handler.invoke(proxy, <Method doThing>, [x]). O handler decide o que fazer e o que retornar.
A assinatura de invoke
Object invoke(Object proxy, Method method, Object[] args) throws Throwableproxy— a própria instância do proxy (raramente usada; cuidado ao chamar métodos nela de dentro deinvoke, pois isso re-entra no handler e pode causar loop infinito).method— oMethodque foi chamado;method.getName(),method.getReturnType(), suas anotações, etc., estão todos disponíveis.args— os argumentos comoObject[](nullse o método não receber nenhum); primitivos são encapsulados em objetos.- retorno — o que o chamador deve receber; deve ser compatível com
method.getReturnType()ou você obterá umaClassCastException. Para um métodovoid, retornenull.
Um padrão frequente é encaminhar para um objeto "alvo" real: method.invoke(target, args) — envolvendo essa chamada com logging, temporização, transações ou tentativas repetidas. Essa chamada Method.invoke é o mesmo despacho reflexivo abordado em Java Reflection: Métodos; aqui ela é conduzida inteiramente pelo Method que a JVM entrega ao seu handler. Essa forma de encaminhamento é o idioma decorator via proxy, e é a base do Spring AOP.
Apenas interfaces
A maior restrição: java.lang.reflect.Proxy cria proxies apenas de interfaces, não de classes. Não é possível criar um proxy dinâmico de uma classe concreta com esta API. Se precisar criar proxy de uma classe, é necessário recorrer a uma biblioteca de bytecode (CGLIB, ByteBuddy) que gera uma subclasse — por isso frameworks as incluem. Para designs baseados em interfaces, o Proxy embutido é suficiente e não requer dependências.
A classe proxy sintetizada:
- Estende
java.lang.reflect.Proxye implementa suas interfaces. - Tem um nome gerado como
$Proxy0. - Roteia
equals,hashCodeetoString(os métodos deObject) porinvoketambém — portanto seu handler deve estar preparado para lidar com eles, ou delegá-los de forma sensata.
Um exemplo prático: proxy de logging e temporização
O programa define uma interface Repository e uma implementação real, depois envolve a implementação em um proxy dinâmico cujo handler registra cada chamada, mede o tempo, encaminha para o objeto real e registra o resultado — adicionando comportamento transversal sem modificar a implementação.
O que observar na execução:
repofoi usado exatamente como umRepository—repo.save(...),repo.count(),repo.find(...)compilaram e executaram — mas nenhuma classe chamada "logging repository" existe no código-fonte. A JVM gerou uma classe$Proxy0implementando a interface, e cada chamada foi paraLoggingHandler.invoke. O proxy é umRepositoryreal (instanceofretornoutrue).- Cada método de negócio recebeu um log automático de entrada/saída e temporização sem nenhuma alteração em
InMemoryRepository. Essa separação — a implementação permanece alheia, a preocupação transversal vive no handler — é o objetivo do AOP, e os proxies dinâmicos são como o Spring implementa@Transactional,@Cacheablee similares para beans baseados em interfaces. - O handler encaminhou cada chamada com
method.invoke(target, args), o que significa que uma falha emfind(99)retornou comoInvocationTargetException. O handler a desembrulhou comgetCause()e relançou aNoSuchElementExceptionreal, de modo que o chamador capturou a exceção natural em vez de um wrapper de reflexão. Um proxy que esquece de desembrulhar vazaInvocationTargetExceptionpara os chamadores. - Métodos de
Objecttambém são roteados porinvoke, então o handler tratou especialmentemethod.getDeclaringClass() == Object.classe os encaminhou diretamente. Sem essa guarda,toString/equals/hashCodetambém seriam registrados (barulhento) ou, se você construísse strings a partir do proxy dentro deinvoke, poderiam recorrer. Tratar os métodos deObjectdeliberadamente é uma parte padrão de escrever um handler de proxy. Proxy.isProxyClass(repo.getClass())confirmou que a classe é sintetizada pela JVM, e seu nome$Proxy0mostra que foi gerada, não escrita. Como a API recebe umClass<?>[]de interfaces, um único proxy pode implementar várias ao mesmo tempo — que é como um único mock ou stub pode satisfazer múltiplos contratos simultaneamente.
Quando usar o quê
- Interface, sem dependência desejada →
java.lang.reflect.Proxy. Embutido, simples, apenas interfaces. - Necessidade de criar proxy de uma classe concreta → ByteBuddy ou CGLIB (baseado em subclasse). Necessário porque
Proxynão consegue. - Apenas precisa criar stubs de interfaces em testes → uma biblioteca de mock (Mockito) construída sobre esses mecanismos — não crie manualmente.
Os proxies dinâmicos encerram a parte de reflexão: desde inspecionar um objeto Class, até ler e escrever campos, invocar métodos, construir instâncias via construtores, ler anotações e finalmente sintetizar implementações inteiras em tempo de execução. Juntos, formam o kit de ferramentas que permite que frameworks operem genericamente sobre tipos com os quais nunca foram compilados — usados com moderação e por trás de abstrações limpas, são o que torna possível o ecossistema Java de containers, mapeadores e executores.