使用ASM来书写Java代码

简介: 使用ASM来书写Java代码

ASM是一套JAVA字节码生成架构。它可以动态生成二进制格式的stub类或其他代理类,或者在类被JAVA虚拟机装入内存之前,动态修改类。 ASM 提供了与 BCEL( http://jakarta.apache.org/bcel )和SERP(http://serp.sourceforge.net/ )相似的功能,只有22K的大小,比起350K的BCEL和150K的SERP来说,是相当小巧的,并且它有更高的执行效率,是BCEL的7倍,SERP 的11倍以上。ASM一贯的设计思想就是将其应用于动态生成领域,因此小巧和快捷一直是这个产品的设计和实现的指导思想。

此产品由法国电信公司的研发工程师Eric Bruneton负责。从2002年7月ASM的第一个版本发布至今,此产品已经升级了五次,日臻完美。到目前为止,ASM最新的版本是1.3.5,你可以去 http://asm.objectweb.org/ 下载。

ASM的最终目标是创建一个生成工具,可以被用来执行对任何类的处理操作(不像一些工具,比如Javassit,它只支持预先定义的类操作,然而在许多场合这一功能是有局限性的)。

JAVA的CLASS文件格式

要想驾驭ASM,先要了解一下JAVA的CLASS文件格式。JAVA的CLASS文件通常是树型结构。根节点包含以下元素:

  • ConstantPool:符号表;
  • FieldInfo:类中的成员变量信息;
  • MethodInfo:类中的方法描述;
  • Attribute:可选的附加节点。

FieldInfo节点包含成员变量的名称,诸如public,private,static等的标志。ConstantValue属性用来存储静态的不变的成员变量的值。Deprecated和Synthetic被用来标记一个成员变量是不被推荐的或由编译器生成的。

MethodInfo节点包含方法的名称,参数的类型和和它的返回值,方法是公有的,私有的或静态的等标志。MethodInfo包含可选的附加属性,其中最重要的是Code属性,它包含非抽象的方法的代码。Exceptions属性包含方法将抛出的Exception的名称。Deprecated 和Synthetic属性的信息同上面的FieldInfo的定义一样。

根节点的可选属性有SourceFile,InnerClasses和Deprecated。SourceFile用来存储被编译成字节码的源代码文件的原始名称;InnerClasses存储内部类的信息。由于这些属性的存在,java 的类格式是可以扩展的,也就是说可以在一个class中附加一些非标准的属性, java虚拟机会忽略这些不可识别的属性,正常的加载这个class。

ConstantPool是一个由数字或字符串常量的索引组成的队列,或由此类的树的其他节点引用的,由其他对象创建的被引用常量的索引组成的队列。这个表的目标是为了减少冗余。例如,FieldInfo节点不包含节点的名称,只包含它在这一表中的索引。同样的,GETFIELD和 PUTFIELD不直接包含成员变量的名称,只包含名称的索引。

精通ASM

Asm架构整体都围绕着两个接口,即ClassVisitor和 CodeVisitor,它们能访问每个类的方法,成员变量,包含在每个方法中的字节码指令。ClassReader用来读取class文件;ClassWriter类用来写生成的Class文件。

为了修改已经存在的class,你必须使用分析class文件的ClassReader,类的修正器和写class文件的ClassWriter。类的修正器就是一个ClassVisitor,它可以委派一部分工作到其他的ClassVisitor,但是为了实现预期的修改步骤,它将改变一些参数的值,或者调用一些其他方法。为了比较容易的实现这种类的修正器,ASM提供了一个ClassAdapter和CodeAdapter,这两个适配器类分别实现了ClassVistor和CodeVistor接口。

HelloWorld,体验造类的神奇

下面是一个应用ASM动态生成字节码的类,并调用其中方法的完整的HelloWorld 程序,程序的功能是动态生成一个Example.class类,并实例化一个Example对象,调用对象的main函数,在屏幕上打印出"Hello world!"

 

import java.io.FileOutputStream;
import java.lang.reflect.Method;
 
import org.objectweb.asm.ClassWriter;
 
public class Helloworld extends ClassLoader {
 
  public static void main (final String args[]) throws Exception {
      /* * 此程序将生成一个class,对应的java源代码是:
       * * public class Example {
       *      public static void main (String[] args) {
       *        System.out.println("Hello world!");
       *      }
       *   }
       * */
       // 创建一个ClassWriter
       ClassWriter cw = new ClassWriter(false);
       cw.visit(.ACC_PUBLIC, "Example", "java/lang/Object", null, null);
       // 创建一个 MethodWriter
       CodeVisitor mw = cw.visitMethod(ACC_PUBLIC, "", "()V", null);
       // 推入 磘his 变量
       mw.visitVarInsn(ALOAD, 0);
       // 创建父类的构造函数
       mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V");
       mw.visitInsn( ETURN);
       // 这段代码使用最多一个栈元素和一个本地变量
       mw.visitMaxs(1, 1);
       // 为main方法创建一个 
       MethodWriter mw = cw.visitMethod( ACC_PUBLIC ACC_STATIC, "main","([Ljava/lang/String;)V", null);
       // 使用System类的out成员类
       mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
       // pushes the "Hello World!"
       String constant = mw.visitLdcInsn("Hello world!");
       // 调用System.out的磒rintln 函数
       mw.visitMethodInsn(INVOKEVIRTUAL,
                           "java/io/PrintStream",
                            "println",
                             "(Ljava/lang/String;)V");
       mw.visitInsn(RETURN);
       // 这段代码使用最多两个栈元素和两个本地变量
       mw.visitMaxs(2, 2);
       // 生成字节码形式的类
       byte[] code = cw.toByteArray();
       FileOutputStream fos = new FileOutputStream("Example.class");
       //写文件
       fos.write(code);
       //关闭输出流 fos.close();
       //实例化刚刚生成的类
       Helloworld loader = new Helloworld();
       Class exampleClass = loader.defineClass("Example", code, 0, code.length);
      // 使用动态生成的类打印 碒elloworld
      Method main = exampleClass.getMethods()[0];
      main.invoke(null, new Object[] {null});
  }
}

1.   流程控制

说明:JVM提供了基本的流程控制结构,这些结构都是基于Label而实现的。这些跳转指令,包括基于比较结果的有条件跳转和无条件的GOTO指令。另外,JVM也提供了实现switch结构的LOOKUPSWITCH和TABLESWITCH指令,其中LOOKUPSWITCH是基于键比较的,而TABLESWITCH则是基于键索引的,因此后者的匹配速度更快。

2.   接口声明

ClassWriter cw = new ClassWriter(false);
cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, "asm/IA", null, "java/lang/Object", null);
cw.visitSource("IA.java", null);
cw.visitEnd();

等价于:

public interface IA{}

3.   类声明

3.1  抽象类

ClassWriter cw =new ClassWriter(false);
cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER + ACC_ABSTRACT, "asm/A", null, "java/lang/Object", null);
cw.visitSource("A.java", null);

 

等价于:

public abstract class A{}

3.2  具体类

ClassWriter cw = new ClassWriter(false);
cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER, "asm/A", null,"java/lang/Object", null);
cw.visitSource("A.java", null);
cw.visitEnd();

等价于:

public class A{}

4.   类

4.1  数组

4.1.1创建
mv.visitInsn(ICONST_3);
mv.visitIntInsn(NEWARRAY, T_INT);
mv.visitVarInsn(ASTORE, 1);      //   将数组引用存到局部变量栈1号的位置

等价于:

int[] a = new int[3];

4.1.2取值

mv.visitVarInsn(ALOAD,1);       //   数组引用在局部变量栈1号的位置
mv.visitInsn(ICONST_1);
mv.visitInsn(IALOAD);
mv.visitVarInsn(ISTORE, 2);

等价于:

int b = a[1];

4.1.3赋值

mv.visitVarInsn(ALOAD,1);
mv.visitInsn(ICONST_1);
mv.visitInsn(ICONST_2);
mv.visitInsn(IASTORE);

等价于:

a[1] = 2;

4.2  构造函数

4.2.1<init>
4.2.1.1   创建
MethodVisitor mv= cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null,null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object","<init>", ()V");
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();

说明:构造函数<init>在执行时,需要首先执行父类的构造函数或者类内部其他构造

函数。

4.2.1.2   调用
mv.visitTypeInsn(NEW,"asm/A");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "asm/A", "<init>","()V");
mv.visitVarInsn(ASTORE, 1);

等价于:

A a = new A();

说明:在初始化一般对象时,我们需要先调用NEW指令,来创建该对象实例。而由于

后续的INVOKESPECIAL指令是调用类的构造函数,而该指令执行完以后,对对象的引

用将从栈中弹出,所以在NEW指令执行完以后,INVOKESPECIAL指令执行以前,我们

需要调用DUP指令,来增加对象引用的副本。

4.2.2<clinit>
4.2.2.1   创建
MethodVisitor mv= cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null,null);
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
mv.visitLdcInsn("hello world");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream","println",
"(Ljava/lang/String;)V");
mv.visitInsn(RETURN);
mv.visitMaxs(2, 0);
mv.visitEnd();

等价于:

static {
    System.out.println("helloworld");
}
4.2.2.2   调用

<clinit>在类被加载时自动调用。

4.3  字段

4.3.1一般字段
4.3.1.1   创建
FieldVisitor fv= cw.visitField(ACC_PRIVATE, "a", "I", null, null);
fv.visitEnd();

等价于:

private int a;
4.3.1.2   读取

读取类当中名字为a,类型为int的字段的值。

mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, "asm/A", "a", "I");
4.3.1.3   设置
mv.visitVarInsn(ALOAD,0);
mv.visitInsn(ICONST_2);
mv.visitFieldInsn(PUTFIELD, "asm/A", "a", "I");

等价于:

a = 2;

4.3.2静态字段

4.3.2.1   创建
FieldVisitor fv= cw.visitField(ACC_PRIVATE + ACC_STATIC, "a", "I", null,
null);
fv.visitEnd();

等价于:

private static int a;
4.3.2.2   读取
mv.visitFieldInsn(GETSTATIC,"asm/A", "a", "I");
4.3.2.3   设置
mv.visitInsn(ICONST_2);
mv.visitFieldInsn(PUTSTATIC, "asm/A", "a", "I");

等价于:

a = 2;

4.4  方法

4.4.1接口方法
4.4.1.1   定义
mv =cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "getA", "()V",null, null);
mv.visitEnd();
4.4.1.2   调用
mv.visitVarInsn(ALOAD,1);
mv.visitMethodInsn(INVOKEINTERFACE, "asm/IA", "getA","()V");

等价于:

public interface IA{
     public void geA();
}
public class A implements IA{
     public void geA(){…}
}
 
IA a = new A();
a.getA();
4.4.2一般方法
4.4.2.1   定义
MethodVisitor mv= cw.visitMethod(ACC_PUBLIC, "getA", "()V", null, null);
mv.visitCode();
mv.visitInsn(RETURN);
mv.visitMaxs(0, 1);
mv.visitEnd();

等价于:

public void getA() {}
4.4.2.2   调用
mv.visitVarInsn(ALOAD,1);
mv.visitMethodInsn(INVOKEVIRTUAL, "asm/A", "getA","()V");

等价于:

A a = new A():
a.getA();
4.4.3静态方法
4.4.3.1   定义
MethodVisitor mv= cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "getA", "()V",
null, null);
mv.visitCode();
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();

等价于:

public static void getA() {}
4.4.3.2   调用
mv.visitMethodInsn(INVOKESTATIC, "asm/A","getB", "()V");

等价于:

A.getB();

说明:一般方法比静态方法在声明和调用时均要多传入一个this引用作为参数。另外,当使用INVOKESPECIAL来调用方法时,虚拟机将直接根据指令当中所指明的类类型来调用方法;而当使用INVOKEVIRTUAL来调用方法时,虚拟机将根据实例的实际类型来调用方法。

 

4.5  异常处理

4.5.1声明
mv.visitTryCatchBlock(l0,l1, l1, "java/lang/Exception");
mv.visitLabel(l0);
mv.visitLabel(l1);

等价于:

try {
} catch (Exception e) {
}

说明:在visitTryCatchBlock()当中,第一,二,三个参数均是Label实例,其中一,二表示try块的范围,三则是catch块的开始位置。而第四个参数则是异常类型。而当异常发生时,JVM将会将异常实例置于运行栈的栈顶。

 

4.5.2Signature

a)  说明:J2SE 5.0为了支持范型,参数化参数,Annotation和枚举等新增特性,因此增加了一个Signature属性,作为类,字段,方法的Description之外的一个辅助机制。

 

4.5.3Annotation

a)  Annotation:

 i.     定义:

cw.visit(V1_5, ACC_PUBLIC + ACC_ANNOTATION + ACC_ABSTRACT + ACC_INTERFACE, "asm/AN", null, "java/lang/Object", new String[]{ "java/lang/annotation/Annotation" });

等价于:

public @interface AN {}

ii.     使用:通过ClassVisitor,FieldVisitor,MethodVisitor上的visitAnnotation()方法,来获取一个AnnotationVisitor实例,从而为类,字段,方法设置Annotation。

AnnotationVisitor av0 = cw.visitAnnotation("Lasm/AN;", false);
av0.visitEnd();
@AN
public class A{}

b)  属性:

 i.     定义

mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "age","()I", null, null);
mv.visitEnd();
 
mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "age","()I", null, null);
mv.visitEnd();
 
mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "name","()Ljava/lang/String;",
null, null);
av0 = mv.visitAnnotationDefault();
av0.visit(null, "A");
av0.visitEnd();
mv.visitEnd();

等价于:

public @interface AN {
     int age();
     String name() default "A";
}

ii.     使用

av0 = cw.visitAnnotation("Lasm/AN;", false);
av0.visit("age", new Integer(1));
av0.visit("name", "B");
av0.visitEnd();

等价于:

@AN(age = 1, name = "B")
public class A {
}

c)  范型

Ø  定义

cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER,"asm/A", "<T::Lasm/IA;G:Lasm/B;>Ljava/lang/Object;Ljava/lang/Comparable;","java/lang/Object", new String[] { "java/lang/Comparable"});

 

等价于:

public class A<T extends IA, G extends B> implements Comparable {
}

说明:在类定义当中使用范型时,需要增加Signature字段来添加范型信息。该Signature的组成是“<范型参数名:范型扩展的类:范型扩展的接口…>父类描述接口描述”

Ø  范型字段

1.1定义

FieldVisitor fv = cw.visitField(ACC_PRIVATE, "l","Ljava/util/List;", "Ljava/util/List<Ljava/lang/String;>;", null);
fv.visitEnd();

等价于:

private List<String> l;

说明:在声明范型字段时,需要增加Signature来增加范型信息。该Signature的组成是

“基类型描述<参数类型描述>”

 

1.2使用:由于范型信息只是供编译器在编译时进行类型检查,而在编译以后该信息将会被擦除,因此在使用时与没有范型的情况一致。

Ø  范型方法

1.1 定义

mv = cw.visitMethod(ACC_PUBLIC, "getList","(Ljava/util/Map;)Ljava/util/List;","(Ljava/util/Map<Ljava/lang/String;Ljava/lang/Integer;>;)Ljava/util/List<Ljava/lang/String;>;",null);

 

等价于:

public List<String> getList(Map<String, Integer> maps) {…}

1.2 使用:由于范型信息只是供编译器在编译时进行类型检查,而在编译以后该信息将会被擦除,因此在使用时与没有范型的情况一致。

Ø  枚举

1.1 定义:

ClassWriter cw = new ClassWriter(false);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
 
cw.visit(V1_5, ACC_PUBLIC + ACC_FINAL + ACC_SUPER + ACC_ENUM,"asm/E",
"Ljava/lang/Enum<Lasm/E;>;", "java/lang/Enum", null);
cw.visitSource("E.java", null);
 
fv = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC + ACC_ENUM,"E1", "Lasm/E;",
null, null);
fv.visitEnd();         //   定义静态字段E1
 
fv = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC + ACC_ENUM,"E2", "Lasm/E;",
ull, null);
fv.visitEnd();         //   定义静态字段E2
 
 
fv = cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC + ACC_SYNTHETIC,"ENUM$VALUES",
"[Lasm/E;", null, null);
fv.visitEnd();         //   定义存储所有枚举值的静态字段ENUM$VALUES
 
mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V",null, null);
mv.visitCode();
 
//   初始化E1
mv.visitTypeInsn(NEW, "asm/E");
mv.visitInsn(DUP);
mv.visitLdcInsn("E1");
mv.visitInsn(ICONST_0);
mv.visitMethodInsn(INVOKESPECIAL, "asm/E", "<init>","(Ljava/lang/String;I)V");
mv.visitFieldInsn(PUTSTATIC, "asm/E", "E1","Lasm/E;");
//   初始化E2
mv.visitTypeInsn(NEW, "asm/E");
mv.visitInsn(DUP);
mv.visitLdcInsn("E2");
mv.visitInsn(ICONST_1);
mv.visitMethodInsn(INVOKESPECIAL, "asm/E", "<init>","(Ljava/lang/String;I)V");
mv.visitFieldInsn(PUTSTATIC, "asm/E", "E2","Lasm/E;");
//   初始化ENUM$VALUES,将E1,E2存入ENUM$VALUES当中
mv.visitInsn(ICONST_2);
mv.visitTypeInsn(ANEWARRAY, "asm/E");
mv.visitInsn(DUP);
mv.visitInsn(ICONST_0);
mv.visitFieldInsn(GETSTATIC, "asm/E", "E1","Lasm/E;");
mv.visitInsn(AASTORE);
mv.visitInsn(DUP);
mv.visitInsn(ICONST_1);
mv.visitFieldInsn(GETSTATIC, "asm/E", "E2","Lasm/E;");
mv.visitInsn(AASTORE);
mv.visitFieldInsn(PUTSTATIC, "asm/E", "ENUM$VALUES","[Lasm/E;");
mv.visitInsn(RETURN);
mv.visitMaxs(8, 0);
mv.visitEnd();
 
 
mv = cw.visitMethod(ACC_PRIVATE, "<init>","(Ljava/lang/String;I)V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitVarInsn(ILOAD, 2);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Enum","<init>", "(Ljava/lang/String;I)V");
mv.visitInsn(RETURN);
mv.visitMaxs(3, 3);
mv.visitEnd();
 
//   使用arraycopy()方法,将ENUM$VALUES的值存入一个新数组当中,并返回。
mv = cw.visitMethod(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "values","()[Lasm/E;", null,
null);
...
mv.visitEnd();
 
//   返回某个枚举值的字符表示
mv = cw.visitMethod(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "valueOf","(Ljava/lang/String;)Lasm/E;", null, null);
...
mv.visitEnd();
 
cw.visitEnd();

等价于:

public enum E {
     E1, E2
}

 

b)  使用:

mv.visitFieldInsn(GETSTATIC, "asm/E", "E1","Lasm/E;");

等价于:

E e = E.E1;

说明:从上面的代码可以看到,即使是一个简单的枚举,也需要使用很多的代码才能定义,因此更可行的办法是使用Java编译器来生成枚举。

相关文章
|
18小时前
|
Java 测试技术
如何编写干净、可维护的Java代码
如何编写干净、可维护的Java代码
|
1天前
|
安全 Java API
Java中的函数式编程:简化代码,提升效率
【6月更文挑战第25天】本文将深入探讨Java中函数式编程的概念、原理及其应用。我们将通过具体示例和代码片段来展示如何利用Java 8及以上版本中的Lambda表达式、Stream API等特性来简化代码编写,提高开发效率和程序性能。文章旨在为Java开发者提供一种现代的、更加简洁高效的编程方式。
18 5
|
2天前
|
Java 数据库 Spring
解锁 Spring 框架新姿势,让你的 Java 代码更加优雅!
【6月更文挑战第25天】在Spring框架中优化依赖注入、应用AOP和事务管理能提升代码优雅性。例如,使用构造函数注入降低耦合,如将手动创建改为容器管理;通过AOP实现横切关注点,如统一日志记录,避免重复代码;利用`@Transactional`注解简化事务处理,确保数据一致性。这些技巧让代码更简洁、可维护。
|
2天前
|
安全 IDE Java
使用MapStruct和Lombok简化代码
使用MapStruct和Lombok简化代码
17 2
|
2天前
|
消息中间件 监控 Java
Java 框架界的‘巨星’Spring,让你的代码翩翩起舞!
【6月更文挑战第25天】Spring,Java框架的明星,以其依赖注入(DI)和面向切面编程(AOP)简化开发。DI协调类间协作,AOP提供日志、监控等附加功能。事务管理确保代码稳定性,注解如`@Transactional`自动化事务处理。Spring的集成能力使代码灵活适应多样技术场景,让编程变得优雅高效,犹如舞蹈般流畅。跟随Spring,让代码起舞!
|
3天前
|
JavaScript 前端开发 Java
java 执行 javascript 代码
java 执行 javascript 代码
15 6
|
3天前
|
人工智能 算法 Java
java中经典算法代码整理
java中经典算法代码整理
15 0
|
3天前
|
Java
看看写的多漂亮的java代码
看看写的多漂亮的java代码
|
3天前
|
设计模式 Java
Java中设计模式及举例代码
Java中设计模式及举例代码
|
3天前
|
存储 自然语言处理 算法
JAVA代码编写规范
JAVA代码编写规范
24 1