小咖(Java)有一天陪女朋友(Object)去逛街,逛吃逛吃...遇见了科特林(Kotlin)。打起了招呼,"嗨,科特林好久不见,最近怎么样呢?"
科特林:"也就那样,还在安卓拧螺丝。你呢,这你女朋友吧,我怎么看着眼熟。"
小咖:"我也那样,离开安卓换了个工厂拧螺丝。没事,一起逛逛"。
此时,小咖的女朋友(Object)使劲的掐了小咖,然后道:"小咖,我朋友找我了,我先回去了。"
....
"喂,小鸥哇。你在哪呀?我刚去找你,你不在家"。小咖打着电话问道。
"你管我在哪,你好好陪你朋友,不需要我陪着,反正你也不懂我,哼~"。小鸥直接挂掉电话。
小咖然后重新拨打,话筒里传来"您拨打的电话,已经关机,请稍后再拨。Sorry, the number you dailed"。
小咖很懵圈,不明白自己的女朋友为什么生气,自己明明很了解她,姓甚名谁,身高体重,家庭成员,爱好..都很清楚。
小咖不能就此放弃,根据小鸥喜好开始推测在哪,去找小鸥好好哄哄..
言归正传:
一个Java经过编译,生成字节码文件(.class)。经过类加载器加载后,程序可以通过反射获取类所有的信息,就像小咖知道女朋友所有的信息,就可以想办法哄好女朋友。
Java程序使用过程中,通过反射获取类所有的信息,就可以去实例化,增强,切面编程(AOP)等。
此处,我们思考一个问题:
为什么反射能获取类所有的信息?
在解答问题前,先看 程序=数据结构+算法 这个公式,作为程序员开发的Java 代码(当然也可以是Kotlin等)先看做是原始数据,经过编译(词法分析、语法分析、语义分析、代码生成)生成字节码文件是加工后的数据。既然是数据就需数据结构承载数据,算法是数据进行运算的处理过程暂时不管。那么,需要思考什么结构可以承载字节码信息。
如果只是单纯地去思考结构就没意义了,所以还需思考字节码有哪些信息。
通过抽丝剥茧,核心问题是字节码有哪些信息。因为我们不?是重新设计,最简单解决方式?是编译生成一个class,然后看class。
来,上菜,不对上代码:
/**
* 字节码有什么信息呢
*/
public class HelloByteCode {
/**定义个私有属性**/
private int a;
/**定义个私有属性**/
private static int aStatic;
/**定义个私有属性**/
private final static int aFinalStatic = 1;
public HelloByteCode() {
}
public void testMethod() {
}
public void testMethod(int a) {
this.a = a;
}
public static void main(String[] args) {
}
}
打开命令行执行:
javac -encoding utf-8 HelloByteCode.java
可以看到生成HelloByteCode.class文件,该文件记录了什么信息呢?
NodePad打开是什么样呢?
应该是乱码,一探究竟:
(O_o)??,不认识,果然是乱码。那么怎么打开呢?
别着急,我们可以用下面的命令查看
javap -verbose HelloByteCode.class
结果如下:
Classfile /HelloByteCode.class
Last modified 2022-10-19; size 478 bytes
MD5 checksum dc8978d076ad0c0217b845692932bc77
Compiled from "HelloByteCode.java"
public class HelloByteCode
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#21 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#22 // HelloByteCode.a:I
#3 = Class #23 // HelloByteCode
#4 = Class #24 // java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8 aStatic
#8 = Utf8 aFinalStatic
#9 = Utf8 ConstantValue
#10 = Integer 1
#11 = Utf8 <init>
#12 = Utf8 ()V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 testMethod
#16 = Utf8 (I)V
#17 = Utf8 main
#18 = Utf8 ([Ljava/lang/String;)V
#19 = Utf8 SourceFile
#20 = Utf8 HelloByteCode.java
#21 = NameAndType #11:#12 // "<init>":()V
#22 = NameAndType #5:#6 // a:I
#23 = Utf8 HelloByteCode
#24 = Utf8 java/lang/Object
{
public HelloByteCode();
descriptor: ()V
flags: 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 17: 0
line 19: 4
public void testMethod();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 23: 0
public void testMethod(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #2 // Field a:I
5: return
LineNumberTable:
line 26: 0
line 27: 5
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 31: 0
}
SourceFile: "HelloByteCode.java"
看上面的内容,可以看到一些熟悉的内容,定义变量名?,方法名,return,Integer,main方法?和常量值1,其他的信息就有些陌生了。不过这信息还是给我们看的,计算机看到的应该是01编码,因此通过编辑器打开16进制的信息如下:
可以通过:vi HelloByteCode.class
进入交互模式(通过:):%!xxd
如果您用的是windows,可以通过Git Bash查看
00000000: cafe babe 0000 0034 0019 0a00 0400 1509 .......4........
00000010: 0003 0016 0700 1707 0018 0100 0161 0100 .............a..
00000020: 0149 0100 0761 5374 6174 6963 0100 0c61 .I...aStatic...a
00000030: 4669 6e61 6c53 7461 7469 6301 000d 436f FinalStatic...Co
00000040: 6e73 7461 6e74 5661 6c75 6503 0000 0001 nstantValue.....
00000050: 0100 063c 696e 6974 3e01 0003 2829 5601 ...<init>...()V.
00000060: 0004 436f 6465 0100 0f4c 696e 654e 756d ..Code...LineNum
00000070: 6265 7254 6162 6c65 0100 0a74 6573 744d berTable...testM
00000080: 6574 686f 6401 0004 2849 2956 0100 046d ethod...(I)V...m
00000090: 6169 6e01 0016 285b 4c6a 6176 612f 6c61 ain...([Ljava/la
000000a0: 6e67 2f53 7472 696e 673b 2956 0100 0a53 ng/String;)V...S
000000b0: 6f75 7263 6546 696c 6501 0012 4865 6c6c ourceFile...Hell
000000c0: 6f42 7974 6543 6f64 652e 6a61 7661 0c00 oByteCode.java..
000000d0: 0b00 0c0c 0005 0006 0100 0d48 656c 6c6f ...........Hello
000000e0: 4279 7465 436f 6465 0100 106a 6176 612f ByteCode...java/
000000f0: 6c61 6e67 2f4f 626a 6563 7400 2100 0300 lang/Object.!...
00000100: 0400 0000 0300 0200 0500 0600 0000 0a00 ................
00000110: 0700 0600 0000 1a00 0800 0600 0100 0900 ................
00000120: 0000 0200 0a00 0400 0100 0b00 0c00 0100 ................
00000130: 0d00 0000 2100 0100 0100 0000 052a b700 ....!........*..
00000140: 01b1 0000 0001 000e 0000 000a 0002 0000 ................
00000150: 0011 0004 0013 0001 000f 000c 0001 000d ................
00000160: 0000 0019 0000 0001 0000 0001 b100 0000 ................
00000170: 0100 0e00 0000 0600 0100 0000 1700 0100 ................
00000180: 0f00 1000 0100 0d00 0000 2200 0200 0200 ..........".....
00000190: 0000 062a 1bb5 0002 b100 0000 0100 0e00 ...*............
000001a0: 0000 0a00 0200 0000 1a00 0500 1b00 0900 ................
000001b0: 1100 1200 0100 0d00 0000 1900 0000 0100 ................
000001c0: 0000 01b1 0000 0001 000e 0000 0006 0001 ................
000001d0: 0000 001f 0001 0013 0000 0002 0014 0a ...............
第一行,看到cafe babe 这个我们应该熟系一个故事Java命名的来历。这个表示该文件是Class文件,基于安全方面的考虑,判断一个文件是什么文件,不能简单的通过后缀名判断(后缀名可改),应该通过魔数(Magic Number)。
字节码规定 Class 文件开头的 4 个字节的无符号整数称为魔数,它的唯一作用是确定这个文件是否为一个能被虚拟机接受的有效合法的 Class 文件。魔数值固定为 0xCAFEBABE,不会改变。一个 Class 文件不以 0xCAFEBABE 开头,虚拟机在进行文件校验的时候就会直接抛出以下错误:
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 1685430635 in class file HelloByteCodeTest
其他信息,暂时看不出来,到此发现了字节码真实的数据。
接下来我们就来分析字节码,来看字节码怎么记录我们的程序,请看下回分解。
关注点赞转发三重操作,防止走丢(^U^)ノ~YO
本文暂时没有评论,来添加一个吧(●'◡'●)