Vodič za Byte Buddyja

1. Pregled

Jednostavno rečeno, ByteBuddy je knjižnica za dinamičko generiranje Java klasa tijekom izvođenja.

U ovom detaljnom članku koristit ćemo okvir za manipulaciju postojećim klasama, stvaranje novih klasa na zahtjev, pa čak i presretanje poziva metoda.

2. Ovisnosti

Prvo dodajmo ovisnost našem projektu. Za projekte temeljene na Mavenu, ovu ovisnost moramo dodati našoj pom.xml:

 net.bytebuddy byte-prijatelj 1.7.1 

Za projekt temeljen na Gradleu moramo dodati isti artefakt našem graditi.gradle datoteka:

prevesti net.bytebuddy: byte-buddy: 1.7.1

Najnoviju verziju možete pronaći na Maven Central.

3. Stvaranje Java klase u vrijeme izvođenja

Počnimo s stvaranjem dinamičke klase potklasiranjem postojeće klase. Pogledat ćemo klasiku Pozdrav svijete projekt.

U ovom primjeru kreiramo tip (Razred) to je podrazred od Objekt.razred i poništiti toString () metoda:

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

Ono što smo upravo učinili bilo je stvoriti primjerak ByteBuddy. Zatim smo koristili API podklase () proširiti Objekt.razred, a mi smo odabrali toString () super klase (Objekt.razred) pomoću ElementMatchers.

Napokon, s presresti () metodu, osigurali smo našu implementaciju toString () i vratiti fiksnu vrijednost.

The napraviti() metoda pokreće generiranje nove klase.

U ovom je trenutku naša klasa već stvorena, ali još nije učitana u JVM. Zastupljen je primjerom DynamicType.Nepreuzeto, koji je binarni oblik generiranog tipa.

Stoga generiranu klasu moramo učitati u JVM prije nego što je možemo koristiti:

Klasa dynamicType = neopterećenTip.load (getClass () .getClassLoader ()) .getLoaded ();

Sada možemo instancirati dynamicType i zazovite toString () metoda na njemu:

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

Imajte na umu da pozivanje dynamicType.toString () neće raditi jer će se samo pozivati toString () provedba ByteBuddy.class.

The newInstance () je metoda refleksije Java koja stvara novu instancu tipa predstavljenog ovim ByteBuddy objekt; na način sličan korištenju novi ključna riječ s no-arg konstruktorom.

Do sada smo uspjeli poništiti metodu samo u super klasi našeg dinamičkog tipa i vratiti vlastitu fiksnu vrijednost. U sljedećim ćemo odjeljcima pogledati definiranje naše metode s prilagođenom logikom.

4. Delegiranje metoda i prilagođena logika

U našem prethodnom primjeru vraćamo fiksnu vrijednost iz toString () metoda.

U stvarnosti, aplikacije zahtijevaju složeniju logiku od ove. Jedan od učinkovitih načina olakšavanja i omogućavanja prilagođene logike dinamičkim tipovima je delegiranje poziva metode.

Stvorimo dinamički tip koji se podrazredi Foo.razred koja ima sayHelloFoo () metoda:

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

Nadalje, stvorimo još jedan razred Bar sa statičkim sayHelloBar () istog tipa potpisa i povrata kao sayHelloFoo ():

javni statični niz sayHelloBar () {return "Holla u baru!"; }

Ajmo sada delegirati sve pozive na sayHelloFoo () do sayHelloBar () koristeći ByteBuddy‘S DSL. To nam omogućuje pružanje prilagođene logike, napisane na čistoj Javi, našoj novostvorenoj klasi tijekom izvođenja:

Niz r = novi ByteBuddy (). Podrazred (Foo.class) .metoda (imenovana ("sayHelloFoo"). I (isDeclaredBy (Foo.class) .and (vraća (String.class)))). Intercept (MethodDelegation.to (Bar.class)) .make () .load (getClass (). GetClassLoader ()) .getLoaded () .newInstance () .sayHelloFoo (); assertEquals (r, Bar.sayHelloBar ());

Pozivanje na sayHelloFoo () pozvat će se na sayHelloBar () prema tome.

Kako ByteBuddy znati koja metoda u Bar.razred zazivati? Odabire odgovarajuću metodu prema potpisu metode, povratnom tipu, nazivu metode i bilješkama.

The sayHelloFoo () i sayHelloBar () metode nemaju isto ime, ali imaju isti potpis metode i tip povratka.

Ako postoji više od jedne metode koja se može pozvati Bar.razred s odgovarajućim tipom potpisa i povratka, možemo koristiti @BindingPriority napomena za rješavanje dvosmislenosti.

@BindingPriority uzima cjelobrojni argument - što je veća cjelobrojna vrijednost, to je veći prioritet pozivanja određene implementacije. Tako, sayHelloBar () bit će preferirani nad sayBar () u isječku koda ispod:

@BindingPriority (3) javni statički niz sayHelloBar () {return "Holla u baru!"; } @BindingPriority (2) javni statički niz sayBar () {return "bar"; }

5. Definicija metode i polja

Uspjeli smo nadjačati metode deklarirane u super klasi naših dinamičkih tipova. Idemo dalje dodavanjem nove metode (i polja) u naš razred.

Koristit ćemo Java refleksiju za pozivanje dinamički stvorene metode:

Vrsta klase = novi ByteBuddy (). Podrazred (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 (); Metoda m = type.getDeclaredMethod ("custom", null); assertEquals (m.invoke (type.newInstance ()), Bar.sayHelloBar ()); assertNotNull (type.getDeclaredField ("x"));

Napravili smo razred s imenom MyClassName to je podrazred od Objekt.razred. Zatim definiramo metodu, prilagođen, koji vraća a Niz i ima a javnost modifikator pristupa.

Baš kao i u prethodnim primjerima, implementirali smo našu metodu presretanjem poziva i delegiranjem na njih Bar.razred koje smo stvorili ranije u ovom vodiču.

6. Redefiniranje postojeće klase

Iako smo radili s dinamički stvorenim klasama, možemo raditi i s već učitanim klasama. To se može postići redefiniranjem (ili prebaziranjem) postojećih klasa i njihovim korištenjem ByteBuddyAgent da ih ponovo učita u JVM.

Prvo, dodajmo ByteBuddyAgent našem pom.xml:

 net.bytebuddy byte-buddy-agent 1.7.1 

Najnoviju verziju možete pronaći ovdje.

Idemo redefinirati sayHelloFoo () metoda u kojoj smo stvorili Foo.razred ranije:

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

7. Zaključak

U ovom detaljnom vodiču detaljno smo proučili mogućnosti ByteBuddy knjižnica i kako je koristiti za učinkovito stvaranje dinamičkih klasa.

Njegova dokumentacija nudi detaljno objašnjenje unutarnjeg rada i drugih aspekata knjižnice.

Kao i uvijek, cjelovite isječke koda za ovaj vodič možete pronaći na Githubu.