Class文件格式
使用VScode安装Hexdump
插件后,右击选择Show Hexdump
即可查看到Class文件的原始内容,使用JDK中自带的反编译工具javap
可将Class文件反编译成Java汇编代码,使用-verbose
参数可在控制台输出
- 一组以8字节为单位的字节流,各个数据项按照顺序紧凑排列(无空格和换行)
- 对于空间大于8字节的数据项,按照高位在前的方式分隔成多个8字节进行存储
- 只有两种数据类型:无符号数和表
- 无符号数:基本数据类型,以u1、u2、u4、u8来代表集合字节的无符号数
- 表:由多个无符号数和其他表构成的复合数据类型,通常以
_info
结尾
每个Class文件对应如下结构:
数据类型 | 数据项 | 描述 |
---|---|---|
u4 |
magic |
魔数,表明该文件是否为一个能被虚拟机接收的Class文件,固定值0x CA FE BA BE 不会改变 |
u2 |
minor_version |
次版本号,与JDK版本的对应关系从这里可以查到 |
u2 |
major_version |
主版本号,与JDK版本的对应关系从这里可以查到 |
u2 |
constant_pool_count |
常量池的数量 |
cp_info |
constant_pool[constant_pool_count-1] |
常量池信息 |
u2 |
access_flags |
访问标记 |
u2 |
this_class |
当前类 |
u2 |
super_class |
父类 |
u2 |
interfaces_count |
接口数量 |
u2 |
interface[interface_count] |
接口集合 |
u2 |
fields_count |
属性数量 |
field_info |
fields[fields_count] |
属性字段信息 |
u2 |
methods_count |
方法数量 |
method_info |
methods[methods_count] |
方法信息 |
u2 |
attributes_count |
属性数量(元属性) |
attribute_info |
attributes[attribtes_counnt] |
属性信息(元属性) |
阅读Class文件
对应上面Class文件格式,以及常量池、属性和方法,边查文档边对比阅读即可
常量池
常量池中每个常量对应如下结构:
数据类型 | 数据项 | 描述 |
---|---|---|
u1 |
tag |
常量标记可从这里查到 |
u1 |
info[] |
常量信息,对应tag 标记 |
属性
每个属性字段对应如下结构
数据类型 | 数据项 | 描述 |
---|---|---|
u2 |
access_flags |
字段修饰符可从这里查到 |
u2 |
name_index |
字段名索引 |
u2 |
descriptor_index |
描述索引 |
u2 |
attributes_count |
属性数量(元属性) |
attribute_info |
attributes[attributes_count] |
属性信息(元属性) |
方法
方法中出来操作指令外,也有预定义的attributes
属性,具体有如下几个
- stack:方法执行时,操作栈的深度
- locals:局部辨变量所需空间,单位是
slot
,虚拟机分配最小内存空间 - args_size:参数个数,实例方法默认第一个会传递this参数
- LineNumberTable:行号
代码
java代码如下
public class Hello{
private static String hello = "hello";
public static void main(String[] args){
System.out.println(hello + "world");
}
}
原始字节码文件如下:
Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000: CA FE BA BE 00 00 00 3B 00 34 0A 00 02 00 03 07 J~:>...;.4......
00000010: 00 04 0C 00 05 00 06 01 00 10 6A 61 76 61 2F 6C ..........java/l
00000020: 61 6E 67 2F 4F 62 6A 65 63 74 01 00 06 3C 69 6E ang/Object...<in
00000030: 69 74 3E 01 00 03 28 29 56 09 00 08 00 09 07 00 it>...()V.......
00000040: 0A 0C 00 0B 00 0C 01 00 10 6A 61 76 61 2F 6C 61 .........java/la
00000050: 6E 67 2F 53 79 73 74 65 6D 01 00 03 6F 75 74 01 ng/System...out.
00000060: 00 15 4C 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 ..Ljava/io/Print
00000070: 53 74 72 65 61 6D 3B 09 00 0E 00 0F 07 00 10 0C Stream;.........
00000080: 00 11 00 12 01 00 05 48 65 6C 6C 6F 01 00 05 68 .......Hello...h
00000090: 65 6C 6C 6F 01 00 12 4C 6A 61 76 61 2F 6C 61 6E ello...Ljava/lan
000000a0: 67 2F 53 74 72 69 6E 67 3B 12 00 00 00 14 0C 00 g/String;.......
000000b0: 15 00 16 01 00 17 6D 61 6B 65 43 6F 6E 63 61 74 ......makeConcat
000000c0: 57 69 74 68 43 6F 6E 73 74 61 6E 74 73 01 00 26 WithConstants..&
000000d0: 28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 (Ljava/lang/Stri
000000e0: 6E 67 3B 29 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 ng;)Ljava/lang/S
000000f0: 74 72 69 6E 67 3B 0A 00 18 00 19 07 00 1A 0C 00 tring;..........
00000100: 1B 00 1C 01 00 13 6A 61 76 61 2F 69 6F 2F 50 72 ......java/io/Pr
00000110: 69 6E 74 53 74 72 65 61 6D 01 00 07 70 72 69 6E intStream...prin
00000120: 74 6C 6E 01 00 15 28 4C 6A 61 76 61 2F 6C 61 6E tln...(Ljava/lan
00000130: 67 2F 53 74 72 69 6E 67 3B 29 56 08 00 11 01 00 g/String;)V.....
00000140: 04 43 6F 64 65 01 00 0F 4C 69 6E 65 4E 75 6D 62 .Code...LineNumb
00000150: 65 72 54 61 62 6C 65 01 00 04 6D 61 69 6E 01 00 erTable...main..
00000160: 16 28 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 .([Ljava/lang/St
00000170: 72 69 6E 67 3B 29 56 01 00 08 3C 63 6C 69 6E 69 ring;)V...<clini
00000180: 74 3E 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 t>...SourceFile.
00000190: 00 0A 48 65 6C 6C 6F 2E 6A 61 76 61 01 00 10 42 ..Hello.java...B
000001a0: 6F 6F 74 73 74 72 61 70 4D 65 74 68 6F 64 73 0F ootstrapMethods.
000001b0: 06 00 27 0A 00 28 00 29 07 00 2A 0C 00 15 00 2B ..'..(.)..*....+
000001c0: 01 00 24 6A 61 76 61 2F 6C 61 6E 67 2F 69 6E 76 ..$java/lang/inv
000001d0: 6F 6B 65 2F 53 74 72 69 6E 67 43 6F 6E 63 61 74 oke/StringConcat
000001e0: 46 61 63 74 6F 72 79 01 00 98 28 4C 6A 61 76 61 Factory...(Ljava
000001f0: 2F 6C 61 6E 67 2F 69 6E 76 6F 6B 65 2F 4D 65 74 /lang/invoke/Met
00000200: 68 6F 64 48 61 6E 64 6C 65 73 24 4C 6F 6F 6B 75 hodHandles$Looku
00000210: 70 3B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 p;Ljava/lang/Str
00000220: 69 6E 67 3B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 69 ing;Ljava/lang/i
00000230: 6E 76 6F 6B 65 2F 4D 65 74 68 6F 64 54 79 70 65 nvoke/MethodType
00000240: 3B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 ;Ljava/lang/Stri
00000250: 6E 67 3B 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 4F ng;[Ljava/lang/O
00000260: 62 6A 65 63 74 3B 29 4C 6A 61 76 61 2F 6C 61 6E bject;)Ljava/lan
00000270: 67 2F 69 6E 76 6F 6B 65 2F 43 61 6C 6C 53 69 74 g/invoke/CallSit
00000280: 65 3B 08 00 2D 01 00 06 01 77 6F 72 6C 64 01 00 e;..-....world..
00000290: 0C 49 6E 6E 65 72 43 6C 61 73 73 65 73 07 00 30 .InnerClasses..0
000002a0: 01 00 25 6A 61 76 61 2F 6C 61 6E 67 2F 69 6E 76 ..%java/lang/inv
000002b0: 6F 6B 65 2F 4D 65 74 68 6F 64 48 61 6E 64 6C 65 oke/MethodHandle
000002c0: 73 24 4C 6F 6F 6B 75 70 07 00 32 01 00 1E 6A 61 s$Lookup..2...ja
000002d0: 76 61 2F 6C 61 6E 67 2F 69 6E 76 6F 6B 65 2F 4D va/lang/invoke/M
000002e0: 65 74 68 6F 64 48 61 6E 64 6C 65 73 01 00 06 4C ethodHandles...L
000002f0: 6F 6F 6B 75 70 00 21 00 0E 00 02 00 00 00 01 00 ookup.!.........
00000300: 0A 00 11 00 12 00 00 00 03 00 01 00 05 00 06 00 ................
00000310: 01 00 1E 00 00 00 1D 00 01 00 01 00 00 00 05 2A ...............*
00000320: B7 00 01 B1 00 00 00 01 00 1F 00 00 00 06 00 01 7..1............
00000330: 00 00 00 01 00 09 00 20 00 21 00 01 00 1E 00 00 .........!......
00000340: 00 2B 00 02 00 01 00 00 00 0F B2 00 07 B2 00 0D .+........2..2..
00000350: BA 00 13 00 00 B6 00 17 B1 00 00 00 01 00 1F 00 :....6..1.......
00000360: 00 00 0A 00 02 00 00 00 04 00 0E 00 05 00 08 00 ................
00000370: 22 00 06 00 01 00 1E 00 00 00 1E 00 01 00 00 00 "...............
00000380: 00 00 06 12 1D B3 00 0D B1 00 00 00 01 00 1F 00 .....3..1.......
00000390: 00 00 06 00 01 00 00 00 02 00 03 00 23 00 00 00 ............#...
000003a0: 02 00 24 00 25 00 00 00 08 00 01 00 26 00 01 00 ..$.%.......&...
000003b0: 2C 00 2E 00 00 00 0A 00 01 00 2F 00 31 00 33 00 ,........./.1.3.
000003c0: 19 .
javap
反编译内容如下
Last modified 1970年1月1日; size 961 bytes
SHA-256 checksum 4c2c5625f17ff3294ff3375e7601f0e1a381196f1d66485595b4886191ac1041
Compiled from "Hello.java"
public class Hello
minor version: 0
major version: 59
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #14 // Hello
super_class: #2 // java/lang/Object
interfaces: 0, fields: 1, methods: 3, attributes: 3
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Fieldref #8.#9 // java/lang/System.out:Ljava/io/PrintStream;
#8 = Class #10 // java/lang/System
#9 = NameAndType #11:#12 // out:Ljava/io/PrintStream;
#10 = Utf8 java/lang/System
#11 = Utf8 out
#12 = Utf8 Ljava/io/PrintStream;
#13 = Fieldref #14.#15 // Hello.hello:Ljava/lang/String;
#14 = Class #16 // Hello
#15 = NameAndType #17:#18 // hello:Ljava/lang/String;
#16 = Utf8 Hello
#17 = Utf8 hello
#18 = Utf8 Ljava/lang/String;
#19 = InvokeDynamic #0:#20 // #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
#20 = NameAndType #21:#22 // makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
#21 = Utf8 makeConcatWithConstants
#22 = Utf8 (Ljava/lang/String;)Ljava/lang/String;
#23 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V
#24 = Class #26 // java/io/PrintStream
#25 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#26 = Utf8 java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
#29 = String #17 // hello
#30 = Utf8 Code
#31 = Utf8 LineNumberTable
#32 = Utf8 main
#33 = Utf8 ([Ljava/lang/String;)V
#34 = Utf8 <clinit>
#35 = Utf8 SourceFile
#36 = Utf8 Hello.java
#37 = Utf8 BootstrapMethods
#38 = MethodHandle 6:#39 // REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
#39 = Methodref #40.#41 // java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
#40 = Class #42 // java/lang/invoke/StringConcatFactory
#41 = NameAndType #21:#43 // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
#42 = Utf8 java/lang/invoke/StringConcatFactory
#43 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
#44 = String #45 // \u0001world
#45 = Utf8 \u0001world
#46 = Utf8 InnerClasses
#47 = Class #48 // java/lang/invoke/MethodHandles$Lookup
#48 = Utf8 java/lang/invoke/MethodHandles$Lookup
#49 = Class #50 // java/lang/invoke/MethodHandles
#50 = Utf8 java/lang/invoke/MethodHandles
#51 = Utf8 Lookup
{
public Hello();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: getstatic #13 // Field hello:Ljava/lang/String;
6: invokedynamic #19, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
11: invokevirtual #23 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
14: return
LineNumberTable:
line 4: 0
line 5: 14
static {};
descriptor: ()V
flags: (0x0008) ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: ldc #29 // String hello
2: putstatic #13 // Field hello:Ljava/lang/String;
5: return
LineNumberTable:
line 2: 0
}
SourceFile: "Hello.java"
BootstrapMethods:
0: #38 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
Method arguments:
#44 \u0001world
InnerClasses:
public static final #51= #47 of #49; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
ASM开发
ASM是一个Java字节码操作框架,能动态生成类或增强已有类的功能,ASM可直接生成Class文件,也可以在类被加载之前动态改变类的行为,cglib、spring都直接或间接依赖ASM操作字节码
在pom.xml
文件中添加下面两个依赖
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>9.0</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.0</version>
</dependency>
ASM编程模型
- Core API:提供基于事件形式的编程模型,无需一次性将整个类的结构读取到内存,因此该方式快,内存消耗少,但是编程难度大
- Tree API:提供基于属性的编程模型,需要一次性将类完整结构读取到内存中,因此该方式慢,内存消耗大,但是编程难度小
CoreAPI
基于ClassVisitor
接口的三个实现类,来操作Class文件的生成与转化
ClassReader
:解析一个类的Class文件ClassAdapter
:实现变化的功能ClassWriter
:输出变化后的字节码
由于CoreAPI编程难度较大,ASM提供ASMifier工具来帮助开发,可使用该工具生成的ASM结构化代码对比修改,使用方式如下
import com.Service;
import org.objectweb.asm.util.ASMifier;
public class Main {
public static void main(final String[] args) {
try {
ASMifier.main(new String[]{Service.class.getName()}); //调用main方法,传递参数为要分析的类的全类名,即可生成结构化代码
} catch (Exception e) {
e.printStackTrace();
}
}
}
基于ASM的AOP实现
计算某个方法的执行时间的日志输出类
public class MyTimeLogger {
private static long start = 0;
public static void start() {
start = System.currentTimeMillis();
}
public static void end() {
long end = System.currentTimeMillis();
System.out.println("runtime is " + (end - start));
}
}
使用ASMifier工具对比调用该日志类输出多啦以下两处内容
//...
methodVisitor.visitMethodInsn(INVOKESTATIC, "MyTimeLogger", "start", "()V", false);
//...
methodVisitor.visitMethodInsn(INVOKESTATIC, "MyTimeLogger", "end", "()V", false);
//...
编写修改Class文件的类
import org.objectweb.asm.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class MyClassVisitor extends ClassVisitor { //继承ClassVisitor类,复写访问方法
static class MyMethodVisitor extends MethodVisitor { //继承MethodVisitor类,复写访问方法
MyMethodVisitor(MethodVisitor methodVisitor) {
super(Opcodes.ASM9, methodVisitor); //构造方法中指定ASM版本
}
@Override
public void visitCode() { //执行方法前
mv.visitCode();
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "MyTimeLogger", "start", "()V", false); //增加ASMifier工具生成的代码
}
@Override
public void visitInsn(int opcode) { //执行方法后
if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN || opcode == Opcodes.ATHROW) {
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "MyTimeLogger", "end", "()V", false); //增加ASMifier工具生成的代码
}
mv.visitInsn(opcode);
}
}
private MyClassVisitor(ClassVisitor classVisitor) {
super(Opcodes.ASM9, classVisitor); //构造方法中指定ASM版本
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
if (!"<init>".equals(name) && mv != null) {
mv = new MyMethodVisitor(mv); //替换为自己增强的方法访问并返回
}
return mv;
}
static void generate() { //生成修改后的Class文件
ClassReader cr;
byte[] data;
FileOutputStream fos = null;
try {
cr = new ClassReader("Service"); //指定读取的类路径,使用反斜杠分隔
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); //输出页最大
ClassVisitor cv = new MyClassVisitor(cw);
cr.accept(cv, ClassReader.SKIP_DEBUG); //跳过调试
data = cw.toByteArray(); //获取返回的字节数组
File f = new File("target/classes/Service.class"); //输出到Class文件中
fos = new FileOutputStream(f); //获取输出流
fos.write(data); //输入字节数组
System.out.println("修改class文件成功");
} catch (IOException e) {
e.printStackTrace();
} finally { //关闭输出流
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
使用下面代码进行测试
public class Main {
public static void main(String[] args) {
MyClassVisitor.generate(); //生成Class文件
new Service().service(); //调用业务方法
}
}
Comments NOTHING