Storia della questione:
Gli oggetti proxy e il pattern Proxy sono stati introdotti in Java per creare oggetti sostituti che possono controllare l'accesso a un oggetto reale, modificarne il comportamento o introdurre funzioni trasversali (sicurezza, registrazione, metriche). A partire da Java 1.3, è stato introdotto il supporto per i proxy dinamici standard nel package java.lang.reflect, e successivamente sono state sviluppate librerie popolari come CGLIB e ByteBuddy.
Problema:
Non sempre è possibile/pratico modificare direttamente la logica di una classe esistente. Spesso è necessario aggiungere in modo trasparente comportamento (ad esempio, registrazione, caching, transazioni) senza modificare il codice sorgente dell'oggetto, cosa che non è possibile con l'ereditarietà standard.
Soluzione:
Gli oggetti Proxy possono essere implementati staticamente (attraverso subclassing manuale) e dinamicamente (attraverso meccanismi di riflessione o bytecode). Un proxy dinamico consente di creare al volo oggetti che implementano l'interfaccia desiderata e delegano le chiamate all'oggetto reale all'interno del metodo invoke(), offrendo flessibilità per l'inserimento del comportamento di terze parti.
Esempio di codice:
import java.lang.reflect.*; interface Service { void doWork(); } class RealService implements Service { public void doWork() { System.out.println("Doing work!"); } } class LoggingInvocationHandler implements InvocationHandler { private final Object target; public LoggingInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Calling: " + method.getName()); return method.invoke(target, args); } } Service real = new RealService(); Service proxy = (Service) Proxy.newProxyInstance( real.getClass().getClassLoader(), new Class[] {Service.class}, new LoggingInvocationHandler(real)); proxy.doWork(); // La chiamata viene registrata
Caratteristiche chiave:
Il proxy dinamico standard in Java può lavorare con classi che non implementano interfacce?
No. Il proxy standard in Java lavora solo con interfacce. Per le classi senza interfacce (ad esempio, se si desidera proxy un comune classe) sono necessari strumenti di terze parti (come CGLIB, ByteBuddy).
Può essere creato un proxy per un'interfaccia con metodi privati?
No. Le interfacce in Java non possono contenere metodi privati che devono essere proxy. Il proxy dinamico implementa i metodi pubblici dell'interfaccia. A partire da Java 9, sono stati introdotti metodi privati per default, ma non sono accessibili tramite proxy.
Qual è la differenza tra InvocationHandler e MethodInterceptor (CGLIB)?
InvocationHandler è l'interfaccia standard JDK utilizzata per i proxy dinamici. Accetta chiamate ai metodi dell'interfaccia. MethodInterceptor è un'interfaccia di CGLIB che consente di intercettare le chiamate ai metodi in qualsiasi classe tramite eredità dinamica. La differenza è nell'applicabilità e nel livello: JDK funziona solo con interfacce, CGLIB con qualsiasi classe.
Un ingegnere ripete manualmente il codice di registrazione in ogni metodo di tutti i servizi. Aggiungendo nuovi metodi, duplica costantemente la logica, dimenticando spesso di aggiungere le chiamate necessarie.
Vantaggi:
Svantaggi:
Nel progetto è stato implementato l'AOP tramite un proxy dinamico: la registrazione e il controllo delle transazioni vengono effettuati attraverso wrapper-sostituti implementati in modo centralizzato.
Vantaggi:
Svantaggi: