Introduzione a cglib

1. Panoramica

In questo articolo, esamineremo la libreria cglib (Code Generation Library). È una libreria di strumentazione di byte utilizzata in molti framework Java come Hibernate o Spring . La strumentazione bytecode permette di manipolare o creare classi dopo la fase di compilazione di un programma.

2. Dipendenza da Maven

Per utilizzare cglib nel tuo progetto, aggiungi una dipendenza Maven (l'ultima versione può essere trovata qui):

 cglib cglib 3.2.4 

3. Cglib

Le classi in Java vengono caricate dinamicamente in fase di esecuzione. Cglib utilizza questa funzionalità del linguaggio Java per rendere possibile l'aggiunta di nuove classi a un programma Java già in esecuzione.

Hibernate usa cglib per la generazione di proxy dinamici. Ad esempio, non restituirà l'oggetto completo archiviato in un database ma restituirà una versione strumentata della classe archiviata che carica pigramente i valori dal database su richiesta.

I framework di derisione popolari, come Mockito, usano cglib per i metodi di derisione. Il mock è una classe strumentata in cui i metodi sono sostituiti da implementazioni vuote.

Vedremo i costrutti più utili da cglib.

4. Implementazione del proxy utilizzando cglib

Supponiamo di avere una classe PersonService che ha due metodi:

public class PersonService { public String sayHello(String name) { return "Hello " + name; } public Integer lengthOfName(String name) { return name.length(); } }

Si noti che il primo metodo restituisce String e il secondo Integer.

4.1. Restituzione dello stesso valore

Vogliamo creare una semplice classe proxy che intercetterà una chiamata a un metodo sayHello () . La classe Enhancer ci consente di creare un proxy estendendo dinamicamente una classe PersonService utilizzando un metodo setSuperclass () dalla classe Enhancer :

Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(PersonService.class); enhancer.setCallback((FixedValue) () -> "Hello Tom!"); PersonService proxy = (PersonService) enhancer.create(); String res = proxy.sayHello(null); assertEquals("Hello Tom!", res);

Il FixedValue è un'interfaccia di callback che restituisce semplicemente il valore dal metodo Proxied. L'esecuzione del metodo sayHello () su un proxy ha restituito un valore specificato in un metodo proxy.

4.2. Restituzione del valore in base alla firma del metodo

La prima versione del nostro proxy ha alcuni inconvenienti perché non siamo in grado di decidere quale metodo un proxy dovrebbe intercettare e quale metodo dovrebbe essere invocato da una superclasse. Possiamo utilizzare un'interfaccia MethodInterceptor per intercettare tutte le chiamate al proxy e decidere se si desidera effettuare una chiamata specifica o eseguire un metodo da una superclasse:

Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(PersonService.class); enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> { if (method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) { return "Hello Tom!"; } else { return proxy.invokeSuper(obj, args); } }); PersonService proxy = (PersonService) enhancer.create(); assertEquals("Hello Tom!", proxy.sayHello(null)); int lengthOfName = proxy.lengthOfName("Mary"); assertEquals(4, lengthOfName);

In questo esempio, stiamo intercettando tutte le chiamate quando la firma del metodo non proviene dalla classe Object , il che significa che i metodi toString () o hashCode () non verranno intercettati. Oltre a ciò, stiamo intercettando solo metodi da un PersonService che restituisce una stringa . La chiamata a un metodo lengthOfName () non verrà intercettata perché il tipo restituito è un numero intero.

5. Bean Creator

Un altro utile costrutto del cglib è una classe BeanGenerator . Ci permette di creare dinamicamente bean e di aggiungere campi insieme ai metodi setter e getter. Può essere utilizzato dagli strumenti di generazione del codice per generare semplici oggetti POJO:

BeanGenerator beanGenerator = new BeanGenerator(); beanGenerator.addProperty("name", String.class); Object myBean = beanGenerator.create(); Method setter = myBean.getClass().getMethod("setName", String.class); setter.invoke(myBean, "some string value set by a cglib"); Method getter = myBean.getClass().getMethod("getName"); assertEquals("some string value set by a cglib", getter.invoke(myBean));

6. Creazione di mixin

Un mixin è un costrutto che consente di combinare più oggetti in uno. Possiamo includere un comportamento di un paio di classi ed esporre quel comportamento come una singola classe o interfaccia. I cglib Mixin consentono la combinazione di più oggetti in un unico oggetto. Tuttavia, per fare ciò, tutti gli oggetti inclusi in un mixin devono essere supportati da interfacce.

Diciamo che vogliamo creare un mix di due interfacce. Dobbiamo definire sia le interfacce che le loro implementazioni:

public interface Interface1 { String first(); } public interface Interface2 { String second(); } public class Class1 implements Interface1 { @Override public String first() { return "first behaviour"; } } public class Class2 implements Interface2 { @Override public String second() { return "second behaviour"; } } 

Per comporre le implementazioni di Interface1 e Interface2 dobbiamo creare un'interfaccia che le estenda entrambe:

public interface MixinInterface extends Interface1, Interface2 { }

Usando un metodo create () dalla classe Mixin possiamo includere comportamenti di Class1 e Class2 in una MixinInterface:

Mixin mixin = Mixin.create( new Class[]{ Interface1.class, Interface2.class, MixinInterface.class }, new Object[]{ new Class1(), new Class2() } ); MixinInterface mixinDelegate = (MixinInterface) mixin; assertEquals("first behaviour", mixinDelegate.first()); assertEquals("second behaviour", mixinDelegate.second());

La chiamata ai metodi su mixinDelegate richiamerà le implementazioni da Class1 e Class2.

7. Conclusione

In questo articolo, abbiamo esaminato il cglib e i suoi costrutti più utili. Abbiamo creato un proxy utilizzando una classe Enhancer . Abbiamo utilizzato un BeanCreator e, infine, abbiamo creato un Mixin che includeva i comportamenti di altre classi.

Cglib è ampiamente utilizzato dal framework Spring. Un esempio di utilizzo di un proxy cglib di Spring è l'aggiunta di vincoli di sicurezza alle chiamate ai metodi. Invece di chiamare direttamente un metodo, la sicurezza di Spring verificherà prima (tramite proxy) se un controllo di sicurezza specificato viene superato e delegherà al metodo effettivo solo se questa verifica ha avuto esito positivo. In questo articolo abbiamo visto come creare tale proxy per i nostri scopi.

L'implementazione di tutti questi esempi e frammenti di codice può essere trovata nel progetto GitHub: questo è un progetto Maven, quindi dovrebbe essere facile da importare ed eseguire così com'è.