En guide til Byte Buddy

1. Oversigt

Kort sagt, ByteBuddy er et bibliotek til generering af Java-klasser dynamisk ved kørselstid.

I denne aktuelle artikel skal vi bruge rammen til at manipulere eksisterende klasser, oprette nye klasser efter behov og endda opfange metodekald.

2. Afhængigheder

Lad os først tilføje afhængigheden af ​​vores projekt. For Maven-baserede projekter er vi nødt til at tilføje denne afhængighed til vores pom.xml:

 net.bytebuddy byte-ven 1.7.1 

For et Gradle-baseret projekt er vi nødt til at tilføje den samme artefakt til vores build.gradle fil:

kompilere net.bytebuddy: byte-kompis: 1.7.1

Den seneste version kan findes på Maven Central.

3. Oprettelse af en Java-klasse ved kørselstid

Lad os starte med at oprette en dynamisk klasse ved at underklasse en eksisterende klasse. Vi kigger på klassikeren Hej Verden projekt.

I dette eksempel opretter vi en type (Klasse) det er en underklasse af Objekt.klasse og tilsidesætte toString () metode:

DynamicType.Unloaded unloadedType = new ByteBuddy () .subclass (Object.class) .method (ElementMatchers.isToString ()). Intercept (FixedValue.value ("Hello World ByteBuddy!")) .Make ();

Hvad vi lige gjorde var at oprette en forekomst af ByteBuddy. Derefter brugte vi subclass () API at udvide Objekt.klasse, og vi valgte toString () af superklassen (Objekt.klasse) ved brug af ElementMatchers.

Endelig med aflytte () metode, leverede vi vores implementering af toString () og returner en fast værdi.

Det lave() metode udløser generationen af ​​den nye klasse.

På dette tidspunkt er vores klasse allerede oprettet, men ikke indlæst i JVM endnu. Det er repræsenteret af en forekomst af DynamicType.Uloaded, som er en binær form af den genererede type.

Derfor er vi nødt til at indlæse den genererede klasse i JVM, før vi kan bruge den:

Klasse dynamicType = unloadedType.load (getClass () .getClassLoader ()) .getLoaded ();

Nu kan vi sætte gang i dynamisk type og påberåbe sig toString () metode på det:

assertEquals (dynamicType.newInstance (). toString (), "Hello World ByteBuddy!");

Bemærk, at du ringer dynamicType.toString () fungerer ikke, da det kun påberåber sig toString () gennemførelse af ByteBuddy.class.

Det newInstance () er en Java-refleksionsmetode, der opretter en ny forekomst af den type, der er repræsenteret af denne ByteBuddy objekt; på en måde, der ligner brugen af ny nøgleord med en no-arg konstruktør.

Indtil videre har vi kun været i stand til at tilsidesætte en metode i superklassen af ​​vores dynamiske type og returnere vores egen faste værdi. I de næste sektioner vil vi se på at definere vores metode med brugerdefineret logik.

4. Metodedelegering og brugerdefineret logik

I vores tidligere eksempel returnerer vi en fast værdi fra toString () metode.

I virkeligheden kræver applikationer mere kompleks logik end dette. En effektiv måde at lette og klargøre brugerdefineret logik til dynamiske typer er delegering af metodeopkald.

Lad os oprette en dynamisk type, der underklasser Foo.klasse som har sigeHelloFoo () metode:

public String sayHelloFoo () {return "Hej i Foo!"; }

Lad os desuden oprette en anden klasse Bar med en statisk sayHelloBar () af samme signatur og returneringstype som sigeHelloFoo ():

public static String sayHelloBar () {returner "Holla in Bar!"; }

Lad os nu delegere alle påkald af sigeHelloFoo () til sayHelloBar () ved brug af ByteBuddy'S DSL. Dette giver os mulighed for at levere tilpasset logik, skrevet i ren Java, til vores nyoprettede klasse ved kørsel:

Streng r = ny ByteBuddy () .subclass (Foo.class) .metode (navngivet ("sayHelloFoo"). Og (isDeclaredBy (Foo.class). Og (returnerer (String.class)))). Intercept (MethodDelegation.to (Bar.class)) .make () .load (getClass (). GetClassLoader ()) .getLoaded () .newInstance () .sayHelloFoo (); assertEquals (r, Bar.sayHelloBar ());

Påberåbe sig sigeHelloFoo () vil påberåbe sig sayHelloBar () derfor.

Hvordan gør det? ByteBuddy vide hvilken metode i Bar.klasse at påberåbe sig? Det vælger en matchende metode i henhold til metodesignaturen, returtypen, metodens navn og kommentarerne.

Det sigeHelloFoo () og sayHelloBar () metoder har ikke det samme navn, men de har samme metodesignatur og returtype.

Hvis der er mere end en påkaldelig metode i Bar. Klasse med matchende signatur og returtype kan vi bruge @BindingPriority kommentar for at løse tvetydigheden.

@BindingPriority tager et heltalsargument - jo højere heltalværdien er, desto højere prioritet kalder den bestemte implementering. Dermed, sayHelloBar () vil blive foretrukket frem for sayBar () i kodestykket nedenfor:

@BindingPriority (3) offentlig statisk streng sayHelloBar () {returner "Holla i Bar!"; } @BindingPriority (2) offentlig statisk streng sayBar () {return "bar"; }

5. Metode og feltdefinition

Vi har været i stand til at tilsidesætte metoder, der er erklæret i superklassen af ​​vores dynamiske typer. Lad os gå videre ved at tilføje en ny metode (og et felt) til vores klasse.

Vi bruger Java-refleksion til at påkalde den dynamisk oprettede metode:

Klassetype = ny ByteBuddy () .subclass (Object.class) .name ("MyClassName") .defineMethod ("custom", String.class, Modifier.PUBLIC) .intercept (MethodDelegation.to (Bar.class)) .defineField ("x", String.class, Modifier.PUBLIC) .make () .load (getClass (). getClassLoader (), ClassLoadingStrategy.Default.WRAPPER) .getLoaded (); Metode m = type.getDeclaredMethod ("brugerdefineret", null); assertEquals (m.invoke (type.newInstance ()), Bar.sayHelloBar ()); assertNotNull (type.getDeclaredField ("x"));

Vi oprettede en klasse med navnet MyClassName det er en underklasse af Objekt.klasse. Vi definerer derefter en metode, brugerdefinerede, der returnerer en Snor og har en offentlig adgangsmodifikator.

Ligesom vi gjorde i tidligere eksempler implementerede vi vores metode ved at opfange opkald til den og delegere dem til Bar.klasse som vi oprettede tidligere i denne vejledning.

6. Omdefinering af en eksisterende klasse

Selvom vi har arbejdet med dynamisk oprettede klasser, kan vi også arbejde med allerede indlæste klasser. Dette kan gøres ved at omdefinere (eller ombasere) eksisterende klasser og bruge ByteBuddyAgent at genindlæse dem i JVM.

Lad os først tilføje ByteBuddyAgent til vores pom.xml:

 net.bytebuddy byte-kompis-agent 1.7.1 

Den seneste version kan findes her.

Lad os nu omdefinere sigeHelloFoo () metode, vi oprettede i Foo.klasse tidligere:

ByteBuddyAgent.install (); ny ByteBuddy () .redefine (Foo.class) .method (navngivet ("sayHelloFoo")). intercept (FixedValue.value ("Hello Foo Redefined")) .make () .load (Foo.class.getClassLoader (), ClassReloadingStrategy.fromInstalledAgent ()); Foo f = ny Foo (); assertEquals (f.sayHelloFoo (), "Hello Foo Redefined");

7. Konklusion

I denne udførlige vejledning har vi undersøgt grundigt mulighederne i ByteBuddy bibliotek og hvordan man bruger det til effektiv oprettelse af dynamiske klasser.

Dokumentationen giver en grundig forklaring af det indre arbejde og andre aspekter af biblioteket.

Og som altid kan de komplette kodestykker til denne vejledning findes på Github.