Obsah
1. Pohled pod kapotu JVM – přímé generování instrukcí bajtkódu s využitím nástroje Javassist (2)
2. Sekvence instrukcí pro vypsání řetězce „Hello world!“
3. Základ demonstračního příkladu: metoda constructMethodHello()
4. Metody prepareMethod() a setCodeAttributeForMethod()
5. Další součást demonstračního příkladu: abstraktní třída BytecodeGenerator
6. První způsob výpisu řetězce – využití Bytecode.addPrintln()
8. Třetí způsob výpisu řetězce – použití třídy CtClass namísto signatur tříd a metod
9. Čtvrtý způsob výpisu řetězce – převod instance třídy Class na CtClass
10. Spuštění metod právě vytvořené třídy ve stejném virtuálním stroji
11. Zdrojový kód demonstračního příkladu ClassGenerationTest8
12. Výpis bajtkódu vygenerovaného demonstračním příkladem ClassGenerationTest8
13. Repositář se zdrojovými kódy dnešního demonstračního příkladu
1. Pohled pod kapotu JVM – přímé generování instrukcí bajtkódu s využitím nástroje Javassist (2)
V předchozí části seriálu o programovacím jazyce Java i o virtuálním stroji Javy jsme si vysvětlili základní způsob tvorby bajtkódu metod s využitím tříd javassist.bytecode.Bytecode a javassist.bytecode.Opcode. Připomeňme si, že v první třídě se nachází mnoho metod určených pro přidávání nových instrukcí do bajtkódu zvolené metody (přesněji řečeno do bytového pole tvořícího hodnotu atributu „Code“). Jednotlivé instrukční kódy se samozřejmě nemusí zadávat ručně, protože jsou uloženy jako konstanty v již zmíněné třídě javassist.bytecode.Opcode. Minule jsme si ukázali, jak lze s využitím této dvojice tříd vytvořit jednoduchou metodu, která buď vrátí celočíselnou konstantu nebo nejprve sečte hodnotu svých dvou parametrů a vrátí výsledek tohoto součtu. Podobným způsobem lze zkonstruovat i metody se složitějšími výpočty, které mohou využívat hodnoty libovolného primitivního datového typu (byte, short, int, long, float, double, boolean či char).
Dnes si ukážeme způsob volání nestatické metody, přesněji řečeno způsob konstrukce sekvence instrukcí použitých pro volání metody. To je již poněkud komplikovanější činnost, neboť vzhledem k vlastnostem virtuálního stroje Javy je při volání metody vždy použito plně kvalifikované jméno třídy i signatura metody – nepoužívají se tedy adresy, ať již v absolutní či relativní podobě. I když se může nepřímý způsob volání metod s využitím jména třídy a signatury metody znát poněkud komplikovaný či zdlouhavý, umožňuje relativně snadnou implementaci záměny tříd (různé verze třídy od jiného dodavatele, třída upravená pomocí Javassistu), různé manipulace s classloadery atd. Komplikovanost volání je tedy vyvážena většími možnostmi v porovnání s pouhým skokem na určenou adresu (druhým neméně důležitým důvodem je fakt, že většina metod je považována za metody virtuální).
2. Sekvence instrukcí pro vypsání řetězce „Hello world!“
V demonstračním příkladu, který bude postupně popsán v navazujících kapitolách, se bude tvořit bajtkód metod, jejichž jediným úkolem je vypsání řetězce na standardní či chybový výstup. Nejjednodušeji je tisk řetězce realizován (přetíženými) metodami System.out.println() a System.err.println(), přičemž první metoda slouží pro tisk na standardní výstup a metoda druhá pro tisk na výstup chybový. Podívejme se nyní, jakým způsobem se vlastně přeloží volání těchto dvou zmíněných metod. Po přeložení následující testovací třídy:
public class Test { static { System.out.println("Hello world!"); System.err.println("Hello world!"); System.exit(0); } }
příkazem:
javac Test
vznikne, jak zajisté čtenáři tohoto článku vědí, soubor Test.class obsahující mj. i bajtkód s instrukcemi tvořícími tělo statického bloku (ten se „spustí“ po načtení třídy do JVM). Obsah souboru Test.class můžeme snadno dekompilovat příkazem:
javap -c -v Test
Podívejme se nyní na výstup získaný tímto příkazem:
Compiled from "Test.java" public class Test extends java.lang.Object SourceFile: "Test.java" minor version: 0 major version: 50 Constant pool: const #1 = Method #8.#16; // java/lang/Object."<init>":()V const #2 = Field #17.#18; // java/lang/System.out:Ljava/io/PrintStream; const #3 = String #19; // Hello world! const #4 = Method #20.#21; // java/io/PrintStream.println:(Ljava/lang/String;)V const #5 = Field #17.#22; // java/lang/System.err:Ljava/io/PrintStream; const #6 = Method #17.#23; // java/lang/System.exit:(I)V const #7 = class #24; // Test const #8 = class #25; // java/lang/Object const #9 = Asciz <init>; const #10 = Asciz ()V; const #11 = Asciz Code; const #12 = Asciz LineNumberTable; const #13 = Asciz <clinit> const #14 = Asciz SourceFile; const #15 = Asciz Test.java; const #16 = NameAndType #9:#10; // "<init>":()V const #17 = class #26; // java/lang/System const #18 = NameAndType #27:#28; // out:Ljava/io/PrintStream; const #19 = Asciz Hello world!; const #20 = class #29; // java/io/PrintStream const #21 = NameAndType #30:#31; // println:(Ljava/lang/String;)V const #22 = NameAndType #32:#28; // err:Ljava/io/PrintStream; const #23 = NameAndType #33:#34; // exit:(I)V const #24 = Asciz Test; const #25 = Asciz java/lang/Object; const #26 = Asciz java/lang/System; const #27 = Asciz out; const #28 = Asciz Ljava/io/PrintStream;; const #29 = Asciz java/io/PrintStream; const #30 = Asciz println; const #31 = Asciz (Ljava/lang/String;)V; const #32 = Asciz err; const #33 = Asciz exit; const #34 = Asciz (I)V; { public Test(); Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 static {}; Code: Stack=2, Locals=0, Args_size=0 0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3; //String Hello world! 5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: getstatic #5; //Field java/lang/System.err:Ljava/io/PrintStream; 11: ldc #3; //String Hello world! 13: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 16: iconst_0 17: invokestatic #6; //Method java/lang/System.exit:(I)V 20: return LineNumberTable: line 3: 0 line 4: 8 line 5: 16 line 6: 20 }
Nejzajímavější je v tuto chvíli zjištění, jak je realizováno volání metody System.out.println(). Jedná se o nestatickou metodu se signaturou „(Ljava/lang/String;)V“ ze třídy java.io.PrintStream. V bajtkódu je nejdříve nutné s využitím instrukce getstatic získat statický atribut nazvaný „out“ ze třídy java.lang.System (což je instance java.io.PrintStream) a uložit tuto referenci na zásobník. Následně je nutné uložit na zásobník odkaz (referenci) na řetězec a poslední (třetí) instrukcí je zavolání příslušné metody – jedná se o instrukci invokevirtual. Řetězec (reference) je přitom použit jako parametr metody. Všechny tři instrukce přitom používají index do constant poolu:
const #2 = Field #17.#18; // java/lang/System.out:Ljava/io/PrintStream; const #3 = String #19; // Hello world! const #4 = Method #20.#21; // java/io/PrintStream.println:(Ljava/lang/String;)V getstatic #2; // Field java/lang/System.out:Ljava/io/PrintStream; ldc #3; // String Hello world! invokevirtual #4; // Method java/io/PrintStream.println:(Ljava/lang/String;)V
Zcela stejným způsobem je realizováno volání metody System.err.println():
const #5 = Field #17.#22; // java/lang/System.err:Ljava/io/PrintStream; const #3 = String #19; // Hello world! const #4 = Method #20.#21; // java/io/PrintStream.println:(Ljava/lang/String;)V getstatic #5; // Field java/lang/System.err:Ljava/io/PrintStream; ldc #3; // String Hello world! invokevirtual #4; // Method java/io/PrintStream.println:(Ljava/lang/String;)V
3. Základ demonstračního příkladu: metoda constructMethodHello()
V následujících kapitolách si popíšeme čtyři varianty konstrukce bajtkódu, jehož úkolem bude vypsat řetězec na standardní či na chybový výstup. Vzhledem k tomu, že základ konstrukce nové metody je vždy stejný, můžeme kvůli zjednodušení zdrojového kódu dnešního demonstračního příkladu provést (oproti příkladům popsaným minule) několik změn. První změna se týká toho, že metody v nově vytvářené třídě se budou konstruovat v jediné společné metodě s názvem constructMethodHello. Této metodě se předá odkaz na právě vytvářenou třídu (je typu CtClass), dále pak jméno vytvářené metody a konečně třetím parametrem této metody je třída představující vlastní generátor bajtkódu (viz též kapitolu číslo 5 s podrobnějším vysvětlením). Z následujícího výpisu je patrné, že se nejdříve vytvoří kostra nové prázdné metody (prepareMethod) a následně se k této metodě přiřadí atribut „Code“ obsahující bajtkód představující tělo této metody:
/** * Vytvoreni metody public static void hello*(). * * @param generatedClass * predstavuje vytvarenou tridu * @param methodName * jmeno vytvarene metody * @param generator * generator bajtkodu vytvarene metody * @throws CannotCompileException * vyhozena v pripade chyby ve zdrojovem kodu nebo strukturalni chyby v bajtkodu * @throws NotFoundException * vyhozena v pripade, ze nebyla nalezena nektera pomocna trida */ private static void constructMethodHello(CtClass generatedClass, String methodName, BytecodeGenerator generator) throws CannotCompileException, NotFoundException { MethodInfo methodInfo = prepareMethod(generatedClass, methodName); ConstPool constPool = methodInfo.getConstPool(); Bytecode bytecode = generator.generateBytecode(constPool); setCodeAttributeForMethod(methodInfo, bytecode); }
4. Metody prepareMethod() a setCodeAttributeForMethod()
Vytvoření metody bez přiřazeného atributu „Code“ je v případě nástroje Javassist velmi jednoduché. Kostra metody se vytvoří konstruktorem new CtMethod(), kterému se předá návratový typ metody, jméno metody, typy všech parametrů metody i třída, do níž bude metoda vložena. Následně se metodě přiřadí příslušné příznaky, zde konkrétně příznak statické metody a příznak pro metodu veřejnou. Posledním úkonem je vložení metody do její třídy s využitím volání CtClass.addMethod():
/** * Vytvoreni kostry metody a vraceni objektu typu MethodInfo. * * @param generatedClass * predstavuje vytvarenou tridu * @param methodName * jmeno vytvarene metody * @return * @throws CannotCompileException * vyhozena v pripade chyby ve zdrojovem kodu nebo strukturalni chyby v bajtkodu */ private static MethodInfo prepareMethod(CtClass generatedClass, String methodName) throws CannotCompileException { CtClass returnType = CtClass.voidType; CtClass[] parameterTypes = {}; // u metody je nutne znat jeji jmeno, navratovou hodnotu i typy parametru CtMethod helloMethod = new CtMethod(returnType, methodName, parameterTypes, generatedClass); // zmena modifikatoru helloMethod.setModifiers(Modifier.STATIC | Modifier.PUBLIC); // pridani metody do tridy generatedClass.addMethod(helloMethod); // ziskani informace o metoda MethodInfo methodInfo = helloMethod.getMethodInfo(); return methodInfo; }
Pokud již máme k dispozici bajtkód nové metody (to je úkol představený v dalších kapitolách), je přiřazení tohoto bajtkódu k metodě taktéž velmi jednoduché, což snadno zjistíme při pohledu na kód další uživatelské metody nazvané příznačně setCodeAttributeForMethod():
/** * Nastaveni atributu "Code" s bajktodem pro vybranou metodu. * * @param methodInfo * objekt typu MethodInfo * @param bytecode * bajtkod vytvarene metody. */ private static void setCodeAttributeForMethod(MethodInfo methodInfo, Bytecode bytecode) { CodeAttribute codeAttribute = bytecode.toCodeAttribute(); methodInfo.setCodeAttribute(codeAttribute); }
5. Další součást demonstračního příkladu: abstraktní třída BytecodeGenerator
Ve třetí kapitole jsme si řekli, že se všechny čtyři varianty metody typu void helloWorld() budou tvořit prakticky stejným způsobem, bude se lišit pouze způsob generování jejich bajtkódu. Aby bylo možné tuto funkcionalitu zaručit, předává se ve třetím parametru uživatelské metody constructMethodHello() (uvedené právě ve třetí kapitole) odkaz na instanci třídy BytecodeGenerator, resp. přesněji řečeno odkaz na instanci některého potomka této třídy, protože ve skutečnosti je BytecodeGenerator třídou čistě abstraktní (mohlo by se jednat i o rozhraní, nenapadl mě však žádný vhodný název pro toto rozhraní, proto jsem zůstal u abstraktní třídy :-). Ve třídě BytecodeGenerator je předepsána pouze jediná metoda nazvaná generateBytecode(), které se předá odkaz na constant pool a návratovou hodnotou je bajtkód některé z variant metody void helloWorld():
/** * Abstraktni trida, kterou mohou rozsirit vsechny tridy urcenyme pro generovani bajtkodu. */ abstract class BytecodeGenerator { /** * Vytvoreni bajtkodu predstavujiciho sekvenci instrukci pro libovolnou * metodu. * * @param constPool * tabulka konstant pouzivana metodou * @return bajtkod predstavujici sekvenci instrukci pro vytvorenou metodu * @throws NotFoundException * vyvolana v pripade, ze se nenaleznou nektere pomocne tridy */ abstract public Bytecode generateBytecode(ConstPool constPool) throws NotFoundException; }
6. První způsob výpisu řetězce – využití Bytecode.addPrintln()
První způsob konstrukce bajtkódu metody, jejímž úkolem bude výpis řetězce, je velmi jednoduchý, protože zde využijeme volání Bytecode.addPrintln(). Výpis řetězce je totiž tak často prováděnou funkcí, že se autoři nástroje Javassist rozhodli nám zjednodušit práci a vytvořit všechny tři potřebné instrukce s využitím volání této jediné metody. Z tohoto důvodu je možné první verzi generátoru bajtkódu napsat velmi snadno, ovšem povšimněte si, že nesmíme zapomenout na konec bajtkódu vložit instrukci return zajišťující korektní návrat z metody. Ve skutečnosti nás však Javassist nijak při tvorbě bajtkódu nekontroluje, takže se můžeme pokusit tuto instrukci odstranit a sledovat, jak bude s takto vytvořeným bajtkódem „spokojen“ virtuální stroj Javy:
/** * Generator bajtkodu prvni verze metody hello(). */ class HelloMethodGenerator1 extends BytecodeGenerator { /** * Vytvoreni bajtkodu predstavujiciho sekvenci instrukci pro metodu * void hello1(). * * @param constPool * tabulka konstant pouzivana metodou * @return bajtkod predstavujici sekvenci instrukci pro vytvorenou metodu */ @Override public Bytecode generateBytecode(ConstPool constPool) { final int stackSize = 1; final int localVars = 0; // vygenerovat bajtkod Bytecode bytecode = new Bytecode(constPool, stackSize, localVars); // instrukce pro tisk retezce lze vygenerovat (pridat do bajtkodu) // velmi jednoduse s vyuzitim metody Bytecode.addPrintln() bytecode.addPrintln("Hello world #1!"); // dulezite je taktez spravne ukoncit volani metody a zabezpecit // navrat do metody volajici bytecode.addOpcode(Opcode.RETURN); // vratit prave vytvoreny bajtkod return bytecode; } }
7. Druhý způsob výpisu řetězce – využití Bytecode.addGetstatic(), Bytecode.addLdc() a Bytecode.addInvokevirtual()
Druhý způsob konstrukce bajtkódu metody, jejímž úkolem bude výpis řetězce, je již poněkud komplikovanější, jelikož zde již budeme zapisovat všechny tři instrukce explicitně. Nejprve je však nutné do constant poolu vložit nový řetězec, což zajišťuje volání ConstPool.addStringInfo(). Návratovou hodnotou tohoto volání je index nové položky uložené do constant poolu; tento index je nutné si zapamatovat, neboť ho využijeme dále. Další postup je již poměrně přímočarý, neboť nástroj Javassist nabízí metody Bytecode.addGetstatic(), Bytecode.addLdc() a Bytecode.addInvokevirtual(). Tyto tři metody jsou přetížené a my zde využijeme varianty, v níž jsou plná jména tříd a signatury metod reprezentovány řetězci. Interně musí Javassist tyto řetězce taktéž uložit do constant poolu, což jsme ostatně již viděli ve druhé kapitole:
/** * Generator bajtkodu druhe verze metody hello(). */ class HelloMethodGenerator2 extends BytecodeGenerator { /** * Vytvoreni bajtkodu predstavujiciho sekvenci instrukci pro metodu * void hello2(). * * @param constPool * tabulka konstant pouzivana metodou * @return bajtkod predstavujici sekvenci instrukci pro vytvorenou metodu */ @Override public Bytecode generateBytecode(ConstPool constPool) { final int stackSize = 1; final int localVars = 0; // pridat do konstant poolu novy retezec final int stringConstant = constPool.addStringInfo("Hello world #2!"); // vygenerovat bajtkod Bytecode bytecode = new Bytecode(constPool, stackSize, localVars); // vygenerovani trojice instrukci: // getstatic // ldc // invokevirtual bytecode.addGetstatic("java.lang.System", "err", "Ljava/io/PrintStream;"); bytecode.addLdc(stringConstant); bytecode.addInvokevirtual("java.io.PrintStream", "println", "(Ljava/lang/String;)V"); // dulezite je taktez spravne ukoncit volani metody a zabezpecit // navrat do metody volajici bytecode.addOpcode(Opcode.RETURN); // vratit prave vytvoreny bajtkod return bytecode; } }
8. Třetí způsob výpisu řetězce – použití třídy CtClass namísto signatur tříd a metod
V předchozí kapitole jsme si uvedli jeden ze způsobů využití metod Bytecode.addGetstatic(), Bytecode.addLdc() a Bytecode.addInvokevirtual(). Povšimněte si, že metodě Bytecode.addGetstatic() bylo nutné předat jméno třídy se statickým atributem, jméno atributu i jeho typ (ve formě signatury). Podobně bylo nutné metodě Bytecode.addInvokevirtual() předat jméno třídy, jméno volané metody a její plnou signaturu (návratový typ i typy parametrů). Existují situace, kdy je předávání těchto řetězců vhodné – může se například jednat o specializované překladače – ovšem někdy je výhodnější použít typově bezpečnější přístup. Ve skutečnosti je totiž možné použít i alternativní podobu metod Bytecode.addGetstatic() a Bytecode.addInvokevirtual(). Těmto metodám se předají příslušné instance třídy typu CtClass (ty lze vytvořit různým způsobem) a parametry či návratový typ u addInvokevirtual() může být taktéž reprezentován instancemi třídy CtClass. Základní způsob využití je vidět ze třetí verze generátoru metody typu „Hello world“:
/** * Generator bajtkodu treti verze metody hello(). */ class HelloMethodGenerator3 extends BytecodeGenerator { /** * Vytvoreni bajtkodu predstavujiciho sekvenci instrukci pro metodu * void hello3(). * * @param constPool * tabulka konstant pouzivana metodou * @return bajtkod predstavujici sekvenci instrukci pro vytvorenou metodu */ @Override public Bytecode generateBytecode(ConstPool constPool) throws NotFoundException { final int stackSize = 1; final int localVars = 0; // pridat do konstant poolu novy retezec final int stringConstant = constPool.addStringInfo("Hello world #3!"); // ziskat vychozi class pool final ClassPool pool = ClassPool.getDefault(); // pripravit obrazy trid, s nimiz se bude dale pracovat final CtClass classJavaLangSystem = pool.get("java.lang.System"); final CtClass classJavaIoPrintStream = pool.get("java.io.PrintStream"); final CtClass classString = pool.get("java.lang.String"); // parametry vytvorene metody final CtClass params[] = {classString}; // vygenerovat bajtkod Bytecode bytecode = new Bytecode(constPool, stackSize, localVars); // vygenerovani trojice instrukci: // getstatic // ldc // invokevirtual bytecode.addGetstatic(classJavaLangSystem, "err", "Ljava/io/PrintStream;"); bytecode.addLdc(stringConstant); bytecode.addInvokevirtual(classJavaIoPrintStream, "println", CtClass.voidType, params); // dulezite je taktez spravne ukoncit volani metody a zabezpecit // navrat do metody volajici bytecode.addOpcode(Opcode.RETURN); // vratit prave vytvoreny bajtkod return bytecode; } }
9. Čtvrtý způsob výpisu řetězce – převod instance třídy Class na CtClass
Existuje dokonce ještě jednodušší způsob, jak získat instance třídy CtClass představující obrazy tříd System, PrintStream či String. Jeden z konstruktorů třídy CtClass totiž jako svůj argument akceptuje i třídu java.lang.Class, jejíž instanci lze získat pro každou třídu či pro každé rozhraní v běžícím virtuálním stroji Javy. Pro instanci libovolné třídy stačí použít volání Object.getClass(), pro třídu samotnou (tedy pro datový typ) pak existuje literál .class (Foo.class, X.class, String.class). To tedy znamená, že vůbec nemusíme v našem kódu explicitně používat řetězcovou podobu jmen tříd „java.lang.System“, „java.io.PrintStream“ či „java.lang.String“, protože lze snadno získat objety typu Class a z nich pak vytvořit objekty typu CtClass:
/** * Generator bajtkodu ctvrte verze metody hello(). */ class HelloMethodGenerator4 extends BytecodeGenerator { /** * Vytvoreni bajtkodu predstavujiciho sekvenci instrukci pro metodu * void hello4(). * * @param constPool * tabulka konstant pouzivana metodou * @return bajtkod predstavujici sekvenci instrukci pro vytvorenou metodu */ @Override public Bytecode generateBytecode(ConstPool constPool) throws NotFoundException { final int stackSize = 1; final int localVars = 0; // pridat do konstant poolu novy retezec final int stringConstant = constPool.addStringInfo("Hello world #4!"); // ziskat vychozi class pool ClassPool pool = ClassPool.getDefault(); // pripravit obrazy trid, s nimiz se bude dale pracovat // java.lang.System String classJavaLangSystemName = System.class.getName(); CtClass classJavaLangSystem = pool.get(classJavaLangSystemName); // java.lang.System.err String classJavaIoPrintStreamName = System.err.getClass().getName(); CtClass classJavaIoPrintStream = pool.get(classJavaIoPrintStreamName); // java.lang.String String classStringName = "foo".getClass().getName(); CtClass classString = pool.get(classStringName); // parametry vytvorene metody CtClass params[] = {classString}; // vygenerovat bajtkod Bytecode bytecode = new Bytecode(constPool, stackSize, localVars); // vygenerovani trojice instrukci: // getstatic // ldc // invokevirtual bytecode.addGetstatic(classJavaLangSystem, "err", "Ljava/io/PrintStream;"); bytecode.addLdc(stringConstant); bytecode.addInvokevirtual(classJavaIoPrintStream, "println", CtClass.voidType, params); // dulezite je taktez spravne ukoncit volani metody a zabezpecit // navrat do metody volajici bytecode.addOpcode(Opcode.RETURN); // vratit prave vytvoreny bajtkod return bytecode; } }
10. Spuštění metod právě vytvořené třídy ve stejném virtuálním stroji
Prozatím všechny třídy (a jejich metody), které jsme generovali s využitím nástroje Javassist, byly spouštěny mimo virtuální stroj Javy, v němž byl spuštěn tento nástroj. To je sice v mnoha případech v pořádku, zejména tehdy, pokud potřebujeme nějakou třídu vytvořit či modifikovat pouze jednou, ovšem ve chvíli, kdy je nutné vytvořenou/modifikovanou třídu ihned použít by bylo vhodnější, aby se třída načetla a inicializovala ve stejném virtuálním stroji. I tuto možnost nástroj Javassist programátorům nabízí, takže je ho možné využít například u aspektově orientovaného programování atd. Podívejme se nyní na základní způsob inicializace vytvořené třídy a spuštění jejich metod. Použijeme přitom dvě techniky – převod instance CtClass na java.lang.Class, což zajišťuje metoda CtClass.toClass() (toto není tak jednoduché, jak by se mohlo na první pohled zdát, protože Javassist+JVM zde musí provést velké množství inicializačních operací):
/** * Spusteni vsech metod typu "hello*()" * * @param generatedClass * Trida vygenerovana nastrojem Javassist. * * @throws CannotCompileException * muze byt vyhozena v prubehu prevodu CtClass na Class * @throws InstantiationException * muze byt vyhozena v prubehu prevodu CtClass na Class * @throws IllegalAccessException * muze byt vyhozena v prubehu spusteni vybrane metody * @throws NoSuchMethodException * muze byt vyhozena v prubehu spusteni vybrane metody * @throws InvocationTargetException * muze byt vyhozena v prubehu spusteni vybrane metody */ private static void runMethodsFromNewClass(CtClass generatedClass) throws CannotCompileException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { Class c = generatedClass.toClass(); for (int i = 1; i <= 4; i++) { invokeStaticMethod(c, "hello" + i); } }
Druhou zde použitou technikou je zavolání metody s využitím reflection API. Vzhledem k tomu, že všechny čtyři spouštěné metody jsou statické a současně i bezparametrické, nepředává se ve volání Method.invoke() žádná hodnota (ono null pouze nahrazuje hodnotu this použitou u nestatických metod):
/** * Zavolani vybrane staticke metody * * @param anyClass * trida, v niz je staticka metoda deklarovana * @param methodName * jmeno staticke metody * @throws IllegalAccessException * muze byt vyhozena v prubehu spusteni vybrane metody * @throws NoSuchMethodException * muze byt vyhozena v prubehu spusteni vybrane metody * @throws InvocationTargetException * muze byt vyhozena v prubehu spusteni vybrane metody */ @SuppressWarnings("unchecked") private static void invokeStaticMethod(Class anyClass, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { Method hello = anyClass.getMethod(methodName); hello.invoke(null); }
Existují ale i lepší způsoby spuštění metod vytvořené třídy, v ideálním případě lze nadeklarovat rozhraní či abstraktní rodičovskou třídu a provést spuštění přímo, tj. bez použití reflection API.
11. Zdrojový kód demonstračního příkladu ClassGenerationTest8
Veškeré uživatelské metody i pětice pomocných tříd je součástí dnešního prvního a současně i jediného demonstračního příkladu nazvaného ClassGenerationTest8. V tomto příkladu se provede trojice operací. Nejdříve se vytvoří nová třída nazvaná GeneratedClass8 s pěticí vygenerovaných metod main(), hello1() až hello4() a následně se struktura celé této třídy vypíše na standardní výstup s využitím stejného postupu, s jakým jsme se seznámili již v předchozí části tohoto seriálu – zpětným přečtením kódu každé metody s následnou iterací přes získaný kód a výpisem symbolických jmen jednotlivých instrukcí. Posléze se nově vytvořená třída GeneratedClass8 načte do JVM a všechny čtyři statické metody hello1() až hello4() se spustí. Následuje výpis celého zdrojového kódu tohoto demonstračního příkladu:
import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import javassist.Modifier; import javassist.NotFoundException; import javassist.bytecode.BadBytecode; import javassist.bytecode.Bytecode; import javassist.bytecode.CodeAttribute; import javassist.bytecode.CodeIterator; import javassist.bytecode.ConstPool; import javassist.bytecode.MethodInfo; import javassist.bytecode.Mnemonic; import javassist.bytecode.Opcode; /** * Abstraktni trida, kterou mohou rozsirit vsechny tridy urcenyme pro generovani bajtkodu. */ abstract class BytecodeGenerator { /** * Vytvoreni bajtkodu predstavujiciho sekvenci instrukci pro libovolnou * metodu. * * @param constPool * tabulka konstant pouzivana metodou * @return bajtkod predstavujici sekvenci instrukci pro vytvorenou metodu * @throws NotFoundException * vyvolana v pripade, ze se nenaleznou nektere pomocne tridy */ abstract public Bytecode generateBytecode(ConstPool constPool) throws NotFoundException; } /** * Generator bajtkodu prvni verze metody hello(). */ class HelloMethodGenerator1 extends BytecodeGenerator { /** * Vytvoreni bajtkodu predstavujiciho sekvenci instrukci pro metodu * void hello1(). * * @param constPool * tabulka konstant pouzivana metodou * @return bajtkod predstavujici sekvenci instrukci pro vytvorenou metodu */ @Override public Bytecode generateBytecode(ConstPool constPool) { final int stackSize = 1; final int localVars = 0; // vygenerovat bajtkod Bytecode bytecode = new Bytecode(constPool, stackSize, localVars); // instrukce pro tisk retezce lze vygenerovat (pridat do bajtkodu) // velmi jednoduse s vyuzitim metody Bytecode.addPrintln() bytecode.addPrintln("Hello world #1!"); // dulezite je taktez spravne ukoncit volani metody a zabezpecit // navrat do metody volajici bytecode.addOpcode(Opcode.RETURN); // vratit prave vytvoreny bajtkod return bytecode; } } /** * Generator bajtkodu druhe verze metody hello(). */ class HelloMethodGenerator2 extends BytecodeGenerator { /** * Vytvoreni bajtkodu predstavujiciho sekvenci instrukci pro metodu * void hello2(). * * @param constPool * tabulka konstant pouzivana metodou * @return bajtkod predstavujici sekvenci instrukci pro vytvorenou metodu */ @Override public Bytecode generateBytecode(ConstPool constPool) { final int stackSize = 1; final int localVars = 0; // pridat do konstant poolu novy retezec final int stringConstant = constPool.addStringInfo("Hello world #2!"); // vygenerovat bajtkod Bytecode bytecode = new Bytecode(constPool, stackSize, localVars); // vygenerovani trojice instrukci: // getstatic // ldc // invokevirtual bytecode.addGetstatic("java.lang.System", "err", "Ljava/io/PrintStream;"); bytecode.addLdc(stringConstant); bytecode.addInvokevirtual("java.io.PrintStream", "println", "(Ljava/lang/String;)V"); // dulezite je taktez spravne ukoncit volani metody a zabezpecit // navrat do metody volajici bytecode.addOpcode(Opcode.RETURN); // vratit prave vytvoreny bajtkod return bytecode; } } /** * Generator bajtkodu treti verze metody hello(). */ class HelloMethodGenerator3 extends BytecodeGenerator { /** * Vytvoreni bajtkodu predstavujiciho sekvenci instrukci pro metodu * void hello3(). * * @param constPool * tabulka konstant pouzivana metodou * @return bajtkod predstavujici sekvenci instrukci pro vytvorenou metodu */ @Override public Bytecode generateBytecode(ConstPool constPool) throws NotFoundException { final int stackSize = 1; final int localVars = 0; // pridat do konstant poolu novy retezec final int stringConstant = constPool.addStringInfo("Hello world #3!"); // ziskat vychozi class pool final ClassPool pool = ClassPool.getDefault(); // pripravit obrazy trid, s nimiz se bude dale pracovat final CtClass classJavaLangSystem = pool.get("java.lang.System"); final CtClass classJavaIoPrintStream = pool.get("java.io.PrintStream"); final CtClass classString = pool.get("java.lang.String"); // parametry vytvorene metody final CtClass params[] = {classString}; // vygenerovat bajtkod Bytecode bytecode = new Bytecode(constPool, stackSize, localVars); // vygenerovani trojice instrukci: // getstatic // ldc // invokevirtual bytecode.addGetstatic(classJavaLangSystem, "err", "Ljava/io/PrintStream;"); bytecode.addLdc(stringConstant); bytecode.addInvokevirtual(classJavaIoPrintStream, "println", CtClass.voidType, params); // dulezite je taktez spravne ukoncit volani metody a zabezpecit // navrat do metody volajici bytecode.addOpcode(Opcode.RETURN); // vratit prave vytvoreny bajtkod return bytecode; } } /** * Generator bajtkodu ctvrte verze metody hello(). */ class HelloMethodGenerator4 extends BytecodeGenerator { /** * Vytvoreni bajtkodu predstavujiciho sekvenci instrukci pro metodu * void hello4(). * * @param constPool * tabulka konstant pouzivana metodou * @return bajtkod predstavujici sekvenci instrukci pro vytvorenou metodu */ @Override public Bytecode generateBytecode(ConstPool constPool) throws NotFoundException { final int stackSize = 1; final int localVars = 0; // pridat do konstant poolu novy retezec final int stringConstant = constPool.addStringInfo("Hello world #4!"); // ziskat vychozi class pool ClassPool pool = ClassPool.getDefault(); // pripravit obrazy trid, s nimiz se bude dale pracovat // java.lang.System String classJavaLangSystemName = System.class.getName(); CtClass classJavaLangSystem = pool.get(classJavaLangSystemName); // java.lang.System.err String classJavaIoPrintStreamName = System.err.getClass().getName(); CtClass classJavaIoPrintStream = pool.get(classJavaIoPrintStreamName); // java.lang.String String classStringName = "foo".getClass().getName(); CtClass classString = pool.get(classStringName); // parametry vytvorene metody CtClass params[] = {classString}; // vygenerovat bajtkod Bytecode bytecode = new Bytecode(constPool, stackSize, localVars); // vygenerovani trojice instrukci: // getstatic // ldc // invokevirtual bytecode.addGetstatic(classJavaLangSystem, "err", "Ljava/io/PrintStream;"); bytecode.addLdc(stringConstant); bytecode.addInvokevirtual(classJavaIoPrintStream, "println", CtClass.voidType, params); // dulezite je taktez spravne ukoncit volani metody a zabezpecit // navrat do metody volajici bytecode.addOpcode(Opcode.RETURN); // vratit prave vytvoreny bajtkod return bytecode; } } /** * Test moznosti nastroje Javassist - vygenerovani jednoduche tridy * s metodou main a nekolika dalsimi statickymi bezparametrickymi metodami * "hello*()" * Jakmile je trida vytvorena, je nactena do JVM a jeji staticke metody jsou * spusteny. * * @author Pavel Tisnovsky */ public class ClassGenerationTest8 { /** * Jmeno vygenerovane tridy. */ private static final String GENERATED_CLASS_NAME = "GeneratedClass8"; /** * Zdrojovy kod metody main(), ktery bude nasledne zkompilovan * do bajtkodu a zakomponovan do vytvorene tridy. */ private static final String MAIN_METHOD_SOURCE_TEXT = "public static void main(String[] args)" + "{" + " hello1();" + " hello2();" + " hello3();" + " hello4();" + "}"; /** * Vytvoreni metody main() z jejiho zdrojoveho kodu. * * @param generatedClass * predstavuje vytvarenou tridu * @throws CannotCompileException * vyhozena v pripade chyby ve zdrojovem kodu */ private static void addMethodMain(CtClass generatedClass) throws CannotCompileException { CtMethod methodMain = CtMethod.make(MAIN_METHOD_SOURCE_TEXT, generatedClass); generatedClass.addMethod(methodMain); } /** * Vytvoreni metody public static void hello*(). * * @param generatedClass * predstavuje vytvarenou tridu * @param methodName * jmeno vytvarene metody * @param generator * generator bajtkodu vytvarene metody * @throws CannotCompileException * vyhozena v pripade chyby ve zdrojovem kodu nebo strukturalni chyby v bajtkodu * @throws NotFoundException * vyhozena v pripade, ze nebyla nalezena nektera pomocna trida */ private static void constructMethodHello(CtClass generatedClass, String methodName, BytecodeGenerator generator) throws CannotCompileException, NotFoundException { MethodInfo methodInfo = prepareMethod(generatedClass, methodName); ConstPool constPool = methodInfo.getConstPool(); Bytecode bytecode = generator.generateBytecode(constPool); setCodeAttributeForMethod(methodInfo, bytecode); } /** * Vytvoreni kostry metody a vraceni objektu typu MethodInfo. * * @param generatedClass * predstavuje vytvarenou tridu * @param methodName * jmeno vytvarene metody * @return * @throws CannotCompileException * vyhozena v pripade chyby ve zdrojovem kodu nebo strukturalni chyby v bajtkodu */ private static MethodInfo prepareMethod(CtClass generatedClass, String methodName) throws CannotCompileException { CtClass returnType = CtClass.voidType; CtClass[] parameterTypes = {}; // u metody je nutne znat jeji jmeno, navratovou hodnotu i typy parametru CtMethod helloMethod = new CtMethod(returnType, methodName, parameterTypes, generatedClass); // zmena modifikatoru helloMethod.setModifiers(Modifier.STATIC | Modifier.PUBLIC); // pridani metody do tridy generatedClass.addMethod(helloMethod); // ziskani informace o metoda MethodInfo methodInfo = helloMethod.getMethodInfo(); return methodInfo; } /** * Nastaveni atributu "Code" s bajktodem pro vybranou metodu. * * @param methodInfo * objekt typu MethodInfo * @param bytecode * bajtkod vytvarene metody. */ private static void setCodeAttributeForMethod(MethodInfo methodInfo, Bytecode bytecode) { CodeAttribute codeAttribute = bytecode.toCodeAttribute(); methodInfo.setCodeAttribute(codeAttribute); } /** * Vytvoreni tridy s metodou main(). * * @throws CannotCompileException * vyhozena v pripade chyby ve zdrojovem kodu metody main() * nebo pri strukturalni chybe bajtkodu metod hello1()-hello4(). * @throws IOException * pokud dojde k chybe pri zapisu bajtkodu na disk * @throws NotFoundException * pokud dojde k chybe pri zapisu bajtkodu na disk */ private static CtClass generateClass() throws CannotCompileException, NotFoundException, IOException { // ziskat vychozi class pool ClassPool pool = ClassPool.getDefault(); // vytvoreni nove verejne tridy CtClass generatedClass = pool.makeClass(GENERATED_CLASS_NAME); // konstrukce nove metody void hello1() constructMethodHello(generatedClass, "hello1", new HelloMethodGenerator1()); // konstrukce nove metody void hello2() constructMethodHello(generatedClass, "hello2", new HelloMethodGenerator2()); // konstrukce nove metody void hello3() constructMethodHello(generatedClass, "hello3", new HelloMethodGenerator3()); // konstrukce nove metody void hello4() constructMethodHello(generatedClass, "hello4", new HelloMethodGenerator4()); // pridani metody do teto tridy addMethodMain(generatedClass); // ulozeni bajtkodu na disk generatedClass.writeFile(); return generatedClass; } /** * Vypis struktury vybrane metody. * * @param generatedClass * predstavuje vytvarenou tridu * @param methodName * jmeno metody, jejiz struktura se ma vypsat * @throws NotFoundException * vyhozena, pokud metoda nebyla nalezena * @throws BadBytecode * vyhozena, pokud se nalezne neplatna instrukce v bytekodu */ private static void printMethodStructure(CtClass generatedClass, String methodName) throws NotFoundException, BadBytecode { System.out.println("Method '" + methodName + "' structure:"); CtMethod method = generatedClass.getDeclaredMethod(methodName); if (method == null) { System.out.println(" not found!"); return; } MethodInfo methodInfo = method.getMethodInfo(); System.out.println(" real name: " + methodInfo.getName()); System.out.println(" descriptor: " + methodInfo.getDescriptor()); System.out.println(" access flags: " + Modifier.toString(methodInfo.getAccessFlags())); System.out.println(" method body:"); printMethodBody(methodInfo); System.out.println(); } /** * Vypis instrukci tvoricich telo vybrane metody. * * @throws NotFoundException * vyhozena, pokud metoda nebyla nalezena * @throws BadBytecode * vyhozena, pokud se nalezne neplatna instrukce v bytekodu */ private static void printMethodBody(MethodInfo methodInfo) throws BadBytecode { CodeAttribute ca = methodInfo.getCodeAttribute(); CodeIterator iterator = ca.iterator(); while (iterator.hasNext()) { int index = iterator.next(); int opcode = iterator.byteAt(index); System.out.println(" " + Mnemonic.OPCODE[opcode]); } } /** * Vypis struktury vybranych metod z generovane tridy. * * @param generatedClass * predstavuje vytvarenou tridu * @throws NotFoundException * vyhozena, pokud metoda nebyla nalezena * @throws BadBytecode */ private static void printMethodStructures(CtClass generatedClass) throws NotFoundException, BadBytecode { printMethodStructure(generatedClass, "main"); printMethodStructure(generatedClass, "hello1"); printMethodStructure(generatedClass, "hello2"); printMethodStructure(generatedClass, "hello3"); printMethodStructure(generatedClass, "hello4"); } /** * Spusteni vsech metod typu "hello*()" * * @param generatedClass * Trida vygenerovana nastrojem Javassist. * * @throws CannotCompileException * muze byt vyhozena v prubehu prevodu CtClass na Class * @throws InstantiationException * muze byt vyhozena v prubehu prevodu CtClass na Class * @throws IllegalAccessException * muze byt vyhozena v prubehu spusteni vybrane metody * @throws NoSuchMethodException * muze byt vyhozena v prubehu spusteni vybrane metody * @throws InvocationTargetException * muze byt vyhozena v prubehu spusteni vybrane metody */ private static void runMethodsFromNewClass(CtClass generatedClass) throws CannotCompileException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { Class c = generatedClass.toClass(); for (int i = 1; i <= 4; i++) { invokeStaticMethod(c, "hello" + i); } } /** * Zavolani vybrane staticke metody * * @param anyClass * trida, v niz je staticka metoda deklarovana * @param methodName * jmeno staticke metody * @throws IllegalAccessException * muze byt vyhozena v prubehu spusteni vybrane metody * @throws NoSuchMethodException * muze byt vyhozena v prubehu spusteni vybrane metody * @throws InvocationTargetException * muze byt vyhozena v prubehu spusteni vybrane metody */ @SuppressWarnings("unchecked") private static void invokeStaticMethod(Class anyClass, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { Method hello = anyClass.getMethod(methodName); hello.invoke(null); } /** * Spusteni generatoru tridy. * * @param args nevyuzito */ public static void main(String[] args) { System.out.println("class generation begin: " + GENERATED_CLASS_NAME); try { CtClass generatedClass = generateClass(); // dulezite - generovana trida nesmi byt "zmrazena" generatedClass.defrost(); // vypis struktury bajtkodu vsech metod printMethodStructures(generatedClass); // spusteni metod runMethodsFromNewClass(generatedClass); } catch (CannotCompileException e) { e.printStackTrace(); } catch (NotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (BadBytecode e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } System.out.println("class generation end: " + GENERATED_CLASS_NAME); } }
12. Výpis bajtkódu vygenerovaného demonstračním příkladem ClassGenerationTest8
Demonstrační příklad ClassGenerationTest8 po svém spuštění vytvoří třídu GeneratedClass8 a navíc na standardní výstup vypíše základní strukturu této třídy. Na závěr jsou spuštěny všechny čtyři metody hello*(), které provedou výpis na chybový výstup:
class generation begin: GeneratedClass8 Method 'main' structure: real name: main descriptor: ([Ljava/lang/String;)V access flags: public static method body: invokestatic invokestatic invokestatic invokestatic return Method 'hello1' structure: real name: hello1 descriptor: ()V access flags: public static method body: getstatic ldc invokevirtual return Method 'hello2' structure: real name: hello2 descriptor: ()V access flags: public static method body: getstatic ldc invokevirtual return Method 'hello3' structure: real name: hello3 descriptor: ()V access flags: public static method body: getstatic ldc invokevirtual return Method 'hello4' structure: real name: hello4 descriptor: ()V access flags: public static method body: getstatic ldc invokevirtual return class generation end: GeneratedClass8 Hello world #1! Hello world #2! Hello world #3! Hello world #4!
Bude jistě zajímavé podívat se i na strukturu bajtkódu třídy GeneratedClass8 vygenerované dnešním druhým demonstračním příkladem ClassGenerationTest8. Pro výpis struktury bajtkódu opět využijeme standardní nástroj javap:
javap -c -private GeneratedClass8
Z výpisu vypsaného disassemblerem je patrné, že všechny čtyři metody jsou tvořeny stejnou sekvencí instrukcí, nezávisle na tom, jakým způsobem byly tyto metody ve skutečnosti zkonstruovány. Dále je patrné, že nástroj Javassist automaticky vytvořil všechny potřebné záznamy v constant poolu. Jedná se o všechny záznamy typu NameAndType, Method, Class a s nimi související záznamy typu Asciz:
Compiled from "GeneratedClass8.java" public class GeneratedClass8 extends java.lang.Object SourceFile: "GeneratedClass8.java" minor version: 0 major version: 50 Constant pool: const #1 = Asciz GeneratedClass8; const #2 = class #1; // GeneratedClass8 const #3 = Asciz java/lang/Object; const #4 = class #3; // java/lang/Object const #5 = Asciz SourceFile; const #6 = Asciz GeneratedClass8.java; const #7 = Asciz hello1; const #8 = Asciz ()V; const #9 = Asciz java/lang/System; const #10 = class #9; // java/lang/System const #11 = Asciz err; const #12 = Asciz Ljava/io/PrintStream;; const #13 = NameAndType #11:#12; // err:Ljava/io/PrintStream; const #14 = Field #10.#13; // java/lang/System.err:Ljava/io/PrintStream; const #15 = Asciz Hello world #1!; const #16 = String #15; // Hello world #1! const #17 = Asciz java/io/PrintStream; const #18 = class #17; // java/io/PrintStream const #19 = Asciz println; const #20 = Asciz (Ljava/lang/String;)V; const #21 = NameAndType #19:#20; // println:(Ljava/lang/String;)V const #22 = Method #18.#21; // java/io/PrintStream.println:(Ljava/lang/String;)V const #23 = Asciz Code; const #24 = Asciz hello2; const #25 = Asciz Hello world #2!; const #26 = String #25; // Hello world #2! const #27 = Asciz hello3; const #28 = Asciz Hello world #3!; const #29 = String #28; // Hello world #3! const #30 = Asciz hello4; const #31 = Asciz Hello world #4!; const #32 = String #31; // Hello world #4! const #33 = Asciz main; const #34 = Asciz ([Ljava/lang/String;)V; const #35 = NameAndType #7:#8; // hello1:()V const #36 = Method #2.#35; // GeneratedClass8.hello1:()V const #37 = NameAndType #24:#8; // hello2:()V const #38 = Method #2.#37; // GeneratedClass8.hello2:()V const #39 = NameAndType #27:#8; // hello3:()V const #40 = Method #2.#39; // GeneratedClass8.hello3:()V const #41 = NameAndType #30:#8; // hello4:()V const #42 = Method #2.#41; // GeneratedClass8.hello4:()V const #43 = Asciz <init>; const #44 = NameAndType #43:#8; // "<init>":()V const #45 = Method #4.#44; // java/lang/Object."<init>":()V { public static void hello1(); Code: Stack=2, Locals=0, Args_size=0 0: getstatic #14; //Field java/lang/System.err:Ljava/io/PrintStream; 3: ldc #16; //String Hello world #1! 5: invokevirtual #22; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return public static void hello2(); Code: Stack=2, Locals=0, Args_size=0 0: getstatic #14; //Field java/lang/System.err:Ljava/io/PrintStream; 3: ldc #26; //String Hello world #2! 5: invokevirtual #22; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return public static void hello3(); Code: Stack=2, Locals=0, Args_size=0 0: getstatic #14; //Field java/lang/System.err:Ljava/io/PrintStream; 3: ldc #29; //String Hello world #3! 5: invokevirtual #22; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return public static void hello4(); Code: Stack=2, Locals=0, Args_size=0 0: getstatic #14; //Field java/lang/System.err:Ljava/io/PrintStream; 3: ldc #32; //String Hello world #4! 5: invokevirtual #22; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return public static void main(java.lang.String[]); Code: Stack=0, Locals=1, Args_size=1 0: invokestatic #36; //Method hello1:()V 3: invokestatic #38; //Method hello2:()V 6: invokestatic #40; //Method hello3:()V 9: invokestatic #42; //Method hello4:()V 12: return public GeneratedClass8(); Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: invokespecial #45; //Method java/lang/Object."<init>":()V 4: return }
13. Repositář se zdrojovými kódy dnešního demonstračního příkladu
Následuje – v tomto seriálu již tradiční – kapitola s odkazy na zdrojové kódy. Dnes popsaný demonstrační příklad je společně s dalšími pomocnými skripty uložen do Mercurial repositáře dostupného na adrese http://icedtea.classpath.org/people/ptisnovs/jvm-tools/. V následující tabulce najdete odkazy na prozatím nejnovější verze těchto zdrojových kódů:
# | Zdrojový soubor/skript | Umístění souboru v repositáři |
---|---|---|
1 | ClassGenerationTest8.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/2588b1e9c0da/javassist/ClassGenerationTest8/ClassGenerationTest8.java |
2 | buildClassGenerator8.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/2588b1e9c0da/javassist/ClassGenerationTest8/buildClassGenerator8.sh |
3 | runClassGenerator8.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/2588b1e9c0da/javassist/ClassGenerationTest8/runClassGenerator8.sh |
4 | runGeneratedClass8.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/2588b1e9c0da/javassist/ClassGenerationTest8/runGeneratedClass8.sh |
14. Odkazy na Internetu
- Open Source ByteCode Libraries in Java
http://java-source.net/open-source/bytecode-libraries - ASM Home page
http://asm.ow2.org/ - Seznam nástrojů využívajících projekt ASM
http://asm.ow2.org/users.html - ObjectWeb ASM (Wikipedia)
http://en.wikipedia.org/wiki/ObjectWeb_ASM - Java Bytecode BCEL vs ASM
http://james.onegoodcookie.com/2005/10/26/java-bytecode-bcel-vs-asm/ - BCEL Home page
http://commons.apache.org/bcel/ - Byte Code Engineering Library (před verzí 5.0)
http://bcel.sourceforge.net/ - Byte Code Engineering Library (verze >= 5.0)
http://commons.apache.org/proper/commons-bcel/ - BCEL Manual
http://commons.apache.org/bcel/manual.html - Byte Code Engineering Library (Wikipedia)
http://en.wikipedia.org/wiki/BCEL - BCEL Tutorial
http://www.smfsupport.com/support/java/bcel-tutorial!/ - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html - Bytecode Outline plugin for Eclipse (screenshoty + info)
http://asm.ow2.org/eclipse/index.html - Javassist
http://www.jboss.org/javassist/ - Byteman
http://www.jboss.org/byteman - Java programming dynamics, Part 7: Bytecode engineering with BCEL
http://www.ibm.com/developerworks/java/library/j-dyn0414/ - The JavaTM Virtual Machine Specification, Second Edition
http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap – The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) – Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - aspectj (Eclipse)
http://www.eclipse.org/aspectj/ - Aspect-oriented programming (Wikipedia)
http://en.wikipedia.org/wiki/Aspect_oriented_programming - AspectJ (Wikipedia)
http://en.wikipedia.org/wiki/AspectJ - EMMA: a free Java code coverage tool
http://emma.sourceforge.net/ - Cobertura
http://cobertura.sourceforge.net/ - jclasslib bytecode viewer
http://www.ej-technologies.com/products/jclasslib/overview.html