En guide til Byte Buddy

1. Oversikt

Enkelt sagt, ByteBuddy er et bibliotek for å generere Java-klasser dynamisk i løpetid.

I denne aktuelle artikkelen skal vi bruke rammeverket til å manipulere eksisterende klasser, opprette nye klasser etter behov, og til og med avlytte metodeanrop.

2. Avhengigheter

La oss først legge til avhengigheten til prosjektet vårt. For Maven-baserte prosjekter, må vi legge til denne avhengigheten til våre pom.xml:

 net.bytebuddy byte-kompis 1.7.1 

For et Gradle-basert prosjekt, må vi legge til den samme gjenstanden til vår build.gradle fil:

kompilere net.bytebuddy: byte-kompis: 1.7.1

Den siste versjonen finner du på Maven Central.

3. Opprette en Java-klasse ved kjøretid

La oss starte med å lage en dynamisk klasse ved å underklasse en eksisterende klasse. Vi tar en titt på klassikeren Hei Verden prosjekt.

I dette eksemplet lager vi en type (Klasse) som er en underklasse av Objekt.klasse og overstyre toString () metode:

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

Det vi nettopp gjorde var å lage en forekomst av ByteBuddy. Så brukte vi API for underklasse () å forlenge Objekt.klasse, og vi valgte toString () av superklassen (Objekt.klasse) ved hjelp av ElementMatchers.

Til slutt, med avskjære() metoden, ga vi vår implementering av toString () og returner en fast verdi.

De gjøre() metode utløser generasjonen av den nye klassen.

På dette tidspunktet er klassen vår allerede opprettet, men ikke lastet inn i JVM ennå. Det er representert av en forekomst av DynamicType. Ulastet, som er en binær form av den genererte typen.

Derfor må vi laste den genererte klassen inn i JVM før vi kan bruke den:

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

Nå kan vi sette i gang dynamisk type og påkalle toString () metode på det:

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

Legg merke til at du ringer dynamicType.toString () vil ikke fungere siden det bare vil påkalle toString () Implementering av ByteBuddy.class.

De newInstance () er en Java-refleksjonsmetode som skaper en ny forekomst av typen representert av dette ByteBuddy gjenstand; på en måte som ligner på å bruke ny nøkkelord med en no-arg konstruktør.

Så langt har vi bare klart å overstyre en metode i superklassen av vår dynamiske type og returnere vår egen faste verdi. I de neste avsnittene vil vi se på å definere metoden vår med tilpasset logikk.

4. Metodedelegering og egendefinert logikk

I vårt forrige eksempel returnerer vi en fast verdi fra toString () metode.

I virkeligheten krever applikasjoner mer kompleks logikk enn dette. En effektiv måte å legge til rette for og klargjøre tilpasset logikk til dynamiske typer er delegering av metodeanrop.

La oss lage en dynamisk type som underklasser Foo.klasse som har siHelloFoo () metode:

public String sayHelloFoo () {return "Hello in Foo!"; }

Videre, la oss lage en annen klasse Bar med en statisk siHelloBar () av samme signatur og returtype som siHelloFoo ():

offentlig statisk streng siHelloBar () {return "Holla in Bar!"; }

La oss delegere alle påkallelser av siHelloFoo () til siHelloBar () ved hjelp av ByteBuddy‘S DSL. Dette lar oss tilby tilpasset logikk, skrevet i ren Java, til vår nyopprettede klasse ved kjøretid:

Streng r = ny ByteBuddy () .subclass (Foo.class) .metode (kalt ("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åkaller siHelloFoo () vil påkalle siHelloBar () tilsvarende.

Hvordan gjør ByteBuddy vet hvilken metode i Bar.klasse å påkalle? Den velger en samsvarende metode i henhold til metodesignaturen, returtypen, metodens navn og merknader.

De siHelloFoo () og siHelloBar () metoder har ikke samme navn, men de har samme metodesignatur og returtype.

Hvis det er mer enn en invokabel metode i Bar.klasse med matchende signatur og returtype, kan vi bruke @BindingPriority kommentar for å løse tvetydigheten.

@BindingPriority tar et heltalsargument - jo høyere helverdien er, desto høyere prioritet har det å ringe den spesielle implementeringen. Og dermed, siHelloBar () vil bli foretrukket fremfor sayBar () i kodebiten nedenfor:

@BindingPriority (3) offentlig statisk streng siHelloBar () {return "Holla in Bar!"; } @BindingPriority (2) offentlig statisk streng sayBar () {return "bar"; }

5. Metode og feltdefinisjon

Vi har vært i stand til å overstyre metoder som er erklært i superklassen av våre dynamiske typer. La oss gå lenger ved å legge til en ny metode (og et felt) i klassen vår.

Vi vil bruke Java-refleksjon for å påkalle den dynamisk opprettede metoden:

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 ("tilpasset", null); assertEquals (m.invoke (type.newInstance ()), Bar.sayHelloBar ()); assertNotNull (type.getDeclaredField ("x"));

Vi opprettet en klasse med navnet MyClassName det er en underklasse av Objekt.klasse. Vi definerer deretter en metode, tilpasset, som returnerer a String og har en offentlig tilgangsmodifikator.

Akkurat som vi gjorde i tidligere eksempler, implementerte vi metoden vår ved å avlytte samtaler til den og delegere dem til Bar.klasse som vi opprettet tidligere i denne opplæringen.

6. Omdefinere en eksisterende klasse

Selv om vi har jobbet med dynamisk opprettede klasser, kan vi også jobbe med allerede lastede klasser. Dette kan gjøres ved å omdefinere (eller ombasere) eksisterende klasser og bruke ByteBuddyAgent å laste dem inn i JVM.

La oss først legge til ByteBuddyAgent til vår pom.xml:

 net.bytebuddy byte-kompis-agent 1.7.1 

Den siste versjonen finner du her.

La oss nå omdefinere siHelloFoo () metoden vi opprettet i Foo.klasse Tidligere:

ByteBuddyAgent.install (); ny ByteBuddy () .redefine (Foo.class) .metode (kalt ("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. Konklusjon

I denne forseggjorte guiden har vi sett grundig på funksjonene til ByteBuddy biblioteket og hvordan du bruker det til effektiv opprettelse av dynamiske klasser.

Dokumentasjonen gir en grundig forklaring på det indre arbeidet og andre aspekter ved biblioteket.

Og som alltid kan du finne de komplette kodebitene for denne opplæringen på Github.


$config[zx-auto] not found$config[zx-overlay] not found