Il proxy è un design pattern strutturale in cui un oggetto P
funge da mediatore nelle chiamate verso un altro oggetto O
. In altri termini un chiamante non invocare direttamente i metodi dell’oggetto O
, ma lo fa attraverso l’intermediazione di un terzo attore P
che si occupa di invocare concretamente i metodi sull’oggetto originale, e di restituire i risultati dell’invocazione in caso di metodi con valori di ritorno. L’esempio che generalmente viene riportato dalla letteratura specializzata è quello dell’utilizzo di una risorsa su disco, il cui caricamento in memoria è differito sino all’utilizzo vero e proprio della risorsa. Al link Design Patterns – Proxy Pattern trovate una implementazione del pattern per la gestione di immagini. Le classi proxy possono essere utilizzate per diversi scopi. Un elenco non esaustivo è il seguente:
- Registrazione quando un metodo inizia e finisce.
- Eseguire controlli aggiuntivi sugli argomenti.
- Mokkare il comportamento della classe originale.
- Implementare accesso lazy a risorse costose.
Implementazione
Di solito gli oggetti proxy hanno gli stessi metodi dell’oggetto originale e nelle implementazioni con Dynamic Proxy java estendono esplicitamente (extends
) la classe originale. La gestione delle chiamate avviene attraverso uno o più handler, che possono o meno invocare il metodo originale.
La classe java.lang.reflect.Proxy
espone diversi metodi statici per la manipolazione dei proxy dinamici. In particolare i metodi getProxyClass
e newProxyInstance
possono essere utilizzati rispettivamente per generare una classe proxy per una dato set di interfacce o la relativa istanza. Ad ogni istanza di un proxy deve essere associato un oggetto handler
che implementa l’interfaccia java.lang.reflect.InvocationHandler
. Ogni invocazione di un metodo sull’istanza del proxy viene intercettata dal metodo invoker()
dell’handler
, al quale è passata l’istanza (del proxy), il metodo invocato ed un array contenente gli argomenti di invocazione.
Una nuova istanza di proxy è quindi generata nel modo seguente:
1 2 3 4 5 |
InvocationHandler handler = new MyInvocationHandler(...); Class proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), new Class[] { Foo.class }); Foo f = (Foo) proxyClass. getConstructor(new Class[] { InvocationHandler.class }). newInstance(new Object[] { handler }); |
o più semplicemente.
1 2 |
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class[] { Foo.class }, handler); |
Esempio
L’esempio che presentiamo è quello di un proxy che estende le funzionalità di un logger per inserire in tutti i messaggi una componente comune che identifica tutte le chiamate ad un servizio web. Nell’articolo JAX-WS Interceptor abbiamo visto come sia possibile intercettare le chiamate ad un web service implementato con JAX-WS al fine di memorizzare in un oggetto ThreadLocal
la componente Header
della Request
. Quello che faremo è di intercettare le chiamate al log e di inserire nel messaggio il sender della request, in questo modo ogni stringa di log sarà riconducibile al chiamante.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class LoggerProducer { public static Logger getLogger( Class<?> clazz ) { Logger logger = LoggerFactory.getLogger( clazz ); LoggerHandler handler = new LoggerHandler(logger); return (Logger) Proxy.newProxyInstance(Logger.class.getClassLoader(), new Class[] { Logger.class }, handler); } static class LoggerHandler implements InvocationHandler { private final Logger logger; public LoggerHandler(Logger original) { this.logger = original; } public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { if ( args != null && args.length == 1 && ( method.getName().equals("info") || method.getName().equals("debug") || method.getName().equals("trace") || method.getName().equals("warn") || method.getName().equals("error") ) ) { Header header = RequestContextHolder.getCurrentRequestHeader(); if (header != null) { args[0] = "SENDER[" + header.getSender() + "] - " + args[0]; } } return method.invoke(logger, args); } } } |
Nel dettaglio l’esempio utilizza il logger org.slf4j.Logger
e attraverso la corrispondente classe factory org.slf4j.LoggerFactory
genera un logger “wrappandolo” all’interno di un dynamic proxy. A tale scopo è implementato un handler il cui costruttore riceve in input il logger generato:
1 2 3 4 5 6 |
class LoggerHandler implements InvocationHandler { private final Logger logger; public LoggerHandler(Logger original) { this.logger = original; } } |
invoke()
, richiesto dall’interfaccia InvocationHandler
, verifica che il metodo invocato sia uno tra debug
, trace
, warn
ed error
esposti dal dall’oggetto Logger
ed in caso affermativo modifica l’argomento di input inserendo nel messaggio il sender:
1 2 3 4 |
Header header = RequestContextHolder.getCurrentRequestHeader(); if (header != null) { args[0] = "SENDER[" + header.getSender() + "] - " + args[0]; } |
Codice Sorgente
Il codice sorgente dell’esempio è scaricabile qui jaxws-webapp-proxy.