ASMバイトコード操作クラスライブラリ(Java言語の世界からバイトコードの世界への扉を開く) | JDクラウドテクノロジーチーム

序文:人間に釣りを教えることは、彼に釣りを教えることよりも優れています。ASM の使用に関する記事はたくさんあり、簡単なデモもたくさんあります。では、ASM にはどのような機能があるのでしょうか? ASM コードの書き方を学ぶにはどうすればよいですか? ASM の使用が必要になるのはどのようなシナリオですか? これらの疑問を念頭に置いてこの記事を読んでみましょう。

このセクションはスペースの制限により削除されました (パート 6: TreeApi と CoreApi の比較、コア API クラスの紹介など)。興味がある場合は、作成者に連絡して連絡することができます。

個人的には、核心は 5 番目の部分にあると思います。つまり、書きたいクラスの ASM コードの書き方を確認し、ASM の機能を完全に理解することで、後の特定のシナリオで次のことができることがわかります。それを使用して、実行したい機能を実現します。

1. ASM の概要

1. ASMとは何ですか?

ASM は、汎用の Java バイトコード操作および分析フレームワークです。これを使用して、既存のクラスを変更したり、バイナリ形式でクラスを動的に直接生成したりできます。ASM は、カスタムの複雑な変換およびコード分析ツールを構築できる、一般的なバイトコード変換および分析アルゴリズムを多数提供します。ASM は他の Java バイトコード フレームワークと同様の機能を提供しますが、パフォーマンスに重点を置いています。可能な限り小さく高速になるように設計および実装されているため、動的システムでの使用に適しています (もちろん、コンパイラーなどの静的方法で使用することもできます)。

.java ファイルは、Java コンパイラ (javac) によってコンパイルされた後、.class ファイルを生成します。.classファイルにはバイトコード(ByteCode)データが格納されます。ASMが操作するオブジェクトはバイトコード(ByteCode)であり、多くの場合、バイトコード(ByteCode)の具体的な形式は.classファイルです。

ASM がバイトコード (ByteCode) を処理する方法は、「分割-変更-マージ」です。

バイトコードツール クラスの作成 インターフェースを実装する メソッド呼び出し クラス拡張 親クラスのメソッド呼び出し アドバンテージ 欠点がある 一般的な用途 学習コスト
Javaプロキシ サポート サポート サポート サポートしません サポートしません シンプルな動的プロキシを推奨 機能が制限されており、拡張機能はサポートされていません スプリングオープ、MyBatis 1つ星
アズム サポート サポート サポート サポート サポート ほとんど制限なしで任意のバイトコードを挿入 学ぶのが難しく、多くのコーディングを行う必要がある cglib 5つ星
ジャワ語 サポート サポート サポート サポート サポート Java独自の構文、文字列形式の挿入、直感的な記述 jdk1.5 以降の構文 (ジェネリックなど) はサポートされていません。 ファストジソン、マイバティス 2つ星
cglib サポート サポート サポート サポート サポート バイトバディに似てる bytebuddy によって段階的に廃止される EasyMock、ジャクソンデータバインド 3つ星
バイトバディ サポート サポート サポート サポート サポート あらゆる次元でのインターセプトをサポートし、オリジナルのクラス、メソッド、プロキシ クラス、およびすべてのパラメータを取得できます あまり直観的ではなく、ある程度の学習と理解が必要で、API がたくさんあります SkyWalking、Mockito、Hibernate、powermock 3つ星

比較表参照: http://xingyun.jd.com/shendeng/article/detail/7826

  • ASM公式サイト:https://asm.ow2.io/
  • ASM ソースコード: https://gitlab.ow2.org/asm/asm
  • 開発者ガイド: https://asm.ow2.io/developer-guide.html

2. ASM では何ができるのですか?

生成、変更、削除 (インターフェイス、クラス、フィールド、メソッド...) ASM はバイトコード データを分析、生成、変換することができ、ASM が「Java 言語の世界」の端にある扉であることがよくわかります。このドアは、私たちが「バイトコードの世界」に入るのに役立ちます。

3. ASMの実際の利用シーン

3.1. 春の ASM

最初のアプリケーション シナリオは、Spring フレームワークの AOP です。多くの Java プロジェクトでは Spring フレームワークが使用されており、Spring フレームワークの AOP (アスペクト指向プログラミング) は ASM に依存しています。具体的には、Spring の AOP は、JDK の動的プロキシまたは CGLIB を通じて実装できます。このうち CGLib (Code Generation Library) は ASM をベースに構築されているため、Spring AOP は間接的に ASM を使用します。(Spring Framework リファレンス ドキュメントの 8.6 プロキシ メカニズムから参照)。

3.2. JDK の ASM

2 番目のアプリケーション シナリオは、JDK でのラムダ式です。Java 8 で導入された非常に重要な機能は、ラムダ式のサポートです。ラムダ式を使用するとメソッドをパラメータとして渡すことができるため、コードをより簡潔かつコンパクトにすることができます。ただし、実際には、この段階 (Java 8 バージョン) では、ラムダ式の呼び出しが ASM を通じて実装されていることには気づいていないかもしれません。

rt.jar ファイル内の jdk.internal.org.objectweb.asm パッケージには、JDK の組み込み ASM コードが含まれています。JDK 8 バージョンでは、ASM 5.0 バージョンを使用します。

Lambda 式のコーディング実装を追跡すると、InnerClassLambdaMetafactory.spinInnerClass() メソッドが見つかります。このメソッドでは、次のことを確認します。 JDK は jdk.internal.org.objectweb.asm.ClassWriter を使用して、ラムダ式のコードをラップするクラスを生成します。

LambdaMetafactory.metafactory() 最初のステップは、このメソッドを見つけることです InnerClassLambdaMetafactory.buildCallSite() 2 番目のステップは、このメソッドを見つけることです。

InnerClassLambdaMetafactory.spinInnerClass() 3 番目のステップは、このメソッドを見つけることです。

4. ASM の 2 つのコンポーネント

構成構造の観点から見ると、ASM は 2 つの部分に分かれており、1 つはコア API、もう 1 つはツリー API です。

このうち、Core API には asm.jar、asm-util.jar、asm-commons.jar が含まれ、Tree API には、asm-tree.jar および asm-analysis.jar が含まれます。

asm.jar 内核心クラス:ClassReader、ClassVisitor、ClassWriter、FieldVisitor、FieldWriter、MethodVisitor、MethodWriter、Label、Opcodes、Type

ClassReader クラスは、.class ファイルの内容を読み取り、それをさまざまな部分に分割します。ClassVisitor クラスは、.class ファイルの特定の部分の情報を変更する役割を果たします。ClassWriter クラスは、さまざまな部分を完全な .class ファイルに再構築する役割を果たします。

asm-util.jar のコアクラス

Check で始まるクラスは主に、生成された .class ファイルの内容が正しいかどうかをチェック (Check) する役割を果たします。Trace で始まるクラスは主に、.class ファイルの内容をテキスト出力に出力します。出力されたテキスト情報に基づいて、.class ファイルの内部情報を探索または追跡できます (トレース)。

5、クラスファイル

.class ファイルには ByteCode データが保存されていることは誰もが知っています。ただし、これらの ByteCode データは乱雑ではなく、特定のデータ構造に従っています。

この .class ファイルが従うデータ構造は、Java 仮想マシン仕様で定義されているクラス ファイル形式です。

6. 共通バイトコードライブラリ

Apache Commons BCEL: BCEL は Byte Code Engineering Library の略称です。

Javassist:Javassist表示Javaプログラミングアシスタント

ObjectWeb ASM: このコースの主題。

Byte Buddy: ASM に基づいて実装されたクラス ライブラリ。

2. 何もないところから何かを生み出す

1. 新しいインターフェースを生成する

予想されるゴール:

通常のインターフェース構造を定義する .class ファイルを生成します。

public interface ASMInterface {
    byte byteType = 1;
    short shortType = 1;
    int intType = 1;
    char charType = 's';
    float floatType = 1.1F;
    double doubleType = 1.2;
    long longType = 1L;
    boolean booleanType = false;
    Byte ByteType = 1;
    Short ShortType = Short.valueOf((short)1);
    Integer IntegerType = 1;
    String StringType = "s";
    Float FloatType = 1.1F;
    Double DoubleType = 1.1;
    Long LongType = 1L;
    Boolean BooleanType = true;

    void function();

    default String defaultFunction(Integer integer) {
        System.out.println("param = " + integer);
        return String.valueOf(integer);
    }

    static Integer getInteger(String str) {
        return Integer.valueOf(str);
    }
}

コーディング実装:

public class InterfaceGenerateCore {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/ASMGenerateInterface.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成byte[]内容
        byte[] bytes = dump();

        // (2) 保存byte[]到文件
        FileUtils.writeBytes(filepath, bytes);
    }

    public static byte[] dump() throws Exception {
        // (1) 创建ClassWriter对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // (2) 调用visitXxx()方法,调用顺序和说明如下
        /*
         *  visit
         *  [visitSource][visitModule][visitNestHost][visitPermittedSubclass][visitOuterClass]
         *  (visitAnnotation |
         *   visitTypeAnnotation |
         *   visitAttribute)*
         *  (visitNestMember |
         *   visitInnerClass |
         *   visitRecordComponent |
         *   visitField |
         *   visitMethod)*
         *  visitEnd
         *  []: 表示最多调用一次,可以不调用,但最多调用一次。
         *  ()和|: 表示在多个方法之间,可以选择任意一个,并且多个方法之间不分前后顺序。
         *  *: 表示方法可以调用0次或多次。
         * */

        //定义接口
        /*
         *visit(version, access, name, signature, superName, interfaces)
         *version: 表示当前类的版本信息。在下述示例代码中,其取值为Opcodes.V1_8,表示使用Java 8版本。
         *access: 表示当前类的访问标识(access flag)信息。在下面的示例中,access的取值是ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,也可以写成ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE。如果想进一步了解这些标识的含义,可以参考Java Virtual Machine Specification的Chapter 4. The class File Format部分。
         *name: 表示当前类的名字,它采用的格式是Internal Name的形式。在.java文件中,我们使用Java语言来编写代码,使用类名的形式是Fully Qualified Class Name,例如java.lang.String;将.java文件编译之后,就会生成.class文件;在.class文件中,类名的形式会发生变化,称之为Internal Name,例如java/lang/String。因此,将Fully Qualified Class Name转换成Internal Name的方式就是,将.字符转换成/字符。
         *signature: 表示当前类的泛型信息。因为在这个接口当中不包含任何的泛型信息,因此它的值为null。
         *superName: 表示当前类的父类信息,它采用的格式是Internal Name的形式。
         *interfaces: 表示当前类实现了哪些接口信息。
         **/
        cw.visit(
                V1_8,                                        // version
                ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,   // access
                "sample/ASMGenerateInterface",               // name
                null,                                        // signature
                "java/lang/Object",                          // superName
                null                                         // interfaces
        );
        //定义字段-基本类型
        /*
         * visitField(access, name, descriptor, signature, value)
         *access参数:表示当前字段或方法带有的访问标识(access flag)信息,例如ACC_PUBLIC、ACC_STATIC和ACC_FINAL等。
         *name参数:表示当前字段或方法的名字。
         *descriptor参数:表示当前字段或方法的描述符。这些描述符,与我们平时使用的Java类型是有区别的。byte-B、short-S、int-I、char-C、具体可以参考如下示例代码
         *signature参数:表示当前字段或方法是否带有泛型信息。换句话说,如果不带有泛型信息,提供一个null就可以了;如果带有泛型信息,就需要给它提供某一个具体的值。
         *value参数:是visitField()方法的第5个参数。这个参数的取值,与当前字段是否为常量有关系。如果当前字段是一个常量,就需要给value参数提供某一个具体的值;如果当前字段不是常量,那么使用null就可以了。
         * */
        {
            FieldVisitor fv1 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "byteType", "B", null, new Integer(1));
            fv1.visitEnd();
        }
        {
            FieldVisitor fv2 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "shortType", "S", null, new Integer(1));
            fv2.visitEnd();
        }
        {
            FieldVisitor fv3 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "intType", "I", null, new Integer(1));
            fv3.visitEnd();
        }
        {
            FieldVisitor fv4 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "charType", "C", null, 's');
            fv4.visitEnd();
        }
        {
            FieldVisitor fv5 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "floatType", "F", null, new Float("1.1"));
            fv5.visitEnd();
        }
        {
            FieldVisitor fv6 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "doubleType", "D", null, new Double("1.2"));
            fv6.visitEnd();
        }
        {
            FieldVisitor fv7 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "longType", "J", null, new Long(1L));
            fv7.visitEnd();
        }
        {
            FieldVisitor fv8 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "booleanType", "Z", null, new Integer(0));
            fv8.visitEnd();
        }
        //定义变量-包装类型
        {
            FieldVisitor fv11 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "ByteType", "Ljava/lang/Byte;", null, null);
            fv11.visitEnd();
        }
        {
            FieldVisitor fv12 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "ShortType", "Ljava/lang/Short;", null,null);
            fv12.visitEnd();
        }
        {
            FieldVisitor fv13 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "IntegerType", "Ljava/lang/Integer;", null,null);
            fv13.visitEnd();
        }
        {
            FieldVisitor fv14 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "StringType", "Ljava/lang/String;", null, "s");
            fv14.visitEnd();
        }
        {
            FieldVisitor fv15 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "FloatType", "Ljava/lang/Float;", null,null);
            fv15.visitEnd();
        }
        {
            FieldVisitor fv16 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "DoubleType", "Ljava/lang/Double;", null,null);
            fv16.visitEnd();
        }
        {
            FieldVisitor fv17 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "LongType", "Ljava/lang/Long;", null,  null);
            fv17.visitEnd();
        }
        {
            FieldVisitor fv18 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "BooleanType", "Ljava/lang/Boolean;", null, null);
            fv18.visitEnd();
        }
        //定义方法-抽象方法
        /*
         * visitMethod(access, name, descriptor, signature, exceptions)
         *access参数:表示当前字段或方法带有的访问标识(access flag)信息,例如ACC_PUBLIC、ACC_STATIC和ACC_FINAL等。
         *name参数:表示当前字段或方法的名字。
         *descriptor参数:表示当前字段或方法的描述符。这些描述符,与我们平时使用的Java类型是有区别的。()内为入参,后面为反参
         *signature参数:表示当前字段或方法是否带有泛型信息。换句话说,如果不带有泛型信息,提供一个null就可以了;如果带有泛型信息,就需要给它提供某一个具体的值。
         *exceptions参数:是visitMethod()方法的第5个参数。这个参数的取值,与当前方法声明中是否具有throws XxxException相关。
         * */
        {
            MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "function", "()V", null, null);
            mv1.visitEnd();
        }
        //定义方法-默认方法
        {
            MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "defaultFunction", "(Ljava/lang/Integer;)Ljava/lang/String;", null, null);
            mv2.visitCode();
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitTypeInsn(NEW, "java/lang/StringBuilder");
            mv2.visitInsn(DUP);
            mv2.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
            mv2.visitLdcInsn("param = ");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            mv2.visitVarInsn(ALOAD, 1);
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false);
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitVarInsn(ALOAD, 1);
            mv2.visitMethodInsn(INVOKESTATIC, "java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", false);
            mv2.visitInsn(ARETURN);
            mv2.visitMaxs(3, 2);
            mv2.visitEnd();
        }
        //定义方法-静态方法
        {
            MethodVisitor mv3 = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "getInteger", "(Ljava/lang/String;)Ljava/lang/Integer;", null, null);
            mv3.visitCode();
            mv3.visitVarInsn(ALOAD, 0);
            mv3.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(Ljava/lang/String;)Ljava/lang/Integer;", false);
            mv3.visitInsn(ARETURN);
            mv3.visitMaxs(1, 1);
            mv3.visitEnd();
        }

        {
            MethodVisitor mv4 = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
            mv4.visitCode();
            mv4.visitInsn(ICONST_1);
            mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
            mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "ByteType", "Ljava/lang/Byte;");
            mv4.visitInsn(ICONST_1);
            mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
            mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "ShortType", "Ljava/lang/Short;");
            mv4.visitInsn(ICONST_1);
            mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
            mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "IntegerType", "Ljava/lang/Integer;");
            mv4.visitLdcInsn(new Float("1.1"));
            mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
            mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "FloatType", "Ljava/lang/Float;");
            mv4.visitLdcInsn(new Double("1.1"));
            mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
            mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "DoubleType", "Ljava/lang/Double;");
            mv4.visitInsn(LCONST_1);
            mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
            mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "LongType", "Ljava/lang/Long;");
            mv4.visitInsn(ICONST_1);
            mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
            mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "BooleanType", "Ljava/lang/Boolean;");
            mv4.visitInsn(RETURN);
            mv4.visitMaxs(2, 0);
            mv4.visitEnd();
        }
        cw.visitEnd(); // 注意,最后要调用visitEnd()方法
        // (3) 调用toByteArray()方法
        return cw.toByteArray();
    }
}

検証結果:

生成されたインターフェースは正しいですか?

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Class<?> clazz = classLoader.loadClass("sample.ASMGenerateInterface");
        Field[] declaredFields = clazz.getDeclaredFields();
        if (declaredFields.length > 0) {
            System.out.println("fields:");
            for (Field f : declaredFields) {
                Object value = f.get(null);
                System.out.println("    " + f.getName() + ": " + value);
            }
        }
        Method[] declaredMethods = clazz.getDeclaredMethods();
        if (declaredMethods.length > 0) {
            System.out.println("methods:");
            for (Method m : declaredMethods) {
                System.out.println("    " + m.getName());
            }
        }
    }
}

レンダリングは次のとおりです。



2. 新しいクラスを生成する

予想されるゴール:

通常のクラス構造を定義する .class ファイルを生成します。

public class ASMClass {
    //定义变量-基本类型
    byte byteType = 1;
    short shortType = 1;
    int intType = 1;
    char charType = 's';
    float floatType = 1.1f;
    double doubleType = 1.2;
    long longType = 1;
    boolean booleanType = false;
    //定义变量-包装类型
    Byte ByteType = 1;
    Short ShortType = 1;
    Integer IntegerType = 1;
    String StringType = "string";
    Float FloatType = 1.1f;
    Double DoubleType = 1.1;
    Long LongType = 1l;
    @Deprecated
    Boolean BooleanType = true;
    /*
     * 静态方法
     * */
    public static Integer getInteger(String str) {
        return Integer.valueOf(str);
    }
    /*
     * 实例方法
     * */
    public String instanceMethod(Integer integer) {
        return String.valueOf(integer);
    }
}

コーディング実装:

public class ClassGenerateCore {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/ASMGenerateClass.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成byte[]内容
        byte[] bytes = dump();

        // (2) 保存byte[]到文件
        FileUtils.writeBytes(filepath, bytes);
    }

    public static byte[] dump() throws Exception {
        // (1) 创建ClassWriter对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // (2) 调用visitXxx()方法,调用顺序和说明如下
        /*
         *  visit
         *  [visitSource][visitModule][visitNestHost][visitPermittedSubclass][visitOuterClass]
         *  (visitAnnotation |
         *   visitTypeAnnotation |
         *   visitAttribute)*
         *  (visitNestMember |
         *   visitInnerClass |
         *   visitRecordComponent |
         *   visitField |
         *   visitMethod)*
         *  visitEnd
         *  []: 表示最多调用一次,可以不调用,但最多调用一次。
         *  ()和|: 表示在多个方法之间,可以选择任意一个,并且多个方法之间不分前后顺序。
         *  *: 表示方法可以调用0次或多次。
         * */

        //定义接口
        /*
         *visit(version, access, name, signature, superName, interfaces)
         *version: 表示当前类的版本信息。在下述示例代码中,其取值为Opcodes.V1_8,表示使用Java 8版本。
         *access: 表示当前类的访问标识(access flag)信息。在下面的示例中,access的取值是ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,也可以写成ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE。如果想进一步了解这些标识的含义,可以参考Java Virtual Machine Specification的Chapter 4. The class File Format部分。
         *name: 表示当前类的名字,它采用的格式是Internal Name的形式。在.java文件中,我们使用Java语言来编写代码,使用类名的形式是Fully Qualified Class Name,例如java.lang.String;将.java文件编译之后,就会生成.class文件;在.class文件中,类名的形式会发生变化,称之为Internal Name,例如java/lang/String。因此,将Fully Qualified Class Name转换成Internal Name的方式就是,将.字符转换成/字符。
         *signature: 表示当前类的泛型信息。因为在这个接口当中不包含任何的泛型信息,因此它的值为null。
         *superName: 表示当前类的父类信息,它采用的格式是Internal Name的形式。
         *interfaces: 表示当前类实现了哪些接口信息。
         **/
        cw.visit(
                V1_8,                                        // version
                ACC_PUBLIC + ACC_SUPER,   // access
                "sample/ASMGenerateClass",               // name
                null,                                        // signature
                "java/lang/Object",                          // superName
                null                                         // interfaces
        );
        //定义字段-基本类型
        /*
         * visitField(access, name, descriptor, signature, value)
         *access参数:表示当前字段或方法带有的访问标识(access flag)信息,例如ACC_PUBLIC、ACC_STATIC和ACC_FINAL等。
         *name参数:表示当前字段或方法的名字。
         *descriptor参数:表示当前字段或方法的描述符。这些描述符,与我们平时使用的Java类型是有区别的。byte-B、short-S、int-I、char-C、具体可以参考如下示例代码
         *signature参数:表示当前字段或方法是否带有泛型信息。换句话说,如果不带有泛型信息,提供一个null就可以了;如果带有泛型信息,就需要给它提供某一个具体的值。
         *value参数:是visitField()方法的第5个参数。这个参数的取值,与当前字段是否为常量有关系。如果当前字段是一个常量,就需要给value参数提供某一个具体的值;如果当前字段不是常量,那么使用null就可以了。
         * */
        {
            FieldVisitor fv1 = cw.visitField(0, "byteType", "B", null, new Byte("1"));
            fv1.visitEnd();
        }
        {
            FieldVisitor fv2 = cw.visitField(0, "shortType", "S", null, new Short("1"));
            fv2.visitEnd();
        }
        {
            FieldVisitor fv3 = cw.visitField(0, "intType", "I", null, new Integer(1));
            fv3.visitEnd();
        }
        {
            FieldVisitor fv4 = cw.visitField(0, "charType", "C", null, "s");
            fv4.visitEnd();
        }
        {
            FieldVisitor fv5 = cw.visitField(0, "floatType", "F", null, new Float("1.1"));
            fv5.visitEnd();
        }
        {
            FieldVisitor fv6 = cw.visitField(0, "doubleType", "D", null, new Double("1.2"));
            fv6.visitEnd();
        }
        {
            FieldVisitor fv7 = cw.visitField(0, "longType", "J", null, new Long(1));
            fv7.visitEnd();
        }
        {
            FieldVisitor fv8 = cw.visitField(0, "booleanType", "Z", null, false);
            fv8.visitEnd();
        }
        //定义变量-包装类型
        {
            FieldVisitor fv11 = cw.visitField(0, "ByteType", "Ljava/lang/Byte;", null, 1);
            fv11.visitEnd();
        }
        {
            FieldVisitor fv12 = cw.visitField(0, "ShortType", "Ljava/lang/Short;", null, 1);
            fv12.visitEnd();
        }
        {
            FieldVisitor fv13 = cw.visitField(0, "IntegerType", "Ljava/lang/Integer;", null, 1);
            fv13.visitEnd();
        }
        {
            FieldVisitor fv14 = cw.visitField(0, "StringType", "Ljava/lang/String;", null, "s");
            fv14.visitEnd();
        }
        {
            FieldVisitor fv15 = cw.visitField(0, "FloatType", "Ljava/lang/Float;", null, 1.1f);
            fv15.visitEnd();
        }
        {
            FieldVisitor fv16 = cw.visitField(ACC_PUBLIC, "DoubleType", "Ljava/lang/Double;", null, 1.1);
            fv16.visitEnd();
        }
        {
            FieldVisitor fv17 = cw.visitField(0, "LongType", "Ljava/lang/Long;", null, 1l);
            fv17.visitEnd();
        }
        {
            FieldVisitor fv18 = cw.visitField(ACC_DEPRECATED, "BooleanType", "Ljava/lang/Boolean;", null, true);
            {
                AnnotationVisitor annotationVisitor0 = fv18.visitAnnotation("Ljava/lang/Deprecated;", true);
                annotationVisitor0.visitEnd();
            }
            fv18.visitEnd();
        }
        /*
         * visitMethod(access, name, descriptor, signature, exceptions)
         *access参数:表示当前字段或方法带有的访问标识(access flag)信息,例如ACC_PUBLIC、ACC_STATIC和ACC_FINAL等。
         *name参数:表示当前字段或方法的名字。
         *descriptor参数:表示当前字段或方法的描述符。这些描述符,与我们平时使用的Java类型是有区别的。()内为入参,后面为反参
         *signature参数:表示当前字段或方法是否带有泛型信息。换句话说,如果不带有泛型信息,提供一个null就可以了;如果带有泛型信息,就需要给它提供某一个具体的值。
         *exceptions参数:是visitMethod()方法的第5个参数。这个参数的取值,与当前方法声明中是否具有throws XxxException相关。
         * */
        //定义方法-静态代码块
        {
            MethodVisitor mv2 = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
            mv2.visitCode();
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("class initialization method");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitInsn(RETURN);
            mv2.visitMaxs(2, 0);
            mv2.visitEnd();
        }
        //定义方法-无参构造器
        {
            MethodVisitor methodVisitor = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            methodVisitor.visitCode();
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(ICONST_1);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "byteType", "B");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(ICONST_1);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "shortType", "S");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(ICONST_1);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "intType", "I");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitIntInsn(BIPUSH, 115);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "charType", "C");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitLdcInsn(new Float("1.1"));
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "floatType", "F");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitLdcInsn(new Double("1.2"));
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "doubleType", "D");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(LCONST_1);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "longType", "J");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(ICONST_0);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "booleanType", "Z");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(ICONST_1);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "ByteType", "Ljava/lang/Byte;");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(ICONST_1);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "ShortType", "Ljava/lang/Short;");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(ICONST_1);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "IntegerType", "Ljava/lang/Integer;");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitLdcInsn("string");
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "StringType", "Ljava/lang/String;");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitLdcInsn(new Float("1.1"));
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "FloatType", "Ljava/lang/Float;");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitLdcInsn(new Double("1.1"));
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "DoubleType", "Ljava/lang/Double;");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(LCONST_1);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "LongType", "Ljava/lang/Long;");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(ICONST_1);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "BooleanType", "Ljava/lang/Boolean;");
            methodVisitor.visitInsn(RETURN);
            methodVisitor.visitMaxs(3, 1);
            methodVisitor.visitEnd();

        }
        //定义方法-静态方法
        {
            MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "getInteger", "(Ljava/lang/String;)Ljava/lang/Integer;", null, null);
            mv1.visitCode();
            mv1.visitVarInsn(ALOAD, 0);
            mv1.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(Ljava/lang/String;)Ljava/lang/Integer;", false);
            mv1.visitInsn(ARETURN);
            mv1.visitMaxs(1, 1);
            mv1.visitEnd();
        }
        //定义方法-实例方法
        {
            MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "instanceMethod", "(Ljava/lang/Integer;)Ljava/lang/String;", null, null);
            mv2.visitCode();
            mv2.visitVarInsn(ALOAD, 1);
            mv2.visitMethodInsn(INVOKESTATIC, "java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", false);
            mv2.visitInsn(ARETURN);
            mv2.visitMaxs(1, 2);
            mv2.visitEnd();
        }

        cw.visitEnd(); // 注意,最后要调用visitEnd()方法
        // (3) 调用toByteArray()方法
        return cw.toByteArray();
    }
}

検証結果:

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("sample.ASMGenerateClass");
        Method method = clazz.getDeclaredMethod("instanceMethod",Integer.class);
        Object instance = clazz.newInstance();
        Object invoke = method.invoke(instance, new Integer(12));
        Class<?> aClass = invoke.getClass();
        System.out.println("aClass = " + aClass);
    }
}
或者
public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Class<?> clazz = classLoader.loadClass("sample.ASMGenerateClass");
        Field[] declaredFields = clazz.getDeclaredFields();
        if (declaredFields.length > 0) {
            for (Field f : declaredFields) {
                Object value = f.get(null);
                System.out.println("    " + f.getName() + ": " + value);
            }
        }
        Method[] declaredMethods = clazz.getDeclaredMethods();
        if (declaredMethods.length > 0) {
            for (Method m : declaredMethods) {
                System.out.println("    " + m.getName());
            }
        }
    }
}

レンダリングは次のとおりです。

ClassVisitor の visitXxx() の呼び出しシーケンス

visit
[visitSource][visitModule][visitNestHost][visitPermittedSubclass][visitOuterClass]
(
 visitAnnotation |
 visitTypeAnnotation |
 visitAttribute
)*
(
 visitNestMember |
 visitInnerClass |
 visitRecordComponent |
 visitField |
 visitMethod
)* 
visitEnd
其中,涉及到一些符号,它们的含义如下:
[]: 表示最多调用一次,可以不调用,但最多调用一次。
()|: 表示在多个方法之间,可以选择任意一个,并且多个方法之间不分前后顺序。
*: 表示方法可以调用0次或多次。

FieldVisitor の visitXxx() の呼び出しシーケンス

(
 visitAnnotation |
 visitTypeAnnotation |
 visitAttribute
)*
visitEnd

MethodVisitor の visitXxx() の呼び出しシーケンス

(visitParameter)*
[visitAnnotationDefault]
(visitAnnotation | visitAnnotableParameterCount | visitParameterAnnotation | visitTypeAnnotation | visitAttribute)*
[
    visitCode
    (
        visitFrame |
        visitXxxInsn |
        visitLabel |
        visitInsnAnnotation |
        visitTryCatchBlock |
        visitTryCatchAnnotation |
        visitLocalVariable |
        visitLocalVariableAnnotation |
        visitLineNumber
    )*
    visitMaxs
]
visitEnd
第一组,在visitCode()方法之前的方法。这一组的方法,主要负责parameter、annotation和attributes等内容
第二组,在visitCode()方法和visitMaxs()方法之间的方法。这一组的方法,主要负责当前方法的“方法体”内的opcode内容。其中,visitCode()方法,标志着方法体的开始,而visitMaxs()方法,标志着方法体的结束。
第三组,是visitEnd()方法。这个visitEnd()方法,是最后一个进行调用的方法。

異なる MethodVisitor オブジェクトの visitXxx() メソッドは相互に独立しているため、メソッドの呼び出し順序に従っている限り、正しい結果を取得できます。

3. ジャコウネコが王子様と交換

1. クラスのバージョンを変更する

ClassVisitor サブクラスの実装

public class ClassChangeVersionVisitor extends ClassVisitor {
    public ClassChangeVersionVisitor(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(Opcodes.V1_7, access, name, signature, superName, interfaces);
    }
}

ClassVisitor のサブクラスである ClassChangeVersionVisitor を使用してクラスのバージョンを変更します

public class ASMModifyClass {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);
        byte[] bytes = FileUtils.readBytes(filepath);

        //(1)构建ClassReader
        ClassReader cr = new ClassReader(bytes);

        //(2)构建ClassWriter
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        //(3)串连ClassVisitor
        int api = Opcodes.ASM9;
        ClassVisitor cv = new ClassChangeVersionVisitor(api, cw);

        //(4)结合ClassReader和ClassVisitor
        int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
        cr.accept(cv, parsingOptions);

        //(5)生成byte[]
        byte[] bytes2 = cw.toByteArray();

        FileUtils.writeBytes(filepath, bytes2);
    }
}

検証と効果

javap -p -v HelloWorld コマンドを実行すると、バージョン番号情報が 52 から 51 に調整されたことがわかります。



2. 各メソッドに計算呼び出し時間を追加する

対象クラスのメソッドの変更と交換---各メソッドの時間計算を追加


public class HelloWorld {
    public int add(int a, int b) throws InterruptedException {
        int c = a + b;
        Random rand = new Random(System.currentTimeMillis());
        int num = rand.nextInt(300);
        Thread.sleep(100 + num);
        return c;
    }

    public int sub(int a, int b) throws InterruptedException {
        int c = a - b;
        Random rand = new Random(System.currentTimeMillis());
        int num = rand.nextInt(400);
        Thread.sleep(100 + num);
        return c;
    }
}

ASMコーディングの実装


public class MethodTimerVisitor2 extends ClassVisitor {
    private String owner;
    private boolean isInterface;

    public MethodTimerVisitor2(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces);
        owner = name;
        isInterface = (access & ACC_INTERFACE) != 0;
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        if (!isInterface && mv != null && !"<init>".equals(name) && !"<clinit>".equals(name)) {
            boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;
            boolean isNativeMethod = (access & ACC_NATIVE) != 0;
            if (!isAbstractMethod && !isNativeMethod) {
                // 每遇到一个合适的方法,就添加一个相应的字段
                FieldVisitor fv = super.visitField(ACC_PUBLIC | ACC_STATIC, getFieldName(name), "J", null, null);
                if (fv != null) {
                    fv.visitEnd();
                }

                mv = new MethodTimerAdapter2(api, mv, owner, name);
            }

        }
        return mv;
    }


    private String getFieldName(String methodName) {
        return "timer_" + methodName;
    }

    private class MethodTimerAdapter2 extends MethodVisitor {
        private final String owner;
        private final String methodName;

        public MethodTimerAdapter2(int api, MethodVisitor mv, String owner, String methodName) {
            super(api, mv);
            this.owner = owner;
            this.methodName = methodName;
        }

        @Override
        public void visitCode() {
            // 首先,处理自己的代码逻辑
            super.visitFieldInsn(GETSTATIC, owner, getFieldName(methodName), "J"); // 注意,字段名字要对应
            super.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
            super.visitInsn(LSUB);
            super.visitFieldInsn(PUTSTATIC, owner, getFieldName(methodName), "J"); // 注意,字段名字要对应

            // 其次,调用父类的方法实现
            super.visitCode();
        }

        @Override
        public void visitInsn(int opcode) {
            // 首先,处理自己的代码逻辑
            if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
                super.visitFieldInsn(GETSTATIC, owner, getFieldName(methodName), "J"); // 注意,字段名字要对应
                super.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
                super.visitInsn(LADD);
                super.visitFieldInsn(PUTSTATIC, owner, getFieldName(methodName), "J"); // 注意,字段名字要对应
            }

            // 其次,调用父类的方法实现
            super.visitInsn(opcode);
        }
    }
}

メソッドを変換する


public class HelloWorldTransformCore {
    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
  
    public static void main(String[] args) {
        String relative_path = "sample/HelloWorld.class";
        String dir = HelloWorldTransformCore.class.getResource("/").getPath();
        String filepath = dir + relative_path;
        File file = new File(filepath);
        try {
            InputStream in = new FileInputStream(file);
            in = new BufferedInputStream(in);
            ByteArrayOutputStream bao = new ByteArrayOutputStream();
            copyLarge(in, bao, new byte[DEFAULT_BUFFER_SIZE]);
            byte[] bytes1 = bao.toByteArray();

            //(1)构建ClassReader
            ClassReader cr = new ClassReader(bytes1);

            //(2)构建ClassWriter
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

            //(3)串连ClassVisitor
            int api = Opcodes.ASM9;
            ClassVisitor cv = new MethodTimerVisitor2(api, cw);

            //(4)结合ClassReader和ClassVisitor
            int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
            cr.accept(cv, parsingOptions);

            //(5)生成byte[]
            byte[] bytes2 = cw.toByteArray();
            OutputStream out = new FileOutputStream(filepath);
            BufferedOutputStream buff = new BufferedOutputStream(out);
            buff.write(bytes2);
            buff.flush();
            buff.close();
            System.out.println("file://" + filepath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static long copyLarge(final InputStream input, final OutputStream output, final byte[] buffer)
            throws IOException {
        long count = 0;
        int n;
        while (-1 != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
            count += n;
        }
        return count;
    }
}

検証結果

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        // 第一部分,先让“子弹飞一会儿”,让程序运行一段时间
        HelloWorld instance = new HelloWorld();
        Random rand = new Random(System.currentTimeMillis());
        for (int i = 0; i < 10; i++) {
            boolean flag = rand.nextBoolean();
            int a = rand.nextInt(50);
            int b = rand.nextInt(50);
            if (flag) {
                int c = instance.add(a, b);
                String line = String.format("%d + %d = %d", a, b, c);
                System.out.println(line);
            }
            else {
                int c = instance.sub(a, b);
                String line = String.format("%d - %d = %d", a, b, c);
                System.out.println(line);
            }
        }

        // 第二部分,来查看方法运行的时间
        Class<?> clazz = HelloWorld.class;
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field f : declaredFields) {
            String fieldName = f.getName();
            f.setAccessible(true);
            if (fieldName.startsWith("timer")) {
                Object FieldValue = f.get(null);
                System.out.println(fieldName + " = " + FieldValue);
            }
        }
    }
}

3. メソッドのパラメータと戻り値を出力します。

対象クラスのメソッドを変更---各メソッドに印刷用の入力パラメータと出力パラメータを追加します。

public class HelloWorld {
    public int test(String name, int age, long idCard, Object obj) {
        int hashCode = 0;
        hashCode += name.hashCode();
        hashCode += age;
        hashCode += (int) (idCard % Integer.MAX_VALUE);
        hashCode += obj.hashCode();
        return hashCode;
    }
}

達成したい想定目標:「メソッドが受け取ったパラメータ値」と「メソッドの戻り値」を出力する。

public class HelloWorld {
    public int test(String name, int age, long idCard, Object obj) {

        int hashCode = 0;
        hashCode += name.hashCode();
        hashCode += age;
        hashCode += (int) (idCard % Integer.MAX_VALUE);
        hashCode += obj.hashCode();
        System.out.println(hashCode);
        return hashCode;
    }
}

この関数の実装の考え方は、「メソッドが入ったとき」に「メソッドが受け取ったパラメータ値」を出力し、「メソッドが終了したとき」に「メソッドの戻り値」を出力します。

首先,我们添加一个ParameterUtils类,在这个类定义了许多print方法,这些print方法可以打印不同类型的数据。

public class ParameterUtils {
    private static final DateFormat fm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void printValueOnStack(boolean value) {
        System.out.println("    " + value);
    }

    public static void printValueOnStack(byte value) {
        System.out.println("    " + value);
    }

    public static void printValueOnStack(char value) {
        System.out.println("    " + value);
    }

    public static void printValueOnStack(short value) {
        System.out.println("    " + value);
    }

    public static void printValueOnStack(int value) {
        System.out.println("    " + value);
    }

    public static void printValueOnStack(float value) {
        System.out.println("    " + value);
    }

    public static void printValueOnStack(long value) {
        System.out.println("    " + value);
    }

    public static void printValueOnStack(double value) {
        System.out.println("    " + value);
    }

    public static void printValueOnStack(Object value) {
        if (value == null) {
            System.out.println("    " + value);
        }
        else if (value instanceof String) {
            System.out.println("    " + value);
        }
        else if (value instanceof Date) {
            System.out.println("    " + fm.format(value));
        }
        else if (value instanceof char[]) {
            System.out.println("    " + Arrays.toString((char[])value));
        }
        else {
            System.out.println("    " + value.getClass() + ": " + value.toString());
        }
    }

    public static void printText(String str) {
        System.out.println(str);
    }
}

次のクラスでは、このクラスを使用して情報を出力します。MethodParameterVisitor2ParameterUtils

ASMコーディングの実装


public class MethodParameterVisitor2 extends ClassVisitor {
    public MethodParameterVisitor2(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        if (mv != null && !name.equals("<init>")) {
            boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;
            boolean isNativeMethod = (access & ACC_NATIVE) != 0;
            if (!isAbstractMethod && !isNativeMethod) {
                mv = new MethodParameterAdapter2(api, mv, access, name, descriptor);
            }
        }
        return mv;
    }

    private static class MethodParameterAdapter2 extends MethodVisitor {
        private final int methodAccess;
        private final String methodName;
        private final String methodDesc;

        public MethodParameterAdapter2(int api, MethodVisitor mv, int methodAccess, String methodName, String methodDesc) {
            super(api, mv);
            this.methodAccess = methodAccess;
            this.methodName = methodName;
            this.methodDesc = methodDesc;
        }

        @Override
        public void visitCode() {
            // 首先,处理自己的代码逻辑
            boolean isStatic = ((methodAccess & ACC_STATIC) != 0);
            int slotIndex = isStatic ? 0 : 1;

            printMessage("Method Enter: " + methodName + methodDesc);

            Type methodType = Type.getMethodType(methodDesc);
            Type[] argumentTypes = methodType.getArgumentTypes();
            for (Type t : argumentTypes) {
                int sort = t.getSort();
                int size = t.getSize();
                String descriptor = t.getDescriptor();
                int opcode = t.getOpcode(ILOAD);
                super.visitVarInsn(opcode, slotIndex);
                if (sort >= Type.BOOLEAN && sort <= Type.DOUBLE) {
                    String methodDesc = String.format("(%s)V", descriptor);
                    printValueOnStack(methodDesc);
                }
                else {
                    printValueOnStack("(Ljava/lang/Object;)V");
                }

                slotIndex += size;
            }

            // 其次,调用父类的方法实现
            super.visitCode();
        }

        @Override
        public void visitInsn(int opcode) {
            // 首先,处理自己的代码逻辑
            if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
                printMessage("Method Exit: " + methodName + methodDesc);
                if (opcode >= IRETURN && opcode <= DRETURN) {
                    Type methodType = Type.getMethodType(methodDesc);
                    Type returnType = methodType.getReturnType();
                    int size = returnType.getSize();
                    String descriptor = returnType.getDescriptor();

                    if (size == 1) {
                        super.visitInsn(DUP);
                    }
                    else {
                        super.visitInsn(DUP2);
                    }
                    String methodDesc = String.format("(%s)V", descriptor);
                    printValueOnStack(methodDesc);
                }
                else if (opcode == ARETURN) {
                    super.visitInsn(DUP);
                    printValueOnStack("(Ljava/lang/Object;)V");
                }
                else if (opcode == RETURN) {
                    printMessage("    return void");
                }
                else {
                    printMessage("    abnormal return");
                }
            }

            // 其次,调用父类的方法实现
            super.visitInsn(opcode);
        }

        private void printMessage(String str) {
            super.visitLdcInsn(str);
            super.visitMethodInsn(INVOKESTATIC, "sample/ParameterUtils", "printText", "(Ljava/lang/String;)V", false);
        }

        private void printValueOnStack(String descriptor) {
            super.visitMethodInsn(INVOKESTATIC, "sample/ParameterUtils", "printValueOnStack", descriptor, false);
        }
    }
}

変換を行う


public class HelloWorldTransformCore {
    public static void main(String[] args) {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);
        byte[] bytes1 = FileUtils.readBytes(filepath);

        //(1)构建ClassReader
        ClassReader cr = new ClassReader(bytes1);

        //(2)构建ClassWriter
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        //(3)串连ClassVisitor
        int api = Opcodes.ASM9;
        ClassVisitor cv = new MethodParameterVisitor2(api, cw);

        //(4)结合ClassReader和ClassVisitor
        int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
        cr.accept(cv, parsingOptions);

        //(5)生成byte[]
        byte[] bytes2 = cw.toByteArray();

        FileUtils.writeBytes(filepath, bytes2);
    }
}

検証結果

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        HelloWorld instance = new HelloWorld();
        int hashCode = instance.test("Tomcat", 10, System.currentTimeMillis(), new Object());
        int remainder = hashCode % 2;

        if (remainder == 0) {
            System.out.println("hashCode is even number.");
        }
        else {
            System.out.println("hashCode is odd number.");
        }
    }
}

4. 非連続構造

1. if ステートメント

public class HelloWorld {
    public void test(int value) {
        if (value == 0) {
            System.out.println("value is 0");
        }
        else {
            System.out.println("value is not 0");
        }
    }
}

ASMコーディングの実装


public class HelloWorldGenerateCore {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成byte[]内容
        byte[] bytes = dump();

        // (2) 保存byte[]到文件
        FileUtils.writeBytes(filepath, bytes);
    }

    public static byte[] dump() throws Exception {
        // (1) 创建ClassWriter对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // (2) 调用visitXxx()方法
        cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld",
                null, "java/lang/Object", null);

        {
            MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv1.visitCode();
            mv1.visitVarInsn(ALOAD, 0);
            mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            mv1.visitInsn(RETURN);
            mv1.visitMaxs(0, 0);
            mv1.visitEnd();
        }

        {
            MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "(I)V", null, null);
            Label elseLabel = new Label();
            Label returnLabel = new Label();

            // 第1段
            mv2.visitCode();
            mv2.visitVarInsn(ILOAD, 1);
            mv2.visitJumpInsn(IFNE, elseLabel);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("value is 0");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitJumpInsn(GOTO, returnLabel);

            // 第2段
            mv2.visitLabel(elseLabel);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("value is not 0");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

            // 第3段
            mv2.visitLabel(returnLabel);
            mv2.visitInsn(RETURN);
            mv2.visitMaxs(0, 0);
            mv2.visitEnd();
        }

        cw.visitEnd();

        // (3) 调用toByteArray()方法
        return cw.toByteArray();
    }
}

検証結果


public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("sample.HelloWorld");
        Object obj = clazz.newInstance();

        Method method = clazz.getDeclaredMethod("test", int.class);
        method.invoke(obj, 0);
        method.invoke(obj, 1);
    }
}

2. switch ステートメント

public class HelloWorld {
    public void test(int val) {
        switch (val) {
            case 1:
                System.out.println("val = 1");
                break;
            case 2:
                System.out.println("val = 2");
                break;
            case 3:
                System.out.println("val = 3");
                break;
            case 4:
                System.out.println("val = 4");
                break;
            default:
                System.out.println("val is unknown");
        }
    }
}

ASMコーディングの実装


public class HelloWorldGenerateCore {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成byte[]内容
        byte[] bytes = dump();

        // (2) 保存byte[]到文件
        FileUtils.writeBytes(filepath, bytes);
    }

    public static byte[] dump() throws Exception {
        // (1) 创建ClassWriter对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // (2) 调用visitXxx()方法
        cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld",
                null, "java/lang/Object", null);

        {
            MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv1.visitCode();
            mv1.visitVarInsn(ALOAD, 0);
            mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            mv1.visitInsn(RETURN);
            mv1.visitMaxs(0, 0);
            mv1.visitEnd();
        }

        {
            MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "(I)V", null, null);
            Label caseLabel1 = new Label();
            Label caseLabel2 = new Label();
            Label caseLabel3 = new Label();
            Label caseLabel4 = new Label();
            Label defaultLabel = new Label();
            Label returnLabel = new Label();

            // 第1段
            mv2.visitCode();
            mv2.visitVarInsn(ILOAD, 1);
            mv2.visitTableSwitchInsn(1, 4, defaultLabel, new Label[]{caseLabel1, caseLabel2, caseLabel3, caseLabel4});

            // 第2段
            mv2.visitLabel(caseLabel1);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("val = 1");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitJumpInsn(GOTO, returnLabel);

            // 第3段
            mv2.visitLabel(caseLabel2);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("val = 2");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitJumpInsn(GOTO, returnLabel);

            // 第4段
            mv2.visitLabel(caseLabel3);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("val = 3");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitJumpInsn(GOTO, returnLabel);

            // 第5段
            mv2.visitLabel(caseLabel4);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("val = 4");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitJumpInsn(GOTO, returnLabel);

            // 第6段
            mv2.visitLabel(defaultLabel);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("val is unknown");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

            // 第7段
            mv2.visitLabel(returnLabel);
            mv2.visitInsn(RETURN);
            mv2.visitMaxs(0, 0);
            mv2.visitEnd();
        }

        cw.visitEnd();

        // (3) 调用toByteArray()方法
        return cw.toByteArray();
    }
}

検証結果

import java.lang.reflect.Method;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("sample.HelloWorld");
        Object obj = clazz.newInstance();

        Method method = clazz.getDeclaredMethod("test", int.class);
        for (int i = 1; i < 6; i++) {
            method.invoke(obj, i);
        }
    }
}

3.for文

public class HelloWorld {
    public void test() {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
    }
}

ASMコーディングの実装

import org.objectweb.asm.*;

import static org.objectweb.asm.Opcodes.*;

public class HelloWorldGenerateCore {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成byte[]内容
        byte[] bytes = dump();

        // (2) 保存byte[]到文件
        FileUtils.writeBytes(filepath, bytes);
    }

    public static byte[] dump() throws Exception {
        // (1) 创建ClassWriter对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // (2) 调用visitXxx()方法
        cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld",
                null, "java/lang/Object", null);

        {
            MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv1.visitCode();
            mv1.visitVarInsn(ALOAD, 0);
            mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            mv1.visitInsn(RETURN);
            mv1.visitMaxs(0, 0);
            mv1.visitEnd();
        }

        {
            MethodVisitor methodVisitor = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
            Label conditionLabel = new Label();
            Label returnLabel = new Label();

            // 第1段
            methodVisitor.visitCode();
            methodVisitor.visitInsn(ICONST_0);
            methodVisitor.visitVarInsn(ISTORE, 1);

            // 第2段
            methodVisitor.visitLabel(conditionLabel);
            methodVisitor.visitVarInsn(ILOAD, 1);
            methodVisitor.visitIntInsn(BIPUSH, 10);
            methodVisitor.visitJumpInsn(IF_ICMPGE, returnLabel);
            methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            methodVisitor.visitVarInsn(ILOAD, 1);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
            methodVisitor.visitIincInsn(1, 1);
            methodVisitor.visitJumpInsn(GOTO, conditionLabel);

            // 第3段
            methodVisitor.visitLabel(returnLabel);
            methodVisitor.visitInsn(RETURN);
            methodVisitor.visitMaxs(0, 0);
            methodVisitor.visitEnd();
        }

        cw.visitEnd();

        // (3) 调用toByteArray()方法
        return cw.toByteArray();
    }
}

検証結果

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("sample.HelloWorld");
        Object obj = clazz.newInstance();

        Method method = clazz.getDeclaredMethod("test");
        method.invoke(obj);
    }
}

4. try-catch ステートメント

public class HelloWorld {
    public void test() {
        try {
            System.out.println("Before Sleep");
            Thread.sleep(1000);
            System.out.println("After Sleep");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ASMコーディングの実装

public class HelloWorldGenerateCore {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成byte[]内容
        byte[] bytes = dump();

        // (2) 保存byte[]到文件
        FileUtils.writeBytes(filepath, bytes);
    }

    public static byte[] dump() throws Exception {
        // (1) 创建ClassWriter对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // (2) 调用visitXxx()方法
        cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld",
                null, "java/lang/Object", null);

        {
            MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv1.visitCode();
            mv1.visitVarInsn(ALOAD, 0);
            mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            mv1.visitInsn(RETURN);
            mv1.visitMaxs(0, 0);
            mv1.visitEnd();
        }

        {
            MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
            Label startLabel = new Label();
            Label endLabel = new Label();
            Label exceptionHandlerLabel = new Label();
            Label returnLabel = new Label();

            // 第1段
            mv2.visitCode();
            // visitTryCatchBlock可以在这里访问
            mv2.visitTryCatchBlock(startLabel, endLabel, exceptionHandlerLabel, "java/lang/InterruptedException");

            // 第2段
            mv2.visitLabel(startLabel);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("Before Sleep");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitLdcInsn(new Long(1000L));
            mv2.visitMethodInsn(INVOKESTATIC, "java/lang/Thread", "sleep", "(J)V", false);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("After Sleep");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

            // 第3段
            mv2.visitLabel(endLabel);
            mv2.visitJumpInsn(GOTO, returnLabel);

            // 第4段
            mv2.visitLabel(exceptionHandlerLabel);
            mv2.visitVarInsn(ASTORE, 1);
            mv2.visitVarInsn(ALOAD, 1);
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/InterruptedException", "printStackTrace", "()V", false);

            // 第5段
            mv2.visitLabel(returnLabel);
            mv2.visitInsn(RETURN);

            // 第6段
            // visitTryCatchBlock也可以在这里访问
            // mv2.visitTryCatchBlock(startLabel, endLabel, exceptionHandlerLabel, "java/lang/InterruptedException");
            mv2.visitMaxs(0, 0);
            mv2.visitEnd();
        }

        cw.visitEnd();

        // (3) 调用toByteArray()方法
        return cw.toByteArray();
    }
}

検証結果

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("sample.HelloWorld");
        Object obj = clazz.newInstance();

        Method method = clazz.getDeclaredMethod("test");
        method.invoke(obj);
    }
}

5.クラスファイルのASMコードを表示する

1.印刷

特定のクラスについて ASM を学習したい場合、または実装したい関数の実装方法がわからない場合は、次のクラスを使用して ASM コードを出力および表示できます。

public class ASMPrint {
    public static void main(String[] args) throws IOException {
        // (1) 设置参数
        String className = "sample.HelloWorld";
        int parsingOptions = ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG;
        boolean asmCode = true;

        // (2) 打印结果
        Printer printer = asmCode ? new ASMifier() : new Textifier();
        PrintWriter printWriter = new PrintWriter(System.out, true);
        TraceClassVisitor traceClassVisitor = new TraceClassVisitor(null, printer, printWriter);
        new ClassReader(className).accept(traceClassVisitor, parsingOptions);
    }
}

className 値はクラスの完全修飾名に設定されます。これは、sample.HelloWorld などの自分で作成したクラス、または java.lang.Comparable などの JDK に付属のクラスにすることができます。

asmCode 値は true または false に設定されます。true の場合、対応する ASM コードを出力でき、false の場合、メソッドに対応する命令を出力できます。

parsingOptions 値は、ClassReader.SKIP_CODE、ClassReader.SKIP_DEBUG、ClassReader.SKIP_FRAMES、および ClassReader.EXPAND_FRAMES の組み合わせに設定されます。0 に設定して、さまざまな詳細レベルで情報を出力することもできます。

2.プラグイン

IDEA の場合は、ASM ByteCode Viewer をインストールし、表示したい Java ファイルを選択して右クリックして選択すると、右側の ASPPlugin に対応する ByteCode、ASMified、および Groovified が表示されます。または、プラグイン ASM BytecodeOutline (私が使用した IDEA のコミュニティ バージョンでは効果が得られませんでした)

6. ツリーAPI

コア API とツリー API の違い

  • Tree API の利点: 使いやすさ: これまでに Core API と Tree API を使用したことがない場合は、Tree API を簡単に始めることができます。機能: より複雑な関数を実装する場合、コア API よりもツリー API の方が実装が簡単です。
  • Core APIのメリット: 実行効率: 同じ機能を実現することを前提とすると、Core APIはTree APIよりも実行効率が高く、時間がかかりません。メモリ使用量: Core API は、Tree API よりも少ないメモリ領域を使用します。
  • 1点目は、ASMではCore APIでもTree APIでもクラス生成、クラス変換、クラス分析の操作が行えることです。
  • 2つ目は、Core APIとTree APIにはそれぞれメリットがあるということです。ツリー API は使いやすく、複雑な操作の実装も簡単ですが、コア API はより高速に実行され、使用するメモリ領域が少なくなります。

この記事はスペースの都合により削除されました。ご興味がございましたら、著者までご連絡ください。

7. この記事で使用したツール

1、ファイルユーティリティ

public class FileUtils {
    public static String getFilePath(String relativePath) {
        String dir = FileUtils.class.getResource("/").getPath();
        return dir + relativePath;
    }
}

8. 将来的に ASM で何ができるか考えてみませんか?

1. クラスの生成 ---- テンプレートに基づいてクラスを生成します (スキャフォールディングと組み合わせてプロジェクトを迅速に構築し、初期のプロジェクト モジュールを迅速に構築します)。

2. クラスの変更 - テンプレートに従ってクラスを変更します (Java ソース コードをコンパイルして .class バイトコード ファイルを生成するときに、特定の構造を組み合わせてクラスを変更し、システム効率の向上と開発マンパワーの解放という目的を達成できます)。 IDEA プラグイン開発と組み合わせることができます。lombok に類似した、またはより強力なプラグインを開発します)

著者: JD Health Ma Renxi

出典:JD Cloud Developer Community 転載の際は出典を明記してください

Microsoft、新しい「Windowsアプリ」を発表 Xiaomi、Xiaomi Velaが完全オープンソース、基盤となるカーネルはNuttX Vite 5 であることを正式発表 Alibaba Cloud 11.12が正式リリース 障害の原因が判明:アクセスキーサービス(アクセスキー)の異常 GitHub レポート: TypeScript が Java に代わって 3 番目に人気になる 言語オペレータの奇跡的な操作 : バックグラウンドでネットワークを切断し、ブロードバンド アカウントを無効にし、ユーザーに光モデムの変更を強制する ByteDance: AI を使用して Linux カーネル パラメータを自動的に調整する Microsoft オープン ソースTerminal Chat Spring Framework 6.1 が正式に GA OpenAI の元 CEO 兼社長の Sam Altman 氏と Greg Brockman 氏が Microsoft に入社
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/u/4090830/blog/10149739