社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
Java 源文件经过 javac 编译器编译之后,将会生成对应的二进制文件。
每个合法的 Java 字节码文件都具备精确的定义,而正是这种精确的定义,才使得 Java 虚拟机得以正确读取和解释所有的 Java 字节码文件。
如最简单的helloworld
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello world");
}
}
图示为javac编译的过程;java文件经过javac编译成class文件;class文件遵守java类定义规范;
可以在这里下载到完整的书籍
编译后的class文件可以被加载到jvm中运行;
下图为class文件的几个重要组成部分:
Access_flag:该项指明了该文件中定义的是类还是接口,同时还指名了类或接口的访问标志,如 public,private, abstract 等信息。访问标记定义如下:
ACC_PUBLIC:是否为public类型
ACC_PRIVATE:是否为private类型
ACC_PROTECTED:是否为protected类型
ACC_STATIC:是否为static类型
ACC_VOLATILE:是否为volatile类型
ACC_TRANSIENT:是否是transient类型
ACC_SYNTHETIC:是否是编译器自动生成
ACC_FINAL:是否是final
ACC_SUPER:是否允许使用invokespecial的新语义;jdk1.0.2后一直为真
ACC_INTERFACE:是否是接口
ACC_ABSTRACE:是否是抽象类
ACC_SYNTHETIC:是否由用户代码生成
ACC_ANNOTATION:是否是注解
ACC_ENUM:是否是枚举类型
This Class:指向表示该类全限定名称的字符串常量的指针。
Fields:该项对类或接口中声明的字段进行了细致的描述。需要注意的是,fields 列表中仅列出了本类或接口中的字段,并不包括从超类和父接口继承而来的字段。字节码定义如下:
B:对应byte类型
J:对应long
C:对应char
S:对应short
D:对应duble
Z:对应boolean
F:对应float
V:对应void
I:对应int
L:类的全限定符开始,以;结束。如 Ljava.lang.String;
[:数组的一个维度;如int[][] 就表示为 [[I
Methods:该项对类或接口中声明的方法进行了细致的描述。例如方法的名称、参数和返回值类型等。需要注意的是,methods 列表里仅存放了本类或本接口中的方法,并不包括从超类和父接口继承而来的方法。使用 ASM 进行 AOP 编程,通常是通过调整 Method 中的指令来实现的。
ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。
ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。
使用ASM框架需要引入ASM的包:可以在你的pom文件中加入下面这段来引入ASM;
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>
ASM 提供了三个基于 ClassVisitor 接口的类来实现 class 文件的生成和转换:
ClassReader:ClassReader 解析一个类的 class 字节码,该类的 accept 方法接受一个 ClassVisitor 的对象,在 accept 方法中,会按上文描述的顺序逐个调用 ClassVisitor 对象的方法。它可以被看做事件的生产者。
ClassAdapter:ClassAdapter 是 ClassVisitor 的实现类。它的构造方法中需要一个 ClassVisitor 对象,并保存为字段 protected ClassVisitor cv。在它的实现中,每个方法都是原封不动的直接调用 cv 的对应方法,并传递同样的参数。可以通过继承 ClassAdapter 并修改其中的部分方法达到过滤的作用。它可以看做是事件的过滤器。
ClassWriter:ClassWriter 也是 ClassVisitor 的实现类。ClassWriter 可以用来以二进制的方式创建一个类的字节码。对于 ClassWriter 的每个方法的调用会创建类的相应部分。例如:调用 visit 方法就是创建一个类的声明部分,每调用一次 visitMethod 方法就会在这个类中创建一个新的方法。在调用 visitEnd 方法后即表明该类的创建已经完成。
package com.violetgo.asm;
/**
* @author weigao
* @since 15/6/8
*/
public class HelloWorld {
public void sayHello() {
System.out.println("Hello World!");
}
}
package com.violetgo.asm;
import org.objectweb.asm.*;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
/**
* @author weigao
* @since 15/6/8
*/
public class TestASM extends ClassLoader implements Opcodes {
public static void main(String[] args) throws IOException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, SecurityException,InstantiationException {
ClassReader cr=new ClassReader(HelloWorld.class.getName());
ClassWriter cw=new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassAdapter adapter = new TestClassAdapter(cw);
cr.accept(adapter, 0);
byte[] code=cw.toByteArray();
//自定义加载器
TestASM loader=new TestASM();
Class<?> appClass=loader.defineClass(null, code, 0,code.length);
appClass.getMethods()[0].invoke(appClass.newInstance(), new Object[]{});
}
public static class TestClassAdapter extends ClassAdapter{
public TestClassAdapter(ClassVisitor classVisitor) {
super(classVisitor);
}
public MethodVisitor visitMethod(int arg, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(arg, name, descriptor, signature, exceptions);
if (name.equals("sayHello")) {
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V");
}
return mv;
}
}
}
输出结果如下、标红部分即为我们注入进去的代码::
Tips:在使用asm编码时;如果遇到较为复杂的类;可以先使用java写一遍;然后再使用javap –c –p 进行反编译;然后针对javap的指令集转换成asm编码即可;
下面介绍下java常用指令;此处不是全部指令:
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!