W3docs

Proxy Dinamici in Java

Crea implementazioni proxy di interfacce a runtime in Java con java.lang.reflect.Proxy e InvocationHandler.

Un proxy dinamico è un oggetto che implementa una o più interfacce, ma ogni chiamata di metodo viene instradata — a runtime — attraverso un singolo handler che scrivi tu. La JVM sintetizza la classe proxy al volo; non scrivi mai l'implementazione. Questa è la parte più potente di java.lang.reflect, ed è il modo in cui funzionano AOP, logging trasparente, lazy loading, stub RPC e librerie di mocking. Questo capitolo mostra come Proxy.newProxyInstance e InvocationHandler si incastrano, e cosa possono e non possono fare.

I due componenti: Proxy e InvocationHandler

Un proxy dinamico richiede tre input:

  1. Un class loader (dove definire la classe sintetizzata).
  2. Un array di interfacce che il proxy implementerà.
  3. Un InvocationHandler — il singolo metodo che riceve ogni chiamata.
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 è ora un oggetto reale che implementa MyService. Chiamare svc.doThing(x) non esegue alcun corpo doThing — non esiste — ma chiama handler.invoke(proxy, <Method doThing>, [x]). L'handler decide cosa fare e cosa restituire.

La firma di invoke

Object invoke(Object proxy, Method method, Object[] args) throws Throwable
  • proxy — l'istanza del proxy stesso (raramente usata; attenzione a chiamare metodi su di essa dall'interno di invoke, poiché ri-entra nell'handler e può causare un loop infinito).
  • method — il Method che è stato chiamato; method.getName(), method.getReturnType(), le sue annotazioni, ecc. sono tutti disponibili.
  • args — gli argomenti come Object[] (null se il metodo non ne accetta); i primitivi sono in forma boxed.
  • return — ciò che il chiamante deve ricevere; deve essere compatibile per assegnazione con method.getReturnType() o si ottiene una ClassCastException. Per un metodo void, restituisce null.

Un pattern frequente è fare il forward a un oggetto "target" reale: method.invoke(target, args) — avvolgendo quella chiamata con logging, timing, transazioni o retry. Quella chiamata a Method.invoke è lo stesso dispatch riflessivo trattato in Reflection Java: Metodi; qui è guidata interamente dal Method che la JVM passa al tuo handler. Questa forma di inoltro è l'idioma decorator via proxy, ed è la base di Spring AOP.

Solo interfacce

Il vincolo più grande: java.lang.reflect.Proxy fa il proxy di interfacce, non di classi. Non puoi fare il proxy dinamico di una classe concreta con questa API. Se hai bisogno di fare il proxy di una classe, utilizzi una libreria bytecode (CGLIB, ByteBuddy) che genera invece una sottoclasse — ecco perché i framework le includono. Per i design basati su interfacce, il Proxy integrato è sufficiente e non richiede dipendenze.

La classe proxy sintetizzata:

  • Estende java.lang.reflect.Proxy e implementa le tue interfacce.
  • Ha un nome generato come $Proxy0.
  • Instrada equals, hashCode e toString (i metodi di Object) attraverso invoke — quindi il tuo handler deve essere pronto a gestirli, o delegarli sensibilmente.

Un esempio pratico: proxy di logging + timing

Il programma definisce un'interfaccia Repository e una vera implementazione, poi avvolge l'implementazione in un proxy dinamico il cui handler registra ogni chiamata, ne misura il tempo, fa il forward all'oggetto reale e registra il risultato — aggiungendo comportamento trasversale senza toccare l'implementazione.

java— editable, runs on the server

Cosa trarre dall'esecuzione:

  • repo era utilizzabile esattamente come un Repositoryrepo.save(...), repo.count(), repo.find(...) compilavano e giravano tutti — eppure nessuna classe denominata "logging repository" esiste nel sorgente. La JVM ha generato una classe $Proxy0 che implementa l'interfaccia, e ogni chiamata è arrivata in LoggingHandler.invoke. Il proxy è un vero Repository (instanceof ha restituito true).
  • Ogni metodo di business ha ricevuto un log automatico di entrata/uscita e timing senza alcuna modifica a InMemoryRepository. Quella separazione — l'implementazione rimane all'oscuro, la logica trasversale vive nell'handler — è l'intero punto dell'AOP, e i proxy dinamici sono il modo in cui Spring implementa @Transactional, @Cacheable e simili per i bean basati su interfacce.
  • L'handler ha fatto il forward di ogni chiamata con method.invoke(target, args), il che significa che un fallimento di find(99) è tornato come InvocationTargetException. L'handler l'ha spacchettato con getCause() e ha rilanciato la vera NoSuchElementException, così il chiamante ha catturato l'eccezione naturale anziché un wrapper di reflection. Un proxy che dimentica di spacchettare fa trapelare InvocationTargetException ai chiamanti.
  • I metodi di Object passano anche attraverso invoke, quindi l'handler ha gestito il caso speciale method.getDeclaringClass() == Object.class e li ha inoltrati normalmente. Senza quella guardia, toString/equals/hashCode verrebbero anch'essi registrati (rumorosi) o, se si costruissero stringhe dal proxy all'interno di invoke, potrebbero ricorrere. Gestire deliberatamente i metodi di Object è una parte standard della scrittura di un handler proxy.
  • Proxy.isProxyClass(repo.getClass()) ha confermato che la classe è sintetizzata dalla JVM, e il suo nome $Proxy0 mostra che è stata generata, non scritta. Poiché l'API accetta un Class<?>[] di interfacce, un proxy può implementarne diverse contemporaneamente — ed è così che un singolo mock o stub può soddisfare più contratti simultaneamente.

Quando usare cosa

  • Interfaccia, nessuna dipendenza richiestajava.lang.reflect.Proxy. Integrato, semplice, solo per interfacce.
  • Bisogno di fare il proxy di una classe concreta → ByteBuddy o CGLIB (basato su sottoclasse). Necessario perché Proxy non può.
  • Solo bisogno di stub di interfacce nei test → una libreria di mocking (Mockito) costruita su questi meccanismi — non farlo a mano.

I proxy dinamici concludono la parte sulla reflection: dall'ispezione di un oggetto Class, alla lettura e scrittura di campi, all'invocazione di metodi, alla creazione di istanze tramite costruttori, alla lettura di annotazioni, e infine alla sintesi di intere implementazioni a runtime. Insieme costituiscono il toolkit che permette ai framework di operare genericamente su tipi contro cui non erano stati compilati — usati con parsimonia e dietro astrazioni pulite, sono ciò che rende possibile l'ecosistema Java di container, mapper e runner.

Pratica

Pratica
Vuoi avvolgere un servizio definito da un'interfaccia 'PaymentGateway' in modo che ogni chiamata di metodo venga registrata, senza modificare la vera implementazione. Chiami 'Proxy.newProxyInstance(...)' passando 'new Class<?>[]{ PaymentGateway.class }' e un handler. All'interno dell'invoke dell'handler, qual è il modo standard per produrre il risultato effettivo del metodo?
Vuoi avvolgere un servizio definito da un'interfaccia 'PaymentGateway' in modo che ogni chiamata di metodo venga registrata, senza modificare la vera implementazione. Chiami 'Proxy.newProxyInstance(...)' passando 'new Class<?>[]{ PaymentGateway.class }' e un handler. All'interno dell'invoke dell'handler, qual è il modo standard per produrre il risultato effettivo del metodo?
Was this page helpful?