Java字节码操纵框架ASM快速入门

ASM 是一个 Java 字节码操纵框架。它可以直接以二进制形式动态地生成 stub 类或其他代理类,或者在装载时动态地修改类。ASM 提供类似于 BCEL 和 SERP 之类的工具包的功能,但是被设计得更小巧、更快速,这使它适用于实时代码插装。

本篇内容使用ASM动态生成java类和方法

在阅读本文之前,需要对JVM有所了解,class文件格式,JVM指令等等

先加入ASM的依赖

  1. <dependency>

  2. <groupId>org.ow2.asm</groupId>

  3. <artifactId>asm-all</artifactId>

  4. <version>5.1</version>

  5. </dependency>

示例一:

这里打算用ASM动态生成如下的类

  1. package com.agent.my3;

  2.  
  3. public class Tester

  4. {

  5. public void run()

  6. {

  7. System.out.println("This is my first ASM test");

  8. }

  9. }

通过javap -s -c Tester 可以看到,class的文件格式(部分)如下:

这里可以看到一些jvm指令,也就是说,jvm执行方法的流程

代码如下:

  1. package com.agent.my3;

  2.  
  3. import org.objectweb.asm.ClassWriter;

  4. import org.objectweb.asm.MethodVisitor;

  5. import org.objectweb.asm.Opcodes;

  6.  
  7. public class ASMGettingStarted

  8. {

  9. /**

  10. * 动态创建一个类,有一个无参数的构造函数

  11. */

  12. static ClassWriter createClassWriter(String className)

  13. {

  14. ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);

  15. //声明一个类,使用JDK1.8版本,public的类,父类是java.lang.Object,没有实现任何接口

  16. cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", null);

  17.  
  18. //初始化一个无参的构造函数

  19. MethodVisitor constructor = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);

  20. //这里请看截图

  21. constructor.visitVarInsn(Opcodes.ALOAD, 0);

  22. //执行父类的init初始化

  23. constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);

  24. //从当前方法返回void

  25. constructor.visitInsn(Opcodes.RETURN);

  26. constructor.visitMaxs(1, 1);

  27. constructor.visitEnd();

  28. return cw;

  29. }

  30. /**

  31. * 创建一个run方法,里面只有一个输出

  32. * public void run()

  33. * {

  34. * System.out.println(message);

  35. * }

  36. * @return

  37. * @throws Exception

  38. */

  39. static byte[] createVoidMethod(String className, String message) throws Exception

  40. {

  41. //注意,这里需要把classname里面的.改成/,如com.asm.Test改成com/asm/Test

  42. ClassWriter cw = createClassWriter(className.replace('.', '/'));

  43.  
  44. //创建run方法

  45. //()V表示函数,无参数,无返回值

  46. MethodVisitor runMethod = cw.visitMethod(Opcodes.ACC_PUBLIC, "run", "()V", null, null);

  47. //先获取一个java.io.PrintStream对象

  48. runMethod.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");

  49. //将int, float或String型常量值从常量池中推送至栈顶 (此处将message字符串从常量池中推送至栈顶[输出的内容])

  50. runMethod.visitLdcInsn(message);

  51. //执行println方法(执行的是参数为字符串,无返回值的println函数)

  52. runMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

  53. runMethod.visitInsn(Opcodes.RETURN);

  54. runMethod.visitMaxs(1, 1);

  55. runMethod.visitEnd();

  56.  
  57. return cw.toByteArray();

  58. }

  59.  
  60. public static void main(String[] args) throws Exception

  61. {

  62. String className = "com.agent.my3.Tester";

  63. byte[] classData = createVoidMethod(className, "This is my first ASM test");

  64. Class<?> clazz = new MyClassLoader().defineClassForName(className, classData);

  65. clazz.getMethods()[0].invoke(clazz.newInstance());

  66. }

  67. }

示例二:

这里打算用ASM动态生成如下的类

  1. package com.agent.my3;

  2.  
  3. public class Tester

  4. {

  5. public Integer getIntVal()

  6. {

  7. return 10;

  8. }

  9. }

通过javap -s -c Tester 可以看到,class的文件格式(部分)如下:


 

代码如下:

  1. package com.agent.my3;

  2.  
  3. import org.objectweb.asm.ClassWriter;

  4. import org.objectweb.asm.MethodVisitor;

  5. import org.objectweb.asm.Opcodes;

  6. import org.objectweb.asm.Type;

  7.  
  8. public class ASMGettingStarted

  9. {

  10. /**

  11. * 动态创建一个类,有一个无参数的构造函数

  12. */

  13. static ClassWriter createClassWriter(String className)

  14. {

  15. ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);

  16. //声明一个类,使用JDK1.8版本,public的类,父类是java.lang.Object,没有实现任何接口

  17. cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, className, null, Type.getInternalName(Object.class), null);

  18.  
  19. //初始化一个无参的构造函数

  20. MethodVisitor constructor = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);

  21. //这里请看截图

  22. constructor.visitVarInsn(Opcodes.ALOAD, 0);

  23. //执行父类的init初始化

  24. constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "<init>", "()V", false);

  25. //从当前方法返回void

  26. constructor.visitInsn(Opcodes.RETURN);

  27. constructor.visitMaxs(1, 1);

  28. constructor.visitEnd();

  29. return cw;

  30. }

  31.  
  32. /**

  33. * 创建一个返回Integer=10的函数

  34. * public Integer getIntVal()

  35. * {

  36. * return 10;

  37. * }

  38. * @return

  39. * @throws Exception

  40. */

  41. static byte[] createRetrurnMethod(String className, int returnValue) throws Exception

  42. {

  43. //注意,这里需要把classname里面的.改成/,如com.asm.Test改成com/asm/Test

  44. ClassWriter cw = createClassWriter(className.replace('.', '/'));

  45.  
  46. //创建get方法

  47. //()Ljava/lang/Integer;表示函数,无参数,返回值为:java.lang.Integer,注意最后面的分号,没有就会报错

  48. MethodVisitor getMethod = cw.visitMethod(Opcodes.ACC_PUBLIC, "getIntVal", "()Ljava/lang/Integer;", null, null);

  49. //将单字节的常量值(-128~127)推送至栈顶(如果不是-128~127之间的数字,则不能用bipush指令)

  50. getMethod.visitIntInsn(Opcodes.BIPUSH, returnValue);

  51. //调用Integer的静态方法valueOf把10转换成Integer对象

  52. String methodDesc = Type.getMethodDescriptor(Integer.class.getMethod("valueOf", int.class));

  53. getMethod.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Integer.class), "valueOf", methodDesc, false);

  54. //从当前方法返回对象引用

  55. getMethod.visitInsn(Opcodes.ARETURN);

  56. getMethod.visitMaxs(1, 1);

  57. getMethod.visitEnd();

  58.  
  59. return cw.toByteArray();

  60. }

  61.  
  62. public static void main(String[] args) throws Exception

  63. {

  64. String className = "com.agent.my3.Tester";

  65. /**

  66. * 因为上面方法用的是Opcodes.BIPUSH指令【将单字节的常量值(-128~127)推送至栈顶(如果不是-128~127之间的数字,则不能用bipush指令】

  67. * 所以,这里传入的int参数,只能是-127~128

  68. * 如果要传入其他的int值,则需要使用其他的jvm指令

  69. */

  70. byte[] classData = createRetrurnMethod(className, 10);

  71. Class<?> clazz = new MyClassLoader().defineClassForName(className, classData);

  72. Object value = clazz.getMethods()[0].invoke(clazz.newInstance());

  73. System.out.println(value);

  74. }

  75. }

注意:示例二里面大量使用了Type类的方法,示例一里面没有使用

  1. package com.agent.my3;

  2.  
  3. public class MyClassLoader extends ClassLoader

  4. {

  5. public MyClassLoader() {

  6. super(Thread.currentThread().getContextClassLoader());

  7. }

  8.  
  9. public Class<?> defineClassForName(String name, byte[] data) {

  10. return this.defineClass(name, data, 0, data.length);

  11. }

  12. }

  13. 转自https://blog.csdn.net/mn960mn/article/details/51418236

猜你喜欢

转载自blog.csdn.net/sanyaoxu_2/article/details/81407486