<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                > **程序分析**、**程序生成**和**程序轉換**都是非常有用的技術,可在許多應用環境下使用;ASM 用于**運行時**類生成與轉換(處理**經過編譯**的 Java 類)。 [TOC] ## 1 概述 - **使用范圍**:嚴格限制于類的讀、寫、轉換和分析 (類的加載過程就超出了它的范圍之外) - **API 模型**:ASM 提供了兩個用于生成和轉換已編譯類的 API,一個是 core API,以基于**事件**的形式來表示類,另一個是 tree API,以基于**對象**的形式來表示類 - core API 要快于 tree API,因為不需要在內存中創建和存儲`Object Tree` - 但是使用 core API 時,類轉換的實現要更難一些,因為在任意給定時刻,類中只有一個元素可用,tree API 則可以在內存中獲取整個類 - **體系結構**: - 基于事件的 API 圍繞事件生成器(類分析器)、事件使用器(類寫入器)和各種預定義的事件篩選器進行,可由開發者自定義,分為兩個步驟: - 將事件生成器(ClassReader)、篩選器(ClassVisitor)和使用器(ClassWriter)組裝為體系結構 - 啟動事件生成器(ClassReader),以執行生成或轉換過程 - 基于對象的 API 中,用于操作 Object Tree 的類生成器或轉換器組件是可以通過組裝而形成的,它們之間的鏈接代表著轉換的順序 - **組織形式**: - `org.objectweb.asm` 和 `org.objectweb.asm.signature` 定義了基于事件的 API,并提供了類分析器和寫入器組件 (asm.jar) - `org.objectweb.asm.util` 提供了各種基于 core API 的工具,可以在開發和調試 ASM 應用程序時使用 (asm-util.jar) - `org.objectweb.asm.commons` 提供了幾個很有用的預定義類轉換器,大多基于 core API (asm-commons.jar) - `org.objectweb.asm.tree` 定義了基于對象的 API,并提供了一些工具,用于在基于事件和基于對象的表示方法之間進行轉換 (asm-tree.jar) - `org.objectweb.asm.tree.analysis` 以 tree API 為基礎,提供了一個類分析框架和幾個預定義的類分析器 (asm-analysis.jar) ## 2 已編譯類的結構表示 一個**已編譯類**和**源文件類**有以下幾點區別: - 已編譯類僅描述**一個類**,一個源文件中可以包含幾個類 - 已編譯類**不包含注釋(comment)**,但可以包含類、字段方法和代碼屬性(Attribute),Java 5 引入注解(annotaion)后,屬性已經變得沒有什么用處了 - 已編譯類中不包含 **package** 和 **import**,因此所有類型(Type)都是全限定的(Qualified) - 已編譯類中包含 [常量池(constant pool)](http://wiki.baidu.com/pages/viewpage.action?pageId=1285072081),其中包含了在類中出現的所有數值、字符串、和類型常量 (但 ASM 隱藏了與常量池有關的所有細節) ### 2.1 內部名 (InternalName) 類型(Type)只能是類(class)或接口類型(interface),類型在已編譯類中用**內部名(InternalName)** 表示;一個類的內部名就是這個類的完全限定名,`.` 號用 `/` 表示,例如 **String** 的內部名為 `java/lang/String` ### 2.2 類型描述符 (Type Descriptor) 內部名只能用于表示類或接口類型,所有其他類型,在已編譯類中都是用類型描述符(Type Descriptor)表示,如 `表2.2.1` 所示。 <blockquote id="表2.2.1">表2.2.1 - 類型描述符示例</blockquote> Java 類型 | 類型描述符 | 補充說明 ---- | ---- boolean | Z | char | C | byte | B | short | S | int | I | float | F | long | J | double | D | Object | Ljava/lang/Object; | 用 `L**;` 表示一個類,以 `;` 號結尾 int[] | [I | 數組類型的描述符是一個 `[` 號后加上其**組件類型**的描述符 Object[][] | [[Ljava/lang/Obejct; | ### 2.3 方法描述符(Method Descriptor) 方法描述符是一個類型描述符列表,用一個字符串描述一個方法的參數類型(parameter Type)和返回類型(return Type),如 `表2` 所示。 <blockquote id="表2.2.2">表2.2.2 - 方法描述符示例</blockquote> 源文件中的方法聲明 | 方法描述符 ---- | ---- void m(int i, float f) | (IF)V int m(Object o) | (Ljava/lang/Object;)I int[] m (int i, String s) | (ILjava/lang/String;)[I Object m(int[] arr) | ([I)Ljava/lang/Object; ## 3 與已編譯類相關的 ASM API ASM 提供了三個基于 ClassVisitor API 的核心組件,用于生成和轉換類: - **ClassReader** 可以看作一個事件產生器,可以分析 `byte[]` 形式的已編譯類,并調用 accept 方法入參里 ClassVisitor 實例的 visitXxx 方法 (`void ClassReader#accept(ClassVisitor, Attribute[], int)`) - **ClassWriter** 可以看作一個事件使用器,其直接以二進制形式生成已編譯類 (`byte[] ClassWriter#toByteArray()`) - **ClassVistor** 可以看作一個事件篩選器,可以將它收到的所有**方法調用**都委托給另一個 ClassVisitor 類 ### 3.1 ClassVisitor:類的生成與轉換 用于生成和轉換已編譯類的 ASM API 是基于 **ClassVisitor** 抽象類的: <blockquote id="清單3.1.1">清單3.1.1 - ClassVisitor 源碼</blockquote> ```java public abstract class ClassVisitor { protected final int api; protected ClassVisitor cv; public ClassVisitor(final int api) {...} public ClassVisitor(final int api, final ClassVisitor cv) {...} public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {...} public void visitSource(String source, String debug) {...} public void visitOuterClass(String owner, String name, String desc) {...} public AnnotationVisitor visitAnnotation(String desc, boolean visible) {...} public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {...} public void visitAttribute(Attribute attr) {...} public void visitInnerClass(String name, String outerName, String innerName, int access) {...} public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {...} public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {...} public void visitEnd() {...} } ``` **ClassVisitor 類的方法必須按以下順序調用:** 1. 首先調用 visit 2. 然后是對 visitSource 的最多一次調用 3. 接下來是對 visitOuterClass 的最多一次調用 4. 然后可按任意順序對 visitAnnotation 和 visitAttribute 的任意多次調用 5. 接著可按任意順序對 visitInnerClass、visitField 或 visitMethod 的任意多次調用 6. 最后是對 visitEnd 的一次調用 <blockquote id="清單3.1.2">清單3.1.2 - ClassVisitor 類方法的訪問順序</blockquote> ``` visit visitSource? visitOuterClass? (visitAnnotation | visitAttribute)* (visitInnerClass | visitField | visitMethod)* visitEnd ``` ### 3.2 ClassReader:類的分析 在分析一個已存在的類時,唯一必需的組件是 ClassReader,以下是一個用例。 <blockquote id="清單3.2.1">清單3.2.1 - 一個用于讀取 HashMap 類信息的 ClassReader 示例</blockquote> ```java public class PrinterClassVisitor extends ClassVisitor { public static void main(String[] args) throws IOException { PrinterClassVisitor cv = new PrinterClassVisitor(); ClassReader cr = new ClassReader("java.util.HashMap"); cr.accept(cv, 0); } public PrinterClassVisitor() { super(Opcodes.ASM4); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { System.out.println(name + " extends " + superName + " {"); } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { System.out.println(" " + desc + " " + name); return null; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { System.out.println(" " + name + desc); return null; } @Override public void visitEnd() { System.out.println("}"); } } /* 控制臺輸出: java/util/HashMap extends java/util/AbstractMap { J serialVersionUID I DEFAULT_INITIAL_CAPACITY ... hash(Ljava/lang/Object;)I ... internalWriteEntries(Ljava/io/ObjectOutputStream;)V } */ ``` ### 3.3 ClassWriter:類的生成 為了生成一個類,唯一必需的組件是 ClassWriter,以下是一個用 ClassWriter 生成 A 接口的例子。 > 清單3.3.1 - `pkg.A` 接口 ```java package pkg; public interface A extends Runnable { int field1 = -1; int field2 = 0; int method1 = (Object o); } ``` > 清單3.3.1 - 用于生成接口 `pkg/A` 的 ClassWriter 用例 ```java public class ClassWriterTest { public static void main(String[] args) { int jdkVersion = V1_5; int accessFlags = ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE; String name = "pkg/A"; String signature = null; String superName = "java/lang/Object"; String[] interfaces = new String[]{"java/lang/Runnable"}; ClassWriter cw = new ClassWriter(0); cw.visit(jdkVersion, accessFlags, name, signature, superName, interfaces); cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "field1", "I", null, -1).visitEnd(); cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "field2", "I", null, 0).visitEnd(); cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "method1", "(Ljava/lang/Object;)I", null, null).visitEnd(); cw.visitEnd(); // 使用生成的類 // cw.toByteArray(); } } ``` **使用 ClassWriter 生成的類:** `cw.toByteArray()` 返回的字節數組可以存儲到 `A.class` 文件中,也可以通過**類加載器(ClassLoader)**動態加載之。 > 清單3.3.2 - 通過自定義類加載器使用 `ClassWriter` 生成的類 ```java public class ClassWriterTest { public static void main(String[] args) { ClassWriter cw = new ClassWriter(0); // 生成類 ... // 使用生成的類 byte[] bytes = cw.toByteArray(); AClassLoader cl = new AClassLoader(); Class classA = cl.defineClass("pkg.A", bytes); // 使用 classA ... } private static class AClassLoader extends ClassLoader { public Class defineClass(String name, byte[] bytes) { return defineClass(name, bytes, 0, bytes.length); } } } ``` ### 3.4 轉換類 (transfor) (1) 將 ClassReader 產生的事件轉給 ClassWriter <blockquote id="清單3.4.1">清單3.4.1</blockquote> <pre style="font-family: 'Courier New','MONACO'"> byte[] in = readClassByte(); ClassReader cr = new ClassReader(in); ClassWriter cw = new ClassWriter(0); cr.accept(cw, 0); byte[] out = cw.toByteArray(); </pre> (2) 在 ClassReader 和 ClassWriter 之間引入一個 ClassVisitor <blockquote id="清單3.4.2">清單3.4.2</blockquote> <pre style="font-family: 'Courier New','MONACO'"> byte[] in = readClassByte(); ClassReader cr = new ClassReader(in); ClassWriter cw = new ClassWriter(0); <b>ClassVisitor cv = newClassVistor(cw); cr.accept(cv, 0);</b> byte[] out = cw.toByteArray(); </pre> (3) 重寫 ClassVisitor 的方法,實現相應的功能 <blockquote id="清單3.4.3">清單3.4.3</blockquote> <pre style="font-family: 'Courier New','MONACO'"> private static ClassVisitor newClassVistor(ClassWriter cw) { return new AClassVisitor(cw); } private static class AClassVisitor extends ClassVisitor { public AClassVisitor(ClassVisitor cw) { super(ASM4, cw); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(<b>V1_5</b>, access, name, signature, superName, interfaces); // 可以修改版本號 } } </pre> (4) 上面的代碼中,整個 `in` 都被**分析**,并**重新構建**了 `out`,如果**將 in 中不被轉換的部分直接拷貝到 out 中,不對其分析,也不生成相應的事件**,效率會高得多;ASM 會自動為方法執行這一優化: - 在 ClassReader#accept 中傳入了 ClassVisitor,如果返回的 MethodVisitor 來自一個 ClassWriter,則整個方法的內容將不會被轉換 - 在這種情況下,ClassReader 不會分析這個方法的內容,也不會生成相應的事件,只是復制 ClassWriter 中表示這個方法的字節數組 <blockquote id="清單3.4.4">清單3.4.4</blockquote> <pre style="font-family: 'Courier New','MONACO'"> byte[] in = readClassByte(); ClassReader cr = new ClassReader(in); <b>ClassWriter cw = new ClassWriter(cr, 0);</b> //執行這一優化 ClassVisitor cv = newClassVistor(cw); cr.accept(cv, 0); byte[] out = cw.toByteArray(); </pre> ### 3.5 移除類成員 在重寫 ClassVisitor 的方法時,**不轉發相應的調用**,可以**移除**相應的類成員(member)。 <blockquote id="清單3.5.1">清單3.5.1 - 一個移除類字段的例子</blockquote> <pre style="font-family: 'Courier New','MONACO'"> class RemoveMethodAdapter extends ClassVisitor { <b>private String mName; private String mDesc;</b> public RemoveMethodAdapter(ClassVisitor cv, String mName, String mDesc) { super(ASM4, cv); this.mName = mName; this.mDesc = mDesc; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { <b>if (name.equals(mName) && desc.equals(mDesc)) { return null; // 不要委托至下一個訪問器,這樣將移除該方法 }</b> return cv.visitMethod(access, name, desc, signature, exceptions); } } </pre> ### 3.6 增加類成員 在重寫 ClassVisitor 的方法時,**遵循 `2.1` 的規則,多轉發一些調用**,可以**增加**相應的類成員(member)。 例如,如果要向類中添加一個字段,必須在原方法調用之間添加對 visitField 的一個新調用,而且必須將這個新調用放在類適配器的一個訪問方法中(可以在 **visitEnd** 中添加字段,確保字段名稱不會重復),以下是一個在類中添加字段的用例: <blockquote id="清單3.6.1">清單3.6.1 - 一個增加類字段的例子</blockquote> <pre style="font-family: 'Courier New','MONACO'"> class AddFieldAdapter extends ClassVisitor { private int fAcc; private String fName; private String fDesc; private boolean isFieldPresent; public AddFieldAdapter(ClassVisitor cv, int fAcc, String fName, String fDesc) { super(ASM4, cv); this.fAcc = fAcc; this.fName = fName; this.fDesc = fDesc; } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { <b>if (name.equals(fName)) { isFieldPresent = true; // 確保字段不會重復 }</b> return cv.visitField(access, name, desc, signature, value); } @Override public void visitEnd() { <b>if (!isFieldPresent) { FieldVisitor fv = cv.visitField(fAcc, fName, fDesc, null, null); if (fv != null) { // 一個類訪問器可以在 visitEnd 中返回 null fv.visitEnd(); } }</b> cv.visitEnd(); } } </pre> ### 3.7 轉換鏈 將幾個適配器鏈接在一起,可以組成幾個獨立的類轉換,以完成復雜轉換。 <blockquote id="清單3.7.1">清單3.7.1 - 通過編寫一個 ClassVisitor 將接收到的方法調用同時轉發給幾個 ClassVisitor</blockquote> <pre style="font-family: 'Courier New','MONACO'"> class MultiClassAdapter extends ClassVisitor { <b>protected ClassVisitor[] cvs;</b> public MultiClassAdapter(ClassVisitor[] cvs) { super(ASM4); this.cvs = cvs; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { <b>for (ClassVisitor cv : cvs) { cv.visit(version, access, name, signature, superName, interfaces); }</b> } } </pre> > 清單3.7.2 - 一個相對復雜的轉換鏈示意圖 <pre style="font-family: 'Courier New','MONACO'"> CR → [ ] [ ] → [ ] ↘ ↗ ↘ [ ] → [ ] → [ ] CR → [ ] ↗ ↘ ↘ ↘ CW ↘ ↗ CR → [ ] → [ ] </pre> ## 4 已編譯方法的結構表示 > 在已編譯類的內部,方法的代碼存儲為一系列**字節碼**指令,為了生成和轉換類,最根本的辦法就是**要了解這些指令,并理解它們是如何工作的** ### 4.1 執行模式 - Java 代碼在**線程**內部執行 - 每個線程都有自己的執行棧,棧由**幀**組成 - 每個幀表示一個**方法調用**,每一幀包括一個**局部變量**部分和一個**操作數棧**部分 - 局部變量部分與操作數棧部分的大小取決于方法的代碼,在**編譯時計算完成**,并隨字節代碼指令一起存儲在已編譯類中 以下是一個具有 3 幀的執行棧。第 1 幀包含 3 個局部變量,其操作數棧的最大值為 4,其中包含 2 個值;第 2 幀包含 2 個局部變量,操作數棧中有 2 個值;第 3 幀位于執行棧的頂端,包含 4 個局部變量和 2 個操作數。 > 清單4.1.1 - 一個具有 3 幀的執行棧 <pre style="font-family: 'Courier New','MONACO'"> —————————————————— ——————————————— —————————————————————— | Frame 1 | | Frame 2 | | Frame 3 | | [L0] [L1] [L2] | | [L0] [L1] | | [L0] [L1] [L2][L3] | | [V0][V2][][] | | [V0][V2][] | | [V0][V2] | —————————————————— ——————————————— —————————————————————— </pre> - 非靜態(static)方法需要保存 this 引用。對于非靜態方法,在創建一個幀時,會對其初始化提供一個空棧,并用目標對象 `this` 及該方法的參數來初始化其局部變量。例如調用 `a.equals(b)`時,將創建 1 幀,前 2 個局部變量將被初始化為 a 和 b - long 和 double 需要兩個變量槽。局部變量部分和操作數棧部分的每個**槽(slot)**可以保存 **long 和 double 變量之外的任意 Java 值**。 ### 4.2 字節碼指令 **字節碼的構成:** 字節碼指令由標識該指令的**操作碼**和固定數目的**參數**組成。 - 操作碼是一個 unsigned byte - 參數是靜態值,確定了精確的指令行為,緊跟操作碼之后 **字節碼的分類:** 字節碼可以分為兩類,一類用于在局部變量和操作數棧之間傳送值,一類僅用于操作數棧。 **用于在局部變量和操作數棧之間傳送值的字節碼:** - 讀取一個局部變量,并將其值壓到操作樹棧中,其參數是局部變量的索引 i(必須讀取):iload, lload, fload, dload, aload - 從操作數棧中彈出一個值,并將其值存儲在由索引 i 指定的局部變量中:istore, lstore,fstore, dstore, astore > 注:將一個值存儲在局部變量中,然后再以不同類型加載之,是非法的;但是如果向局部變量中存儲值,而該值不同于該局部變量中存儲的當前值,卻是合法的。例如 `istore 1 aload 1` 序列是非法的。 **僅用于操作數棧的字節碼:** - 用于處理 **棧** 上的值:`pop` 彈出棧頂部的值;`dup` 壓入頂部棧值的一個副本;`swap` 彈出兩個值,并按逆序壓入之;…… - 在操作數棧壓入一個 **常量** 值:`aconst_null` 壓入 null;`iconst_0` 壓入 int 值 0;`fconst_0` 壓入 0f,`dconst_0` 壓入 0d,`bipush B` 壓入 byte 值 B;`sipush S` 壓入 short 值 S;`ldc CST` 壓入任意 int、float、long、double、String 或 class 常量 CST;…… - 從操作數棧彈出數值,進行**算術邏輯**處理后,將結果壓入棧中:`*add`、`*sub`、`*mul`、`*div`、`*rem` 分別對應于 `+`、`-`、`*`、`/`、`%` 運算,其中 `*` 表示 `i`、`l`、`f` 或 `d`;還有 `<<`、`>>`、`>>>`、`|`、`&`、`^` 運算的對應指令,用于處理 int 和 long 值 - 從棧中彈出一個值,進行**類型轉換**后,將結果壓入棧中:`i2f`、`f2d`、`l2d` 等,將數值由一種類型轉換為另一種類型;`checkcast T` 將一個引用值轉換為類型 T - 用于創建**對象**、鎖定對象、檢測對象類型等:例如 `new TYPE` 將一個 TYPE 類型的新對象壓入棧中(TYPE 是一個內部名) - 用于讀寫一個**字段**的值:`getfield OWNER NAME DESC` 彈出一個對象引用,并壓入其 NAME 字段的值;`putfield OWNER NAME DESC` 彈出一個值和對象引用,并將這個值存儲在它的 NAME 字段中;這兩種情況中,對象必須為 OWNER 類型,字段必須為 DESC 類型;`getstatic` 和 `putstatic` 是類似指令,用于靜態字段(static field)。 - 用于調用一個**方法**或構造方法(其彈出值的個數等于其方法參數個數加1(用于目標對象)),并壓回方法調用的結果:`invokevirtual OWNER NAME DESC` 調用在類 OWNER 中定義的 NAME 方法,其方法描述為 DESC;`invokestatic OWNER NAME DESC` 用于調用靜態方法; `invokespecial` 用于私有方法和構造方法;`invokeinterface` 用于接口中定義的方法;`invokedynamic` 用于動態方法調用機制。 - 用于讀寫**數組**的值:`Xaload` 彈出一個索引和一個數組,并壓入此索引處該數組元素的值;`Xastore` 彈出一個值,一個索引和一個數組,并將這個值存儲在數組的這一索引處;這里的 X 可以是 `i`、`l`、`f`、`d`、`a`,`b`、`c`、`s`。 - 用于無條件地或者在某一條件為真時**跳轉**到到一條任意指令,用于編譯 if、for、do、while、break 和 continue、switch、case 等:例如 `ifeq LABEl` 從棧中彈出一個 int 值,如果該值為 0,則跳轉到 LABEL 指定的指令處(否則正常執行下條指令);還有一些跳轉指令,諸如 `ifne`、`ifge`、`tableswitch`、`lookupswitch` ### 4.3 字節碼指令(執行引擎)工作原理 本節基于以下 User 類,介紹字節碼指令的工作原理。 > 清單4.3.1 - 用于演示字節碼指令工作原理的 <code id="清單4.3.1">User</code> 類 ```java public class User { private int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void checkAndSetAge(int age) { if (age > 0) { this.age = age; } else { throw new IllegalArgumentException(); } } } ``` **User#getAge 說明:** > 清單4.3.2 - `User#getAge` 方法的字節碼指令 ```java public int getAge(); Code: 0: aload_0 1: getfield #2 // Field age:I 4: ireturn ``` - 第 1 條指令讀取局部變量0,并這個 0 值壓入操作數棧 - 第 2 條指令從棧中彈出這個值,即 `this`,并將對象的 `age` 字段壓入棧中,即 `this.age` - 第 3 條指令從棧中彈出這個值,并將其返回給調用者 在這個過程中,執行幀的持續狀態如下: > 清單4.3.3 - `User#getAge` 方法的執行幀的狀態 <pre style="font-family: 'Courier New','MONACO'"> (1) 初始狀態 (2) aload_0 之后 (3) getfield 之后 [this] [this] [this ] [ ] [this] [this.age] </pre> **User#setAge 說明:** > 清單4.3.4 - `User#setAge` 方法的字節碼指令 ```java public void setAge(int); Code: 0: aload_0 1: iload_1 2: putfield #2 // Field age:I 5: return ``` - 第 1 條指令將 `this` 壓入操作數棧 - 第 2 條指令壓入局部變量 1,在為這個**方法調用**創建幀期間,以 age 參數初始化該變量 - 第 3 條指令彈出這兩個值,并將 int 值存儲在被引用對象的 age 字段中,即存儲在 `this.age` 中 - 第 4 條指令表示銷毀當前執行幀(編譯后強制生成),并返回調用者 > 清單4.3.5 | `User#setAge` 方法的執行幀的狀態 <pre style="font-family: 'Courier New','MONACO'"> (1) 初始狀態 (2) aload_0 之后 (3) iload_1 之后 (4) putfield 之后 [this][age] [this][age] [this][age] [this][age] [ ][ ] [this][ ] [this][age] [ ][ ] </pre> **User 默認的構造方法說明:** > 清單4.3.6 - `User#<init>` 方法的字節碼指令 ```java public User(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return ``` - 第 1 條指令將 `this` 壓入操作數棧中 - 第 2 條指令從棧中彈出這個值,并調用 `Object#<init>` 方法 - 第 3 條指令返回調用者 **User#checkAndSetAge 方法說明:** > 清單4.3.7 - `User#<init>` 方法的字節碼指令 ```java public void checkAndSetAge(int); Code: 0: iload_1 1: ifle 12 4: aload_0 5: iload_1 6: putfield #2 // Field age:I 9: goto 20 12: new #3 // class java/lang/IllegalArgumentException 15: dup 16: invokespecial #4 // Method java/lang/IllegalArgumentException."<init>":()V 19: athrow 20: return ``` - 第 1 條指令將初始化為 age 的局部變量1 壓入操作數棧 - 第 2 條指令(ifle)從棧中彈出 1 個值,并將其與 0 進行比較,如果小于等于(le) 0,則跳轉到程序計數器(pc)為12的指令,否則不做任何事,繼續執行下一條指令 - 第 3 ~ 5 條指令與 `setAge` 方法類似 - 第 6 條指令(goto)表示無條件跳轉到 pc 為 20 的指令,即返回 - 第 7 條指令(new)表示創建一個 IllegalArgumentException 實例,并將其壓入操作數棧 - 第 8 條指令(dup)表示重復這個值 - 第 9 條指令(invokespecial)表示彈出這兩個副本之一,并對其調用構造方法 - 第 10 條指令(athorw)彈出剩下的副本,并將它作為異常拋出(方法異常調用完成),之后不會繼續執行下一條指令 **異常表(Exception table):** 不存在用于捕獲異常的字節碼,而是將一個方法的字節碼與一個**異常表(Exception table)**相關聯,異常表規定了在某方法中一給定部分拋出異常時必須執行的代碼。對于下面的 tryCatchFinallyTest 方法: > 清單4.3.8 - `tryCatchFinallyTest` 方法,該方法會返回 `300` ```java public static int tryCatchFinallyTest() { try { return 100; } catch (Exception ex) { return 200; } finally { return 300; } } ``` 其字節碼指令如下: > 清單4.3.9 - `tryCatchFinallyTest` 方法的字節碼指令 ```java public static int tryCatchFinallyTest(); Code: 0: bipush 100 2: istore_0 3: sipush 300 6: ireturn 7: astore_0 8: sipush 200 11: istore_1 12: sipush 300 15: ireturn 16: astore_2 17: sipush 300 20: ireturn Exception table: from to target type 0 3 7 Class java/lang/Exception 0 3 16 any 7 12 16 any } ``` - `from=0, to=3, target=7, type=Exception` 表示:對于 pc 為 0 ~ 3 的指令(try 語句塊),如果發生類型為 java/lang/Exception 的異常,將跳轉到 pc=7 處,否則執行 pc=3 之后的 `ireturn` - `from=0, to=3, target=16, type=any` 表示:執行完 pc 為 0 ~ 3 的指令(try 語句塊)后,無條件跳轉到 pc=16 處的指令(finally 語句塊) - `from=7, to=12, target=16, type=any` 表示:執行完 pc 為 7 ~ 12 的指令(catch 語句塊)后,無條件跳轉到 pc=16 處的指令(finally 語句塊) ## 5 與已編譯方法相關的 ASM API ASM 提供了三個基于 MethodVisitor API 的核心組件,用于生成和轉換方法: - ClassReader 可以看作一個事件產生器,可以分析已編譯方法的內容 (void ClassReader#accept(ClassVisitor, Attribute[], int)) - ClassWriter 可以直接以二進制形式生成已編譯方法 (MethodVisitor ClassWriter#visitMethod(access, name, desc, signature, exceptions)) - MethodVistor 可以看作一個事件篩選器,將它收到的所有方法調用都委托給另一個 MethodVistor 類 ### 5.1 MethodVisitor:方法的生成與轉換 用于生成和轉換已編譯方法的 ASM API 是基于 MethodVisitor 抽象類的: > 清單5.1.1 - MethodVisitor 源碼 ```java public abstract class MethodVisitor { protected final int api; protected MethodVisitor mv; public MethodVisitor(final int api) {} public MethodVisitor(final int api, final MethodVisitor mv) {} // Parameters, annotations and non standard attributes public void visitParameter(String name, int access) {} public AnnotationVisitor visitAnnotationDefault() {} public AnnotationVisitor visitAnnotation(String desc, boolean visible) {} public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {} public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {} public void visitAttribute(Attribute attr) {} public void visitCode() {} public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {} // Normal instructions public void visitInsn(int opcode) {} public void visitIntInsn(int opcode, int operand) {} public void visitVarInsn(int opcode, int var) {} public void visitTypeInsn(int opcode, String type) {} public void visitFieldInsn(int opcode, String owner, String name, String desc) {} public void visitMethodInsn(int opcode, String owner, String name, String desc) {} public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {} public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {} public void visitJumpInsn(int opcode, Label label) {} public void visitLabel(Label label) {} // Special instructions public void visitLdcInsn(Object cst) {} public void visitIincInsn(int var, int increment) {} public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {} public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {} public void visitMultiANewArrayInsn(String desc, int dims) {} public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {} // Exceptions table entries, debug information, max stack and max locals public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {} public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {} public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {} public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String desc, boolean visible) {} public void visitLineNumber(int line, Label start) {} public void visitMaxs(int maxStack, int maxLocals) {} public void visitEnd() {} } ``` **MethodVisitor 類的方法必須按以下順序調用:** > 清單5.1.2 - MethodVisitor 類方法的調用順序 ```java visitAnnotationDefault? (visitAnnotation | visitParameterAnnotation | visitAttribute)* (visitCode (visitTryCatchBlock | visitLabel | visitFrame | visitXxxInsn | visitLocalVariable | visitLineNumber)* visitMaxs)? visitEnd ``` - visitCode 和 visitMaxs 方法可用于**檢測方法的字節碼在一個事件序列中的開始與結束** - visitEnd 必須在最后調用,用于檢測一個方法在一個事件序列中的結束 - 可以將 ClassVisitor 和 MethodVisitor 類合并,生成完整的類 > 清單5.1.3 - 將 ClassVisitor 和 MethodVisitor 類合并,生成完整的類 [書寫格式] <pre style="font-family: 'Courier New','MONACO'"> PrinterClassVisitor cv = new PrinterClassVisitor(); MethodVisitor mv1 = cv.visitMethod(0, "m1", null, null, null); mv1.visitCode(); mv1.visitInsn(0); // ... mv1.visitMaxs(0, 0); mv1.visitEnd(); MethodVisitor mv2 = cv.visitMethod(0, "m2", null, null, null); mv2.visitCode(); mv2.visitInsn(0); // ... mv2.visitMaxs(0, 0); mv2.visitEnd(); cv.visitEnd(); </pre> > 清單5.1.4 - 將 ClassVisitor 和 <code id="清單5.1.4">MethodVisitor</code> 類合并,生成完整的類 [代碼示例] <pre style="font-family: 'Courier New','MONACO'"> /** * 生成默認的構造方法 * * @param superName 父類名稱 */ static void generateDefaultConstruct(ClassWriter cw, String superName) { MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, MethodName.CONSTRUCTOR, MethodDesc.EMPTY_VOID, null, null); // 生成構造方法的字節碼指令 mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, superName, MethodName.CONSTRUCTOR, MethodDesc.EMPTY_VOID, false); mv.visitInsn(RETURN); mv.visitMaxs(1, 1); mv.visitEnd(); } /** * 生成類方法 */ private static void generateMethod(ClassWriter cw, Class<?> beanClass, boolean usePropertyDescriptor) { String internalClassName = BeanEnhanceUtils.getInternalName(beanClass.getName()); ClassDesc classDesc = BeanEnhanceUtils.getClassDescription(beanClass, usePropertyDescriptor); MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, MethodName.VALUE, MethodDesc.VALUE, null, null); mv.visitCode(); // 有屬性,需要調用 getter 方法 if (classDesc.hasField) { generateMethodWithFields(internalClassName, classDesc, mv); } else { generateMethodWithNoField(mv, classDesc, internalClassName); } mv.visitEnd(); } /** * 生成beanClass對應的增強類的字節流 * * @param superName 父類 * @param interfaces 接口列表 */ static byte[] generate(Class<?> beanClass, String superName, String[] interfaces, boolean usePropertyDescriptor) throws Exception { String beanClassName = beanClass.getName(); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);// 自動計算maxStack String getterClassName = createGeneratedClassName(beanClassName); cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, BeanEnhanceUtils.getInternalName(getterClassName), null, superName, interfaces); <b>generateDefaultConstruct(cw, superName);</b> // 生成默認構造方法 <b>generateMethod(cw, beanClass, usePropertyDescriptor);</b> // 生成GetterClass cw.visitEnd(); return cw.toByteArray(); } </pre> **new ClassWriter(int flag) 選項:** - flag 為 0 時,不會進行自動計算,必須自行計算幀,局部變量與操作數棧的大小 - flag 為 `ClassWriter#COMPUTE_MAXS` 時(性能降低10%),ASM 將自動計算局部變量與操作數棧部分的大小,還是必須調用 visitMaxs 方法(可以使用任何參數,但是會被忽略并重新計算),必須自行計算棧幀(實例中 generateDefaultConstruct 方法便是自行計算棧幀) - flag 為 `ClassWriter#COMPUTE_FRAMES` 時(性能降低50%),一切都是自動計算,不再需要調用 visitFrame,但仍然必須調用 visitMaxs 方法(參數將被忽略并重新計算) ### 5.2 方法的生成 針對以下 User 類(拷貝自 [清單4.3.1](#清單4.3.1)): > 清單5.2.1 - 用于演示 MethodVisitor 方法生成的 User 類 ```java public class User { private int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void checkAndSetAge(int age) { if (age > 0) { this.age = age; } else { throw new IllegalArgumentException(); } } } ``` **生成 getAge 方法的 MethodVisitor 用例寫法如下:** > 清單5.2.2 - 用于生成 `User#getAge` 方法的 MethodVisitor 用例 ```java MethodVisitor mv = getMethodVisitor(); mv.visitCode(); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitFieldInsn(Opcodes.GETFIELD, "pkg/User", "age", "I"); mv.visitInsn(Opcodes.IRETURN); mv.visitMaxs(1, 1); mv.visitEnd(); ``` **生成 checkAndSetAge 方法的 MethodVisitor 用例寫法如下:** > 清單5.2.3 - 用于生成 `User#checkAndSetAge` 方法的 MethodVisitor 用例 <pre style="font-family: 'Courier New','MONACO'"> MethodVisitor mv = getMethodVisitor(); // public void checkAndSetAge(int); mv.visitCode(); // Code: mv.visitVarInsn(Opcodes.ILOAD, 1); // 0: iload_1 Label thenBlockLabel = new Label(); // (pc = 12) mv.visitJumpInsn(Opcodes.IFLE, thenBlockLabel); // 1: ifle 12 mv.visitVarInsn(Opcodes.ALOAD, 0); // 4: aload_0 mv.visitVarInsn(Opcodes.ILOAD, 1); // 5: iload_1 mv.visitFieldInsn(Opcodes.PUTFIELD, "User", "age", "I"); // 6: putfield #2 // Field age:I Label elseBlockLabel = new Label();// (pc = 20) mv.visitJumpInsn(Opcodes.GOTO, elseBlockLabel); // 9: goto 20 mv.visitLabel(thenBlockLabel); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); mv.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalArgumentException"); // 12: new #3 // class java/lang/IllegalArgumentException mv.visitInsn(Opcodes.DUP); // 15: dup mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, // 16: invokespecial #4 // Method java/lang/IllegalArgumentException."<init>":()V "java/lang/IllegalArgumentException", "<init>", "()V"); mv.visitInsn(Opcodes.ATHROW); // 19: athrow mv.visitLabel(elseBlockLabel); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); mv.visitInsn(Opcodes.RETURN); // 20: return mv.visitMaxs(2, 2); mv.visitEnd(); </pre> ## 6 通過 ASM 為模板引擎實現運行時類增強 ### 6.1 模板引擎示例 假設存在以下一種使用模板引擎的場景: 1. 開發者創建了 [清單4.3.1](清單4.3.1) 中的 `User` Java 類,如 `清單6.1.1` 所示 2. 開發者在 `user.html` 文件中自定義了數據模板,如 `清單6.1.2` 所示 3. 開發者調用模板引擎,進行數據綁定與結果渲染,如 `清單6.1.3` 所示 4. 模板引擎輸出渲染結果,如 `清單6.1.4` 所示 > 清單6.1.1 - 用于演示模板引擎的 User 類 ```java public class User { private int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void checkAndSetAge(int age) { if (age > 0) { this.age = age; } else { throw new IllegalArgumentException(); } } } ``` > 清單6.1.2 - `user.html` 文件的內容 ```html <pre> idUserMap 中 key 為 1 所對應的 User 實例的 age = ${idUserMap[1].age} userList 中下標為 1 所對應的 User 實例的 age = ${userList[1].age} 變量 min_age = ${min_age} </pre> <ul><!-- 遍歷名稱為 "userList" 的列表 --> <% for (user in userList) { %> <li>${user.age}</li> <% } %> </ul> <table><!-- 遍歷名稱為 "idUserMap" 的映射 --> <% for (entry in idUserMap) {%> <tr> <td>${entry.key}</td> <td>${entry.value.age}</td> </tr> <% } %> </table> ``` > 清單6.1.3 - 通過模板引擎進行數據綁定并渲染結果 ```java Template t = gt.getTemplate("/user.html"); User zhangsan = new User(21); User lisi = new User(22); User wangwu = new User(23); Map<Integer, User> idUserMap = new HashMap<>(); idUserMap.put(1, zhangsan); idUserMap.put(2, lisi); List<User> userList = Arrays.asList(zhangsan, lisi, wangwu); t.binding("zhangsan", zhangsan); t.binding("idUserMap", idUserMap); t.binding("userList", userList); t.binding("min_age", 18); String res = t.render(); System.out.println(res); ``` > 清單6.1.4 - 模板引擎的數據渲染結果 ```html <pre> idUserMap 中 key 為 1 所對應的 User 實例的 age = 21 userList 中下標為 1 所對應的 User 實例的 age = 22 變量 min_age = 18 </pre> <ul><!-- 遍歷名稱為 "userList" 的列表 --> <li>21</li> <li>22</li> <li>23</li> </ul> <table><!-- 遍歷名稱為 "idUserMap" 的映射 --> <tr> <td>1</td> <td>21</td> </tr> <tr> <td>2</td> <td>22</td> </tr> </table> ``` ### 6.2 運行時生成類實例 在 `6.1` 中演示了模板引擎的使用,本文不討論其語法處理(例如處理循環)的實現,而只是介紹模板引擎如何處理 `User` 類。主要分為以下幾個步驟: 1. 將所有支持的類型(List/Map/Array/Model/...)抽象到 `AttributeAccess` 類中(參考`清單6.2.1`),運行時生成 `User$AttributeAccess` 類的 `byte[]`,設為 `code` 2. 運行時通過自定義類加載器加載`code`(參考`清單6.2.2` 與 `清單6.2.3`),定義 `User$AttributeAccess` 類,并返回該類的實例,設為 `user1` 3. 將 `user1` 提供給模板引擎使用 > 清單6.2.1 AttributeAccess 抽象類,封裝了一個獲取對象的屬性的值的方法 ```java public abstract class AttributeAccess implements java.io.Serializable { public abstract Object value(Object o, Object name); public void setValue(Object o, Object name, Object value) { updateValue(o, name, value); } // ... } ``` > 清單6.2.2 自定義類加載器 ```java public class ByteClassLoader extends ClassLoader { public ByteClassLoader(ClassLoader parent) { super(parent); } public Class<?> defineClass(String name, byte[] b) { return defineClass(name, b, 0, b.length); } public Class<?> findClassByName(String clazzName) { try { return getParent().loadClass(clazzName); } catch (ClassNotFoundException ignored) { } return null; } } ``` > 清單6.2.3 通過自定義類加載器加載 code ```java private Object loadContextClassLoader(byte[] code, String className) { Object obj; try { Class<?> enhanceClass = byteContextLoader.findClassByName(className); if (enhanceClass == null) { enhanceClass = byteContextLoader.defineClass(className, code); } obj = enhanceClass.newInstance(); } catch (Exception ex) { return null; } return obj; } ``` 綜上所述,問題變為: 如何通過 ASM 生成 `User$AttributeAccess` 類 ### 6.3 通過 ASM 進行類的生成與轉換 運行時,會通過 ASM 生成 AttributeAccess 的子類 `User$AttributeAccess`,參考 [清單5.1.4](#清單5.1.4) 中的 `generate` 方法,ASM 會為 `User$AttributeAccess` 生成以下方法: - 無參構造方法 - value 方法,對應實現 `AttributeAccess#value(Object, Object)` 方法 > 清單6.3.1 - `generate` 方法 ```java // 生成 AttributeAccess 對應的增強類的字節數組 generate(beanClass, InternalName.ATTRIBUTE_ACCESS, null, usePropertyDescriptor); /** * 生成 beanClass 對應的增強類的字節流 * * @param superName 父類 * @param interfaces 接口列表 */ static byte[] generate(Class<?> beanClass, String superName, String[] interfaces, boolean usePropertyDescriptor) throws Exception { String beanClassName = beanClass.getName(); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);// 自動計算maxStack String getterClassName = createGeneratedClassName(beanClassName); cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, BeanEnhanceUtils.getInternalName(getterClassName), null, superName, interfaces); // 生成默認構造方法 generateDefaultConstruct(cw, superName); // 生成類方法 generateMethod(cw, beanClass, usePropertyDescriptor); cw.visitEnd(); return cw.toByteArray(); } ``` 并為其實現 `AttributeAccess#value(Object, Object)` 方法,分三種情況: - User 中沒有 get(String) 或 get(Object) 方法,如 `清單6.3.2` 所示 - User 中包含 get(String) 方法,如 `清單6.3.3` 所示 - User 中包含 get(Object) 方法,如 `清單6.3.4` 所示 > 清單6.3.2 - User 中沒有 get(String) 或 get(Object) 方法時,ASM 生成的 value 方法 [示例代碼] <pre style="font-family: 'Courier New','MONACO'"> public Object value(Object bean, Object attr) { String attrStr = attr.toString(); int hash = attrStr.hashCode(); User user = (User) bean; switch (hash) { case 1: return user.getAge(); case 2: return user.getXxx(); case 3: if("age".equals(attrStr)){ return user.getAge(); } if("xxx".equals(attrStr)){ return user.getXxx(); } } <b>throw new RuntimeException(ATTRIBUTE_NOT_FOUND, "attribute : " + attrStr);</b> } </pre> > 清單6.3.3 - User 中包含 get(String) 方法時,ASM 生成的 value 方法 [示例代碼] <pre style="font-family: 'Courier New','MONACO'"> public Object value(Object bean, Object attr) { String attrStr = attr.toString(); int hash = attrStr.hashCode(); User user = (User) bean; switch (hash) { case 1: return user.getAge(); case 2: return user.getXxx(); case 3: if("age".equals(attrStr)){ return user.getAge(); } if("xxx".equals(attrStr)){ return user.getXxx(); } } <b>return user.get(attrStr);</b> } </pre> > 清單6.3.4 - User 中包含 get(Object) 方法時,ASM 生成的 value 方法 [示例代碼] <pre style="font-family: 'Courier New','MONACO'"> public Object value(Object bean, Object attr) { String attrStr = attr.toString(); int hash = attrStr.hashCode(); User user = (User) bean; switch (hash) { case 1: return user.getAge(); case 2: return user.getXxx(); case 3: if("age".equals(attrStr)){ return user.getAge(); } if("xxx".equals(attrStr)){ return user.getXxx(); } } <b>return user.get(attr);</b> } </pre> 至此,`User$AttrubiteAccess` 類的生成與轉換的過程結束,返回的 `byte[]` 將傳給自定義的類加載器。 ## 附錄 ### 附錄A 元數據的結構表示 > 表A-1 - 類型簽名舉例 Java 類型 | 對應的類型簽名 ---- | ---- `List<E>` | `Ljava/util/List<TE;>;` `List<?>` | `Ljava/util/List<*>;` `List<? extends Number>` | `Ljava/util/List<+Ljava/lang/Number;>;` `List<? super Integer>` | `Ljava/util/List<-Ljava/lang/Integer;>;` `List<List<String>[]>` | `Ljava/util/List<[Ljava/util/List<Ljava/lang/String;>;>;` `HashMap<K, V>.HashIterator<K>` | `Ljava/util/HashMap<TK;TV;>.HashIterator<TK;>;` `static <T> Class<? extends T> m (int n)` | `<T:Ljava/lang/Obejct;>(I)Ljava/lang/Class<+TT>;` > 表A-2 - 類簽名舉例 Java 類 | 對應的類簽名 ---- | ---- `C<E> extends List<E>` | `<E:Ljava/lang/Object;>Ljava/util/List<TE;>;`
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看