מבוא לג'וואסיסט

1. סקירה כללית

במאמר זה נבחן את ה- Javasisst (עוזר תכנות Java) סִפְרִיָה.

במילים פשוטות, ספרייה זו הופכת את תהליך המניפולציה של Java bytecode לפשוט יותר באמצעות ממשק API ברמה גבוהה מזו שב- JDK.

2. תלות של Maven

כדי להוסיף את ספריית Javassist לפרויקט שלנו עלינו להוסיף יוואסיסט לפום שלנו:

 org.javassist javassist $ {javaassist.version} 3.21.0-GA 

3. מהו ה- Bytecode?

ברמה גבוהה מאוד, כל מחלקת Java שנכתבת בפורמט טקסט רגיל ומורכבת ל- bytecode - ערכת הוראות שניתן לעבד באמצעות מכונת ה- Java Virtual. ה- JVM מתרגם הוראות קוד קוד לפי הוראות הרכבה ברמת המכונה.

בואו נגיד שיש לנו נְקוּדָה מעמד:

נקודה בכיתה ציבורית {int int private; פרטית int y; מהלך ריק ריק (int x, int y) {this.x = x; this.y = y; } // בונים סטנדרטיים / גטרים / סטרים}

לאחר ההידור, נקודה.קלאס ייווצר קובץ המכיל את קוד הביס. אנו יכולים לראות את קוד הביס של אותו מעמד על ידי ביצוע ה- javap פקודה:

javap -c Point.class

פעולה זו תדפיס את הפלט הבא:

מעמד ציבורי com.baeldung.javasisst.Point {public com.baeldung.javasisst.Point (int, int); קוד: 0: aload_0 1: invokespecial # 1 // Method java / lang / Object. "" :() V 4: aload_0 5: iload_1 6: putfield # 2 // שדה x: I 9: aload_0 10: iload_2 11: putfield # 3 // שדה y: I 14: להחזיר מהלך ריק (int, int); קוד: 0: aload_0 1: iload_1 2: putfield # 2 // שדה x: I 5: aload_0 6: iload_2 7: putfield # 3 // שדה y: I 10: return}

כל ההוראות האלה מוגדרות לפי שפת Java; מספר גדול מהם זמין.

בואו ננתח את הוראות קוד ה- Byte של ה- מהלך \ לזוז \ לעבור() שיטה:

  • aload_0 ההוראה טוענת הפניה אל הערימה מהמשתנה המקומי 0
  • iload_1 טוען ערך int מהמשתנה המקומי 1
  • פוטפילד הוא הגדרת שדה איקס של האובייקט שלנו. כל הפעולות הן אנלוגיות לתחום y
  • ההוראה האחרונה היא א לַחֲזוֹר

כל שורת קוד Java מורכבת ל- bytecode עם הוראות מתאימות. ספריית ה- Javassist הופכת את המניפולציה של קוד הביית הזה לקלה יחסית.

4. יצירת מחלקת Java

ניתן להשתמש בספריית Javassist ליצירת קבצי מחלקת Java חדשים.

בואו נגיד שאנחנו רוצים ליצור a JavassistGeneratedClass כיתה המיישמת א java.lang. ניתנים לניתוק מִמְשָׁק. אנחנו רוצים שבכיתה ההיא תהיה תְעוּדַת זֶהוּת שדה של int סוּג. ה ClassFile משמש ליצירת קובץ מחלקה חדש ו- FieldInfo משמש להוספת שדה חדש לכיתה:

ClassFile cf = ClassFile חדש (שקר, "com.baeldung.JavassistGeneratedClass", null); cf.setInterfaces (מחרוזת חדשה [] {"java.lang.Cloneable"}); FieldInfo f = FieldInfo חדש (cf.getConstPool (), "id", "I"); f.setAccessFlags (AccessFlag.PUBLIC); cf.addField (ו); 

אחרי שניצור a JavassistGeneratedClass.class אנחנו יכולים לטעון שיש לו למעשה תְעוּדַת זֶהוּת שדה:

ClassPool classPool = ClassPool.getDefault (); שדה [] שדות = classPool.makeClass (cf) .toClass (). GetFields (); assertEquals (שדות [0] .getName (), "id");

5. טעינת הוראות Bytecode בכיתה

אם אנו רוצים לטעון הוראות קוד קוד של שיטת מחלקה קיימת כבר, אנו יכולים להשיג קוד תכונה של שיטה ספציפית בכיתה. ואז נוכל להשיג א CodeIterator לחזור על כל הוראות קוד הקוד של השיטה.

בואו נטען את כל הוראות קוד ה- bytec של ה- מהלך \ לזוז \ לעבור() שיטת ה- נְקוּדָה מעמד:

ClassPool cp = ClassPool.getDefault (); ClassFile cf = cp.get ("com.baeldung.javasisst.Point") .getClassFile (); MethodInfo minfo = cf.getMethod ("העבר"); CodeAttribute ca = minfo.getCodeAttribute (); CodeIterator ci = ca.iterator (); פעולות רשימת = LinkedList חדש (); ואילו (ci.hasNext ()) {int index = ci.next (); int op = ci.byteAt (אינדקס); operations.add (Mnemonic.OPCODE [op]); } assertEquals (פעולות, Arrays.asList ("aload_0", "iload_1", "putfield", "aload_0", "iload_2", "putfield", "return"));

אנו יכולים לראות את כל הוראות קוד הקוד מהלך \ לזוז \ לעבור() שיטה על ידי צבירת קודי בתים לרשימת הפעולות, כפי שמוצג בקביעה לעיל.

6. הוספת שדות ל- Bytecode הקיים בכיתה

בואו נגיד שאנחנו רוצים להוסיף שדה של int הקלד לקוד התצוגה של המחלקה הקיימת. אנחנו יכולים לטעון את הכיתה באמצעות ClassPoll והוסף שדה לתוכו:

ClassFile cf = ClassPool.getDefault () .get ("com.baeldung.javasisst.Point"). GetClassFile (); FieldInfo f = FieldInfo חדש (ראה getConstPool (), "id", "I"); f.setAccessFlags (AccessFlag.PUBLIC); cf.addField (ו); 

אנו יכולים להשתמש בבבואה כדי לאמת זאת תְעוּדַת זֶהוּת שדה קיים ב נְקוּדָה מעמד:

ClassPool classPool = ClassPool.getDefault (); שדה [] שדות = classPool.makeClass (cf) .toClass (). GetFields (); רשימת שדות רשימת = Stream.of (שדות) .מפה (שדה :: getName) .collect (Collectors.toList ()); assertTrue (fieldsList.contains ("id"));

7. הוספת קונסטרוקטור ל- Bytecode Class

אנו יכולים להוסיף קונסטרוקטור למחלקה הקיימת המוזכרת באחת הדוגמאות הקודמות באמצעות addInvokespecial () שיטה.

ונוכל להוסיף קונסטרוקטור ללא פרמטר על ידי הפעלת a שיטה מ java.lang. אובייקט מעמד:

ClassFile cf = ClassPool.getDefault () .get ("com.baeldung.javasisst.Point"). GetClassFile (); קוד Bytecode = Bytecode חדש (ראה getConstPool ()); code.addAload (0); code.addInvokespecial ("java / lang / Object", MethodInfo.nameInit, "() V"); code.addReturn (null); MethodInfo minfo = MethodInfo חדש (ראה getConstPool (), MethodInfo.nameInit, "() V"); minfo.setCodeAttribute (code.toCodeAttribute ()); cf.addMethod (minfo);

אנו יכולים לבדוק את נוכחותו של הקונסטרוקטור החדש שנוצר על ידי איטרציה מעל קוד קוד:

CodeIterator ci = code.toCodeAttribute (). Iterator (); פעולות רשימת = קישור חדש (); ואילו (ci.hasNext ()) {int index = ci.next (); int op = ci.byteAt (אינדקס); operations.add (Mnemonic.OPCODE [op]); } assertEquals (פעולות, Arrays.asList ("aload_0", "invokespecial", "return"));

8. מסקנה

במאמר זה הצגנו את ספריית הג'אווסיסטים, במטרה להקל על מניפולציה בקוד.

התמקדנו בתכונות הליבה ויצרנו קובץ מחלקה מקוד Java; עשינו גם מניפולציה של קוד-byt של מחלקת Java שכבר נוצרה.

ניתן למצוא את היישום של כל הדוגמאות וקטעי הקוד בפרויקט GitHub - זהו פרויקט Maven, כך שיהיה קל לייבא ולהפעיל אותו כפי שהוא.


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