Einführung in cglib

1. Übersicht

In diesem Artikel werden wir uns die cglib- Bibliothek (Code Generation Library) ansehen . Es ist eine Byte-Instrumentierungsbibliothek, die in vielen Java-Frameworks wie Hibernate oder Spring verwendet wird . Die Bytecode-Instrumentierung ermöglicht das Bearbeiten oder Erstellen von Klassen nach der Kompilierungsphase eines Programms.

2. Maven-Abhängigkeit

Um cglib in Ihrem Projekt zu verwenden, fügen Sie einfach eine Maven-Abhängigkeit hinzu (die neueste Version finden Sie hier):

 cglib cglib 3.2.4 

3. Cglib

Klassen in Java werden zur Laufzeit dynamisch geladen. Cglib verwendet diese Funktion der Java-Sprache, um einem bereits laufenden Java-Programm neue Klassen hinzuzufügen.

Hibernate verwendet cglib zur Generierung dynamischer Proxys. Beispielsweise wird nicht das gesamte in einer Datenbank gespeicherte Objekt zurückgegeben, sondern eine instrumentierte Version der gespeicherten Klasse, die bei Bedarf träge Werte aus der Datenbank lädt.

Beliebte Mocking-Frameworks wie Mockito verwenden cglib für Spottmethoden . Das Mock ist eine instrumentierte Klasse, in der Methoden durch leere Implementierungen ersetzt werden.

Wir werden uns die nützlichsten Konstrukte von cglib ansehen.

4. Implementieren von Proxy mit cglib

Angenommen , wir haben eine PersonService- Klasse mit zwei Methoden:

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

Beachten Sie, dass die erste Methode String und die zweite Integer zurückgibt .

4.1. Rückgabe des gleichen Wertes

Wir möchten eine einfache Proxy-Klasse erstellen, die einen Aufruf einer sayHello () -Methode abfängt . Mit der Enhancer- Klasse können wir einen Proxy erstellen, indem wir eine PersonService- Klasse mithilfe einer setSuperclass () -Methode aus der Enhancer- Klasse dynamisch erweitern :

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);

Der FixedValue ist eine Rückrufschnittstelle, die einfach den Wert von der Proxy-Methode zurückgibt. Das Ausführen der sayHello () -Methode auf einem Proxy gab einen in einer Proxy-Methode angegebenen Wert zurück.

4.2. Rückgabewert abhängig von einer Methodensignatur

Die erste Version unseres Proxys weist einige Nachteile auf, da wir nicht entscheiden können, welche Methode ein Proxy abfangen und welche Methode von einer Oberklasse aufgerufen werden soll. Wir können eine MethodInterceptor- Schnittstelle verwenden, um alle Aufrufe des Proxys abzufangen und zu entscheiden, ob Sie einen bestimmten Aufruf tätigen oder eine Methode aus einer Oberklasse ausführen möchten:

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 diesem Beispiel werden alle Aufrufe abgefangen, wenn die Methodensignatur nicht von der Object- Klasse stammt. Dies bedeutet, dass die Methoden toString () oder hashCode () nicht abgefangen werden. Außerdem fangen wir nur Methoden von einem PersonService ab , der einen String zurückgibt . Der Aufruf einer lengthOfName () -Methode wird nicht abgefangen, da der Rückgabetyp eine Ganzzahl ist.

5. Bean Creator

Ein weiteres nützliches Konstrukt aus der cglib ist eine BeanGenerator- Klasse. Es ermöglicht uns, Beans dynamisch zu erstellen und Felder zusammen mit Setter- und Getter-Methoden hinzuzufügen. Es kann von Codegenerierungswerkzeugen verwendet werden, um einfache POJO-Objekte zu generieren:

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. Mixin erstellen

Ein Mixin ist ein Konstrukt, mit dem mehrere Objekte zu einem kombiniert werden können. Wir können ein Verhalten einiger Klassen einschließen und dieses Verhalten als einzelne Klasse oder Schnittstelle verfügbar machen. Die cglib- Mixins ermöglichen die Kombination mehrerer Objekte zu einem einzigen Objekt. Dazu müssen jedoch alle Objekte, die in einem Mixin enthalten sind, durch Schnittstellen gesichert werden.

Angenommen, wir möchten eine Mischung aus zwei Schnittstellen erstellen. Wir müssen beide Schnittstellen und ihre Implementierungen definieren:

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"; } } 

Um Implementierungen von Interface1 und Interface2 zu erstellen, müssen wir eine Schnittstelle erstellen, die beide erweitert:

public interface MixinInterface extends Interface1, Interface2 { }

Mithilfe einer create () -Methode aus der Mixin- Klasse können wir Verhaltensweisen von Klasse1 und Klasse2 in ein MixinInterface aufnehmen:

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());

Durch das Aufrufen von Methoden für mixinDelegate werden Implementierungen von Class1 und Class2 aufgerufen .

7. Fazit

In diesem Artikel haben wir uns die cglib und ihre nützlichsten Konstrukte angesehen. Wir haben einen Proxy mit einer Enhancer- Klasse erstellt. Wir haben einen BeanCreator verwendet und schließlich ein Mixin erstellt , das Verhaltensweisen anderer Klassen enthält.

Cglib wird häufig vom Spring-Framework verwendet. Ein Beispiel für die Verwendung eines cglib-Proxys durch Spring ist das Hinzufügen von Sicherheitsbeschränkungen zu Methodenaufrufen. Anstatt eine Methode direkt aufzurufen, prüft Spring Security zunächst (über einen Proxy), ob eine bestimmte Sicherheitsprüfung bestanden wurde, und delegiert sie nur dann an die eigentliche Methode, wenn diese Überprüfung erfolgreich war. In diesem Artikel haben wir gesehen, wie ein solcher Proxy für unseren eigenen Zweck erstellt wird.

Die Implementierung all dieser Beispiele und Codefragmente finden Sie im GitHub-Projekt - dies ist ein Maven-Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.