Sprievodca po Byte Buddy

1. Prehľad

Zjednodušene povedané, ByteBuddy je knižnica na dynamické generovanie tried Java za behu.

V tomto bodovom článku budeme používať rámec na manipuláciu s existujúcimi triedami, vytváranie nových tried na požiadanie a dokonca na zachytávanie volaní metód.

2. Závislosti

Najprv pridajme závislosť do nášho projektu. Pre projekty založené na Maven musíme túto závislosť pridať k našej pom.xml:

 net.bytebuddy byte-buddy 1.7.1 

Pre projekt založený na Gradle musíme do nášho pridať rovnaký artefakt build.gradle spis:

zostaviť net.bytebuddy: byte-buddy: 1.7.1

Najnovšiu verziu nájdete na serveri Maven Central.

3. Vytvorenie triedy Java za behu programu

Začnime vytvorením dynamickej triedy podtriedou existujúcej triedy. Pozrime sa na klasiku Ahoj svet projekt.

V tomto príklade vytvoríme typ (Trieda), čo je podtrieda Object.class a prepísať natiahnuť() metóda:

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

Čo sme práve urobili, bolo vytvoriť inštanciu ByteBuddy. Potom sme použili podtrieda () API predĺžiť Object.classa vybrali sme natiahnuť() super triedy (Object.class) použitím ElementMatchers.

Nakoniec s zachytiť () metódou sme zabezpečili našu implementáciu natiahnuť() a vrátiť pevnú hodnotu.

The urobiť() metóda spúšťa generovanie novej triedy.

V tomto okamihu je naša trieda už vytvorená, ale ešte nie je načítaná do JVM. Predstavuje to inštanciu DynamicType. Neuložené, čo je binárna forma generovaného typu.

Preto musíme vygenerovanú triedu načítať do JVM, než ju budeme môcť použiť:

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

Teraz môžeme vytvoriť inštanciu dynamicType a vyvolať natiahnuť() metóda na to:

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

Všimnite si, že volanie dynamicType.toString () nebude fungovať, pretože to vyvolá iba natiahnuť() implementácia ByteBuddy.class.

The newInstance () je Java reflexná metóda, ktorá vytvára novú inštanciu typu reprezentovaného týmto ByteBuddy predmet; podobným spôsobom ako pri použití Nový kľúčové slovo s konštruktorom no-arg.

Doteraz sme boli schopní prepísať iba metódu v super triede nášho dynamického typu a vrátiť našu pevnú hodnotu. V ďalších častiach sa pozrieme na definovanie našej metódy pomocou vlastnej logiky.

4. Delegovanie metódy a vlastná logika

V našom predchádzajúcom príklade vrátime pevnú hodnotu z natiahnuť() metóda.

V skutočnosti si aplikácie vyžadujú zložitejšiu logiku. Jedným z efektívnych spôsobov uľahčenia a zabezpečenia vlastnej logiky pre dynamické typy je delegovanie volaní metód.

Vytvorme dynamický typ, ktorý podtriedy Foo.trieda ktorý má sayHelloFoo () metóda:

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

Ďalej vytvorme ďalšiu triedu Bar so statickým sayHelloBar () rovnakého typu podpisu a návratu ako sayHelloFoo ():

public static Reťazec sayHelloBar () {návrat "Holla v bare!"; }

Teraz poďme delegovať všetky vyvolania z sayHelloFoo () do sayHelloBar () použitím ByteBuddyDSL. To nám umožňuje poskytovať vlastnú logiku napísanú v čistej Jave pre našu novo vytvorenú triedu za behu:

Reťazec r = nový ByteBuddy () .subclass (Foo.class) .method (named ("sayHelloFoo") .and (isDeclaredBy (Foo.class) .and (returns (String.class)))) .intercept (MethodDelegation.to (Bar.class)) .make () .load (getClass (). GetClassLoader ()) .getLoaded () .newInstance () .sayHelloFoo (); assertEquals (r, Bar.sayHelloBar ());

Vyvolávanie sayHelloFoo () vyvolá sayHelloBar () podľa toho.

Ako to robí? ByteBuddy vedieť, ktorá metóda v Bar.trieda dovolať sa? Vyberá metódu zhody podľa podpisu metódy, typu návratu, názvu metódy a anotácií.

The sayHelloFoo () a sayHelloBar () metódy nemajú rovnaký názov, ale majú rovnaký podpis a návratový typ metódy.

Ak existuje viac ako jeden spôsob fakturácie v Bar.trieda so zodpovedajúcim typom podpisu a návratu, môžeme použiť @BindingPriority anotáciu na vyriešenie nejasnosti.

@BindingPriority berie celočíselný argument - čím vyššia je celočíselná hodnota, tým vyššia je priorita volania konkrétnej implementácie. Teda sayHelloBar () budú uprednostňované pred sayBar () v útržku kódu nižšie:

@BindingPriority (3) public static String sayHelloBar () {return "Holla in Bar!"; } @BindingPriority (2) public static String sayBar () {return "bar"; }

5. Metóda a definícia poľa

Dokázali sme prepísať metódy deklarované v super triede našich dynamických typov. Poďme ďalej a do našej triedy pridáme novú metódu (a pole).

Na vyvolanie dynamicky vytvorenej metódy použijeme reflexiu Java:

Typ triedy = nový 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 (); Metóda m = type.getDeclaredMethod ("custom", null); assertEquals (m.invoke (type.newInstance ()), Bar.sayHelloBar ()); assertNotNull (type.getDeclaredField ("x"));

Vytvorili sme triedu s názvom MyClassName to je podtrieda Object.class. Potom definujeme metódu, zvyk, že vráti a String a má verejné modifikátor prístupu.

Rovnako ako v predchádzajúcich príkladoch, aj my sme implementovali našu metódu tým, že sme zachytili volania na ňu a delegovali ich na Bar.trieda ktoré sme vytvorili skôr v tomto návode.

6. Predefinovanie existujúcej triedy

Aj keď sme pracovali s dynamicky vytvorenými triedami, môžeme pracovať aj s už načítanými triedami. To je možné vykonať predefinovaním (alebo rebasingom) existujúcich tried a použitím ByteBuddyAgent znovu ich načítať do JVM.

Najprv dodajme ByteBuddyAgent k nášmu pom.xml:

 net.bytebuddy byte-buddy-agent 1.7.1 

Najnovšiu verziu nájdete tu.

Teraz poďme predefinovať sayHelloFoo () metóda, ktorú sme vytvorili v Foo.trieda skôr:

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

7. Záver

V tomto prepracovanom sprievodcovi sme sa podrobne zaoberali možnosťami ByteBuddy knižnica a ako ju používať na efektívne vytváranie dynamických tried.

Jeho dokumentácia ponúka podrobné vysvetlenie vnútorného fungovania a ďalších aspektov knižnice.

A ako vždy, úplné útržky kódu pre tento tutoriál nájdete na Githube.


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