Hlavní navigace

Pohled pod kapotu JVM – přímé generování instrukcí bajtkódu s využitím nástroje Javassist (2)

23. 7. 2013
Doba čtení: 30 minut

Sdílet

V dnešní části seriálu o jazyce Java si řekneme, jak se s využitím nástroje Javassist dají tvořit i složitější metody (resp. jejich bajtkód). Dále si ukážeme jednu velmi užitečnou funkcionalitu Javassistu – jeho schopnost načíst právě vytvořenou či modifikovanou třídu do JVM a ihned ji začít používat.

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()

7. Druhý způsob výpisu řetězce – využití Bytecode.addGetstatic(), Bytecode.addLdc() a Bytecode.addInvokevirtual()

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

14. Odkazy na Internetu

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()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()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:

CS24_early

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.or­g/people/ptisnovs/jvm-tools/. V následující tabulce najdete odkazy na prozatím nejnovější verze těchto zdrojových kódů:

14. Odkazy na Internetu

  1. Open Source ByteCode Libraries in Java
    http://java-source.net/open-source/bytecode-libraries
  2. ASM Home page
    http://asm.ow2.org/
  3. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2.org/users.html
  4. ObjectWeb ASM (Wikipedia)
    http://en.wikipedia.org/wi­ki/ObjectWeb_ASM
  5. Java Bytecode BCEL vs ASM
    http://james.onegoodcooki­e.com/2005/10/26/java-bytecode-bcel-vs-asm/
  6. BCEL Home page
    http://commons.apache.org/bcel/
  7. Byte Code Engineering Library (před verzí 5.0)
    http://bcel.sourceforge.net/
  8. Byte Code Engineering Library (verze >= 5.0)
    http://commons.apache.org/pro­per/commons-bcel/
  9. BCEL Manual
    http://commons.apache.org/bcel/ma­nual.html
  10. Byte Code Engineering Library (Wikipedia)
    http://en.wikipedia.org/wiki/BCEL
  11. BCEL Tutorial
    http://www.smfsupport.com/sup­port/java/bcel-tutorial!/
  12. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html
  13. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2.org/eclipse/index.html
  14. Javassist
    http://www.jboss.org/javassist/
  15. Byteman
    http://www.jboss.org/byteman
  16. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ibm.com/develo­perworks/java/library/j-dyn0414/
  17. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/VMSpec­TOC.doc.html
  18. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  19. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  20. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  21. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  22. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  23. aspectj (Eclipse)
    http://www.eclipse.org/aspectj/
  24. Aspect-oriented programming (Wikipedia)
    http://en.wikipedia.org/wi­ki/Aspect_oriented_program­ming
  25. AspectJ (Wikipedia)
    http://en.wikipedia.org/wiki/AspectJ
  26. EMMA: a free Java code coverage tool
    http://emma.sourceforge.net/
  27. Cobertura
    http://cobertura.sourceforge.net/
  28. jclasslib bytecode viewer
    http://www.ej-technologies.com/products/jclas­slib/overview.html

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.