ProgrammazioneArchitetto Java

Che cos'è un proxy, i tipi di proxy in Java e come viene realizzato il comportamento dinamico degli oggetti con esso?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

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:

  • I proxy statici richiedono codice predefinito per ciascuna funzione
  • I proxy dinamici possono essere creati al volo durante il runtime per qualsiasi interfaccia
  • Per le classi senza interfacce, sono necessarie librerie di terze parti (ad esempio, CGLIB)

Domande ingannevoli.

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.

Errori tipici e anti-pattern

  • Errore di tipo: tentativo di utilizzare Proxy per una classe che non implementa un'interfaccia
  • Chiamate cicliche implicite che portano a StackOverflow
  • Proxy di classi non destinate a questo, o tentativo di proxy di classi finali

Esempio dalla vita reale

Caso negativo

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:

  • Trasparente, facile da capire il flusso di esecuzione

Svantaggi:

  • Grandissimi sprechi di tempo, moltissimo codice ripetitivo, difficile da mantenere

Caso positivo

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:

  • Più veloce implementare e modificare qualsiasi funzionalità trasversale, nessuna duplicazione

Svantaggi:

  • Necessità di comprendere come funziona il proxy, potenziali complessità nel debug