Простой класс через дерево 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.LoggingTest Starting method: main run 1 Starting method: run2 run 2 Ending method: run2 Ending method: main |
Обратите внимание, что в отличие от обычных списков, объект InsnList может быть изменен во время итерации по нему. Любые изменения незамедлительно отражаются. Таким образом, если после текущей позиции будут вставлены некоторые инструкции, они также будут повторяться.
Ссылка: Управление файлами классов Java с помощью ASM 4 — Часть вторая: Tree API от нашего партнера JCG Дебашиш Рэй Чаудхури в блоге Geeky Articles .