Home > GOF Pattern, java > GoF Patterns: Proxy

GoF Patterns: Proxy

28 marzo 2011

Translate in English with Google Translate
In questo articolo tratterò il pattern Proxy anche detto Surrogate

Motivazione

Si tratta di un pattern strutturale basato su oggetti che viene utilizzato per accedere ad un un oggetto complesso tramite un oggetto semplice.
Questo pattern può risultare utile se l’oggetto complesso:

  • richiede molte risorse computazionali
  • richiede molto tempo per caricarsi
  • è locato su una macchina remota e il traffico di rete determina latenze ed overhead
  • non definisce delle policy di sicurezze e consente un accesso indiscriminato
  • non viene mantenuto in cache ma viene rigenerato ad ogni richiesta


In tutti questi casi è possibile disposte delle politiche di gestione e/o di ottimizzazione.

A seconda del contesto, viene aggiunto un prefisso per descrivere il caso di riferimento:

  • Virtual Proxy Pattern: ritarda la creazione e l’inizializzazione dell’oggetto poiché richiede grosse risorse (es: caricamento immagini )
  • Remote Proxy Pattern: fornisce una rappresentazione locale dell’oggetto remoto (es: accesso ad oggetto remoto tramite RMI )
  • Protection Proxy Pattern: fornisce un controllo sull’accesso dell’oggetto remoto (es: richiesta username/password per l’accesso)
  • Smart Proxy Pattern: fornisce una ottimizzazione dell’oggetto (es: caricamento in memoria dell’oggetto)

Il proxy espone gli stessi metodi dell’oggetto complesso che maschera e questo permette di adattare facilmente l’oggetto senza richiedere modifiche.

Partecipanti e Struttura

Questo pattern è composto dai seguenti partecipanti:

  1. Client: colui che effettua l’invocazione all’operazione di interesse
  2. SubjectInterface: definisce l’interfaccia utilizzata dal Client che viene implementata dal Proxy e dal RealSubject
  3. RealSubject: definisce l’oggetto reale di cui il Proxy avrà il compito di surrogare.
  4. Proxy: definisce la classe che avrà il compito di surrogare l’oggetto reale mantenendo una Reference a tale oggetto, creando e distruggendo l’oggetto ed esponendo gli stessi metodi pubblici dell’oggetto reale definiti dall’interfaccia.

Possiamo schematizzare in UML

Proxy Pattern

Proxy Pattern

Conseguenze

Tale pattern presenta i seguenti vantaggi/svantaggi:

  1. un Proxy Remoto: nasconde il fatto che un oggetto appartiene ad un diverso spazio di indirizzamento
  2. un Proxy Virtuale: ottimizza la creazione di un oggetto solo nel momento in cui è realmente necessario
  3. un Protection Proxy ed uno Smart Proxy: aggiungono ulteriori comportamenti quando si accede ad un oggetto

Implementazione
E’ possibile fare diversi esempi pertanto partiamo da un esempio semplice per poi procedere con degli esempi più complessi, pertanto passerò in rassegna questi esempi:

  • Simple Proxy: con lo scopo di illustrare una semplice invocazione “proxata”
  • Dynamic Proxy: con lo scopo di vedere l’implementazione del proxy tramite le API java
  • Virtual Proxy: con lo scopo di vedere un caso di “lazy loading” cioè di “caricamento pigro”, utilizzato per ridurre il consumo delle risorse solo nei caso di utilizzo.
  • Protection Proxy: con lo scopo di proteggere l’accesso alla risorsa tramite un proxy intermedio.

Simple Proxy
Come primo esempio interponiamo un proxy ad un oggetto di nostro interesse, per disaccoppiare il legame tra il client ed il subject.

Realizziamo il Class Diagram UML:

Proxy Pattern - Simple

Proxy Pattern – Simple

Definiamo l’interfaccia che si occupa di definire il metodo da esporre, nel nostro caso un semplice metodo di stampa.

package patterns.proxy.simple;

interface Subject {
    void stampa();
}

Creiamo la classe RealSubject che si occupa di implementare l’interfaccia Subject.

package patterns.proxy.simple;

public class RealSubject implements Subject {

    @Override
    public void stampa() {
        System.out.println("RealSubject");
    }
}

Creiamo la classe MyProxy che si occupa di “proxare” l’invocazione del Client verso il RealSubject.

package patterns.proxy.simple;

public class MyProxy implements Subject {

    private RealSubject realSubject = new RealSubject();

    @Override
    public void stampa() {
        System.out.println("MyProxy");
        realSubject.stampa();
    }
}

Creiamo la classe Client che si occupa di invocare il metodo stampa() del proxy, il quale invocherà il metodo stampa di RealSubject.

package patterns.proxy.simple;

public class Client {

    private Subject subject;

    public static void main(String[] args) {
        new Client().invoke();
    }

    void invoke() {
        subject = new MyProxy();
        subject.stampa();
    }

}

Eseguiamo il Client e notiamo che l’invocazione del metodo stampa() “passa” per MyProxy per poi accedere al RealSubject.

$JAVA_HOME/bin/java patterns.proxy.simple.Client
MyProxy
RealSubject

Dynamic Proxy
Nelle librerie del JDK, in particolare nel package java.lang.reflect a partire dalla versione Java 1.3, esiste una implementazione del Pattern Proxy per consentire la generazione dinamica di una classe Proxy. L’utilizzo di questa libreria consente al programmatore di non dover più creare la classe Proxy la quale viene creata dinamicamete tramite la reflection.
Questa libreria è stata utilizzata nell’architettura RMI ( Remote Method Invocation ) pertanto a partire dalla versione Java 1.6 non è più richiesta la generazione di Stub e Skeleton per l’invocazione di oggetti remoti. Lo Stub e lo Skeleton venivano generati tramite il comando $JAVA_HOME/bin/rmic e rappresentavano rispettivamente il proxy client e server utilizzati per l’invocazione di metodi remoti. Tramite l’ausilio della librerie della reflection, non è più necessaria la loro generazione in quanto le classi proxy vengono generate dinamicamente con ovvi risparmio di tempo, di codice e di bugs!
L’utilizzo di questa implemetazione consente al Client di continuare ad invocare il metodo del RealSubject senza dover invocare il metodo del Proxy, in quanto sarà la reflection che effettuerà l’indirezione, ma vediamolo praticamente.

Pertanto vediamo lo stesso esempio di prima ma utilizzando il proxy dinamico.

Realizziamo il Class Diagram UML:

Dynamic Proxy

Dynamic Proxy

Dobbiamo creare una classe che implementa InvocationHandler e faccia l’override del metodo invoke al fine di “proxare” la richiesta al RealSubject.

package patterns.proxy.dinamico;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyDynamicProxy implements InvocationHandler {

    private RealSubject realSubject = new RealSubject();

    @Override
    public Object invoke(Object proxy, Method method, Object[] os) throws Throwable {
        System.out.println("MyDynamicProxy ");
        return method.invoke(realSubject, os);
    }
}

Nella classe Client viene utilizzata la classe Proxy per generare tramite la reflection la classe proxy che si occupa, tramite le informazioni recuperate da MyDynamicProxy, di “proxare” le richieste al RealSubject.

package patterns.proxy.dinamico;

import java.lang.reflect.Proxy;

public class Client {

    private Subject subject;

    public static void main(String[] args) throws Exception {
        new Client().invoke();
    }

    public void invoke() {
        subject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),
                new Class[]{Subject.class},
                new MyDynamicProxy());
        subject.stampa();
    }
}

Il risultato dell’invocazione è la seguente:

$JAVA_HOME/bin/java patterns.proxy.dinamic.Client
MyDynamicProxy
RealSubject

Virtual Proxy.

Analizziamo il caso in cui dobbiamo caricare un documento che contenga testo ed immagini. Questi richiedono una determinata formattazione per esempio posizione, dimensione, allineamento ecc. e tali informazioni sono indispensabili per la creazione dell’intero documento.

Il caricamento del testo solitamente non richiede molte risorse, a meno che non carichiamo un testo particolarmente lungo, mentre invece il caricamento delle immagini è sicuramente più critico e può comportare un tempo di attesa decisamente più lungo sia per le dimensioni dell’immagini che per il tipo di connessione dati. Tale operazione critica può essere posticipata e per farlo usiamo questo pattern.

L’obiettivo è di caricare direttamente il testo del documento e di visualizzare le immagini quando vengono richieste. Questo consente di gestire in modo oculata il caricamento di file critici.

Realizziamo il Class Diagram UML:

Simple Class Proxy

Simple Class Proxy

Definiamo l’interfaccia FileInterface che funge da Subject e definisce il metodo usato per la visualizzazione.

package patterns.proxy;

public interface FileInterface {
    public void display();
}

Creiamo la classe concreta FileManager che funge da RealSubject ed ha il compito di caricare e visualizzare i files:

package patterns.proxy;

public class FileManager implements FileInterface {

    private byte[] byteImage;

    public FileManager(String fileName) {
        System.out.print("FileManager() -> " );
        byteImage = load( fileName );
    }

    private byte[] load( String fileName) {
        System.out.println("FileManager.load(): loading file " + fileName);
        return new FileLoader().load(fileName);
    }

    @Override
    public void display() {
        System.out.print("FileManager.display(): display file ");
        for(int i=0;  i<byteImage.length && i<10;i++) {
           System.out.print( Integer.toHexString( byteImage[i] ) + " ");
        }
        System.out.println("...");
    }

}

Creiamo la classe FileLoader che si occupa di eseguire delle istruzioni critiche.

package patterns.proxy;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class FileLoader {

     public byte[] load( String fileName ) {
        byte [] myFile = null;
        try {
            InputStream is = new FileInputStream( fileName );
            myFile = new byte[is.available()];
            is.read(myFile);
        } catch (IOException ex) {}
        return myFile;
    }
}

Creiamo la classe ProxyFileManager che funge da Proxy ed agisce da surrogato. Questa implementa la stessa interfaccia FileInterface implementata dal RealSubject FileManager:

package patterns.proxy;

public class ProxyFileManager implements FileInterface {

    private FileManager fileManager;
    private String fileName;

    public ProxyFileManager(String fileName) {
        System.out.println("ProxyFileManager() " );
        this.fileName = fileName;
    }

    @Override
    public void display() {
        System.out.print("ProxyFileManager.display() -> " );
        if (fileManager==null)
            fileManager = new FileManager( fileName );
        fileManager.display();
    }
}

Creiamo la classe Client che invoca il gestore del documento DocumentManager:

package patterns.proxy;

public class Client {

    public static void main(String[] args) {
        new DocumentManager( "/shared/text.txt", "/shared/image.png" );
    }
}

Creiamo la classe DocumentManager che si occupa di invocare FileManager nel caso del caricamento del testo mentre invoca ProxyFileManager nel caso del caricamento delle immagini.

package patterns.proxy;

public class DocumentManager {

    public DocumentManager(String textFileName, String imageFileName) {
        loadText(textFileName);
        loadImage(imageFileName);
    }

    private void loadText(String textFileName) {
        System.out.println("--LOADING TEXT--");
        new FileManager(textFileName).display();
    }

    private void loadImage(String imageFileName) {
        System.out.println("--LOADING IMAGES--");
        new ProxyFileManager(imageFileName).display();
    }
}

Eseguiamo la classe Cliente notiamo che il documento viene caricato solo in sede di visualizzazione dell’immagine.

L’output è il seguente:

--LOADING TEXT--
FileManager() -> FileManager.load(): loading file /shared/text.txt
FileManager.display(): display file 63 69 61 6f 6f 6f 6f 6f 6f ...
--LOADING IMAGES--
ProxyFileManager()
ProxyFileManager.display() -> FileManager() -> FileManager.load(): loading file /shared/image.png
FileManager.display(): display file 3c 3f 78 6d 6c 20 76 65 72 73 ...

Protection Proxy
Come ultimo esempio vediamo il caso di un Protection Proxy che viene utilizzato per aggiungere delle logiche di protezione di accesso a delle risorse, nel nostro caso a delle classi.
Vediamo il caso in cui vogliamo gestire un processo di autenticazione per la visualizzazione di informazioni fornite da un metodo di una classe. Considerando che tale classe non prevede queste logiche, interponiamo un Proxy che richiederà all’utente di fornire username e password al fine di effettuare una verifica presso un Identity Provider (per esempio un LDAP). Solo nel caso in cui l’autenticazione ha buon esito, allora verrà fornita l’informazione richiesta.

Realizziamo il Class Diagram UML:

Protection Proxy Pattern

Protection Proxy Pattern

Costruiamo l’interfaccia del Subject dal nome ContoCorrente che espone il metodo di visualizzazione dei movimenti del proprio conto:

package patterns.proxy.protection;

import java.util.List;

public interface ContoCorrente {

    List listaMovimenti(String numeroConto);
}

Adesso costruiamo il RealSubject che fornisce l’informazione dei movimenti del conto:

package patterns.proxy.protection;

import java.util.List;

public class IntesaSanPaolo implements ContoCorrente {

    @Override
    public List listaMovimenti(String numeroConto) {
        return Estrattore.getMovimenti(numeroConto);
    }

}

Questa classe si avvale di una proceduta di estrazione gestita dalla classe Estrattore che nel nostro caso ci ritorna semplicemente una lista vuota:

package patterns.proxy.protection;

import java.util.ArrayList;
import java.util.List;

public class Estrattore {

    public static List getMovimenti(String numeroConto) {
        return new ArrayList();
    }

}

A questo punto creiamo una classe proxy che richiede l’autenticazione:

package patterns.proxy.protection;

import java.util.List;

public class ProxyIntesaSanPaolo implements ContoCorrente {

    private String username;
    private String password;
    private IntesaSanPaolo intesaSanPaolo;

    public ProxyIntesaSanPaolo(String username, String password){
        this.username = username;
        this.password = password;
        intesaSanPaolo = new IntesaSanPaolo();
    }

    @Override
    public List listaMovimenti(String numeroConto) {
        List lista = null;
        if( checkUser() )
            lista = intesaSanPaolo.listaMovimenti( numeroConto );
        return lista;
    }

    private boolean checkUser(){
        return IdentityManager.checkUser(username, password);
    }

}

Tale classe invoca un DAO che accedere ad un Identity Provider, nel mio esempio ho invece creato un controllo fake!

package patterns.proxy.protection;

public class IdentityManager {

    //Oggetto DAO che si connettera' ad un Identity Manager
    public static boolean checkUser(String username, String password) {
        return (username.equals(password)) ? true : false;
    }

}

Creiamo la classe del Client che si occupa di invocare il Proxy ed accedere alla informazioni dei movimenti conto.

package patterns.proxy.protection;

public class Utente {

    public static void main(String[] args) {
        ContoCorrente contoCorrente = new ProxyIntesaSanPaolo( "pippo",  "pluto");
        System.out.println( "login errato: " + contoCorrente.listaMovimenti("0123456789") );

        contoCorrente = new ProxyIntesaSanPaolo( "pippo",  "pippo");
        System.out.println( "login corretto: " + contoCorrente.listaMovimenti("0123456789") );
    }
}

Eseguiamo il codice e vediamo il risultato:

$JAVA_HOME/bin/java patterns.proxy.protection.Utente
login errato: null
login corretto: []
Categorie:GOF Pattern, java
  1. 10 maggio 2012 alle 4:02 PM

    Ho una domanda: ma visto che nel costruttore del proxy c’è una new di un realSubject non vedo dove stia la differenza con il creare direttamente un realSubject. Un proxy dovrebbe ritardare la creazione di un oggetto fino al momento necessario, ma se lo crea nel costruttore non credo abbia più senso l’esistenza del proxy. Naturalmente parlo da studente che ha da poco a che fare con i patterns…

    • 12 luglio 2012 alle 3:20 PM

      La tua osservazione è “parzialmente” giusta in quanto il Proxy è un intermediario ed in base al tipo di Proxy (Smart, Protection, Simple ecc) le logiche di accesso al RealSubject sono diverse.
      Ciò vuol dire che non tutti i proxy sono obbligati a ritardare la creazione dell’oggetto ( Lazy vs Proxy ).
      Infatti questo concetto di “ritardo” si applica ad un altro Pattern il “Lazy Loading” il quale ritarda la creazione dell’oggetto solo quando occorre ( Lazy Loading ) ed il “Virtual Proxy” implementa questa logica di “ritardo” pertanto ho corretto l’esempio. Ho colto l’occasione per approfondire l’articolo inserendo il Proxy Dinamico ed altri esempi.

  2. Giuliano
    5 luglio 2014 alle 12:47 PM

    Ottimo articolo, sto studiando java dal testo “Core Java I Fundamentals” (Horstmann, Cornell) e il capitolo in questione era tutt’altro che chiaro. Grazie per il tuo lavoro

  1. 2 aprile 2012 alle 10:28 PM
I commenti sono chiusi.
%d blogger cliccano Mi Piace per questo: