Простой класс через дерево API: давайте используем дерево API для создания нашего первого класса. Опять же, я собираюсь перейти прямо к примеру кода, потому что нет ничего лучше, чем пример кода. Сгенерированный класс имеет метод main, который печатает «Hello World!».
TreeAPIDemo.java
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
package com.geekyarticles.asm;import java.io.DataOutputStream;import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;import org.objectweb.asm.ClassWriter;import org.objectweb.asm.Opcodes;import org.objectweb.asm.tree.ClassNode;import org.objectweb.asm.tree.FieldInsnNode;import org.objectweb.asm.tree.InsnNode;import org.objectweb.asm.tree.LdcInsnNode;import org.objectweb.asm.tree.MethodInsnNode;import org.objectweb.asm.tree.MethodNode;public class TreeAPIDemo { public static void main(String [] args) throws Exception{ ClassNode classNode=new ClassNode(4);//4 is just the API version number //These properties of the classNode must be set classNode.version=Opcodes.V1_6;//The generated class will only run on JRE 1.6 or above classNode.access=Opcodes.ACC_PUBLIC; classNode.signature="Lcom/geekyarticles/asm/Generated;"; classNode.name="com/geekyarticles/asm/Generated"; classNode.superName="java/lang/Object"; //Create a method MethodNode mainMethod=new MethodNode(4,Opcodes.ACC_PUBLIC|Opcodes.ACC_STATIC,"main", "([Ljava/lang/String;)V",null, null); mainMethod.instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")); mainMethod.instructions.add(new LdcInsnNode("Hello World!")); mainMethod.instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V")); mainMethod.instructions.add(new InsnNode(Opcodes.RETURN)); //Add the method to the classNode classNode.methods.add(mainMethod); //Write the class ClassWriter cw=new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES); classNode.accept(cw); //Dump the class in a file File outDir=new File("out/com/geekyarticles/asm"); outDir.mkdirs(); DataOutputStream dout=new DataOutputStream(new FileOutputStream(new File(outDir,"Generated.class"))); dout.write(cw.toByteArray()); dout.flush(); dout.close(); }} |
Как видите, код очень прост. Основное преимущество по сравнению с BCEL заключается в том, что в отличие от BCEL, ASM не требует явного добавления каждой константы в пул констант. Вместо этого ASM заботится о самом постоянном пуле.
Чтение файла класса: ClassNode — это ClassVisitor. Таким образом, чтение класса для использования в дереве API так же просто, как создание объекта ClassReader и использование его для чтения файла класса, а также передача объекта ClassNode в его методе accept в качестве параметра. Как только это сделано, переданный ClassNode полностью инициализируется со всей информацией, представленной в классе. В следующем примере мы распечатаем все методы в классе.
TreeAPIClassReaderDemo.java
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package com.geekyarticles.asm;import java.io.FileInputStream;import java.io.InputStream;import org.objectweb.asm.ClassReader;import org.objectweb.asm.tree.ClassNode;import org.objectweb.asm.tree.MethodNode;public class TreeAPIClassReaderDemo { public static void main(String[] args) throws Exception{ InputStream in=new FileInputStream("out/com/geekyarticles/asm/Generated.class"); ClassReader cr=new ClassReader(in); ClassNode classNode=new ClassNode(); //ClassNode is a ClassVisitor cr.accept(classNode, 0); //Let's move through all the methods for(MethodNode methodNodeclassNode.methods){ System.out.println(methodNode.name+" "+methodNode.desc); } }} |
Изменение файла класса. Изменение файла класса представляет собой комбинацию двух вышеописанных процедур. Сначала мы читаем класс обычным способом, вносим необходимые изменения в данные, а затем записываем их обратно в файл. Следующая программа реализует автоматическое внедрение некоторого регистрационного кода. В настоящее время наш класс Logger печатает только на стандартный вывод. Каждый метод, аннотированный @Loggable, будет зарегистрирован, когда они начнутся и когда возврат. В этом мы не регистрируем бросок-исключение. Однако это также может быть реализовано таким же образом, проверяя код операции ATHROW.
LoggingInsertion.java
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
package com.geekyarticles.asm;import java.io.DataOutputStream;import java.io.File;import java.io.FileOutputStream;import java.io.InputStream;import java.util.Iterator;import org.objectweb.asm.ClassReader;import org.objectweb.asm.ClassWriter;import org.objectweb.asm.Opcodes;import org.objectweb.asm.tree.AbstractInsnNode;import org.objectweb.asm.tree.AnnotationNode;import org.objectweb.asm.tree.ClassNode;import org.objectweb.asm.tree.InsnList;import org.objectweb.asm.tree.LdcInsnNode;import org.objectweb.asm.tree.MethodInsnNode;import org.objectweb.asm.tree.MethodNode;public class LoggingInsertion { public static void main(String[] args) throws Exception{ InputStream in=LoggingInsertion.class.getResourceAsStream("/com/geekyarticles/asm/LoggingTest.class"); ClassReader cr=new ClassReader(in); ClassNode classNode=new ClassNode(); cr.accept(classNode, 0); //Let's move through all the methods for(MethodNode methodNodeclassNode.methods){ System.out.println(methodNode.name+" "+methodNode.desc); boolean hasAnnotation=false; if(methodNode.visibleAnnotations!=null){ for(AnnotationNode annotationNodemethodNode.visibleAnnotations){ if(annotationNode.desc.equals("Lcom/geekyarticles/asm/Loggable;")){ hasAnnotation=true; break; } } } if(hasAnnotation){ //Lets insert the begin logger InsnList beginList=new InsnList(); beginList.add(new LdcInsnNode(methodNode.name)); beginList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/geekyarticles/asm/Logger", "logMethodStart", "(Ljava/lang/String;)V")); Iterator<AbstractInsnNode> insnNodes=methodNode.instructions.iterator(); while(insnNodes.hasNext()){ System.out.println(insnNodes.next().getOpcode()); } methodNode.instructions.insert(beginList); System.out.println(methodNode.instructions); //A method can have multiple places for return //All of them must be handled. insnNodes=methodNode.instructions.iterator(); while(insnNodes.hasNext()){ AbstractInsnNode insn=insnNodes.next(); System.out.println(insn.getOpcode()); if(insn.getOpcode()==Opcodes.IRETURN ||insn.getOpcode()==Opcodes.RETURN ||insn.getOpcode()==Opcodes.ARETURN ||insn.getOpcode()==Opcodes.LRETURN ||insn.getOpcode()==Opcodes.DRETURN){ InsnList endList=new InsnList(); endList.add(new LdcInsnNode(methodNode.name)); endList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/geekyarticles/asm/Logger", "logMethodReturn", "(Ljava/lang/String;)V")); methodNode.instructions.insertBefore(insn, endList); } } } } //We are done now. so dump the class ClassWriter cw=new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES); classNode.accept(cw); File outDir=new File("out/com/geekyarticles/asm"); outDir.mkdirs(); DataOutputStream dout=new DataOutputStream(new FileOutputStream(new File(outDir,"LoggingTest.class"))); dout.write(cw.toByteArray()); dout.flush(); dout.close(); }} |
LoggingTest.java
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
package com.geekyarticles.asm;public class LoggingTest { public static void run1(){ System.out.println("run 1"); } @Loggable public static void run2(){ System.out.println("run 2"); } @Loggable public static void main(String [] args){ run1(); run2(); }} |
Logger.java
|
01
02
03
04
05
06
07
08
09
10
11
|
package com.geekyarticles.asm;public class Logger { public static void logMethodStart(String methodName){ System.out.println("Starting method: "+methodName); } public static void logMethodReturn(String methodName){ System.out.println("Ending method: "+methodName); }} |
Loggable.java
|
01
02
03
04
05
06
07
08
09
10
11
12
|
package com.geekyarticles.asm;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Loggable {} |
Если вы запустите эту программу, сгенерированный файл будет зависеть от класса Logger. Вручную скопируйте класс Logger в правильный пакет в каталоге out. Если вы запустите сгенерированный класс (который является модифицированной версией класса LoggingTest), вывод будет следующий.
|
1
2
3
4
5
6
7
|
bash-4.1$ java com.geekyarticles.asm.LoggingTestStarting method: mainrun 1Starting method: run2run 2Ending method: run2Ending method: main |
Обратите внимание, что в отличие от обычных списков, объект InsnList может быть изменен во время итерации по нему. Любые изменения незамедлительно отражаются. Таким образом, если после текущей позиции будут вставлены некоторые инструкции, они также будут повторяться.
Ссылка: Управление файлами классов Java с помощью ASM 4 — Часть вторая: Tree API от нашего партнера JCG Дебашиш Рэй Чаудхури в блоге Geeky Articles .