W3docs

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:

  1. Um class loader (onde definir a classe sintetizada).
  2. Um array de interfaces que o proxy implementará.
  3. 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 Throwable
  • proxy — a própria instância do proxy (raramente usada; cuidado ao chamar métodos nela de dentro de invoke, pois isso re-entra no handler e pode causar loop infinito).
  • method — o Method que foi chamado; method.getName(), method.getReturnType(), suas anotações, etc., estão todos disponíveis.
  • args — os argumentos como Object[] (null se 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á uma ClassCastException. Para um método void, retorne null.

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.Proxy e implementa suas interfaces.
  • Tem um nome gerado como $Proxy0.
  • Roteia equals, hashCode e toString (os métodos de Object) por invoke també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.

java— editable, runs on the server

O que observar na execução:

  • repo foi usado exatamente como um Repositoryrepo.save(...), repo.count(), repo.find(...) compilaram e executaram — mas nenhuma classe chamada "logging repository" existe no código-fonte. A JVM gerou uma classe $Proxy0 implementando a interface, e cada chamada foi para LoggingHandler.invoke. O proxy é um Repository real (instanceof retornou true).
  • 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, @Cacheable e similares para beans baseados em interfaces.
  • O handler encaminhou cada chamada com method.invoke(target, args), o que significa que uma falha em find(99) retornou como InvocationTargetException. O handler a desembrulhou com getCause() e relançou a NoSuchElementException real, de modo que o chamador capturou a exceção natural em vez de um wrapper de reflexão. Um proxy que esquece de desembrulhar vaza InvocationTargetException para os chamadores.
  • Métodos de Object também são roteados por invoke, então o handler tratou especialmente method.getDeclaringClass() == Object.class e os encaminhou diretamente. Sem essa guarda, toString/equals/hashCode também seriam registrados (barulhento) ou, se você construísse strings a partir do proxy dentro de invoke, poderiam recorrer. Tratar os métodos de Object deliberadamente é uma parte padrão de escrever um handler de proxy.
  • Proxy.isProxyClass(repo.getClass()) confirmou que a classe é sintetizada pela JVM, e seu nome $Proxy0 mostra que foi gerada, não escrita. Como a API recebe um Class<?>[] 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 desejadajava.lang.reflect.Proxy. Embutido, simples, apenas interfaces.
  • Necessidade de criar proxy de uma classe concreta → ByteBuddy ou CGLIB (baseado em subclasse). Necessário porque Proxy nã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.

Prática

Prática
Você quer envolver um serviço definido por uma interface 'PaymentGateway' para que cada chamada de método seja registrada, sem modificar a implementação real. Você chama 'Proxy.newProxyInstance(...)' passando 'new Class<?>[]{ PaymentGateway.class }' e um handler. Dentro do 'invoke' do handler, qual é a forma padrão de produzir o resultado real do método?
Você quer envolver um serviço definido por uma interface 'PaymentGateway' para que cada chamada de método seja registrada, sem modificar a implementação real. Você chama 'Proxy.newProxyInstance(...)' passando 'new Class<?>[]{ PaymentGateway.class }' e um handler. Dentro do 'invoke' do handler, qual é a forma padrão de produzir o resultado real do método?
Was this page helpful?