学习动态代理前先看看-class字节码

2023-11-23 16:59

本文主要是介绍学习动态代理前先看看-class字节码,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

关键词:动态代理    字节码

 


我们利用java进行开发的步骤一般是:①编写类,生成.java文件。②通过javac命令将.java文件内容编译成字节码,生成.class文件。③JVM加载.class文件内容,根据内容获取类的信息完成装配。从这三个步骤可以发现,对于①②步骤的内容都是在JVM启动之前就需要完成,这样的结果就是在JVM运行期间无法去对内容进行修改。而动态代理不一样,对于①②的步骤不需要在JVM启动之前提前完成,它完全可以在JVM运行期间动态的在内存中编写class文件的内容。此时,动态代理就变成了两个步骤:①在运行时的内存中动态生成.class字节码内容。②加载生成的内容根据内容获取类的信息完成装配。

对于动态代理的步骤①,你如果需要编写一个.class文件内容,前提是你得需要了解.class文件的内容,了解它内容的规则,然后依照规则完成内容的编写。

其实如果你了解了.class文件的字节码内容以及其的规则,完全可以跳过先书写.java内容再编译的过程,直接完成字节码的生成。JVM需要的是.class的字节码内容,对于.java文件,可以理解成是给程序员使用的,直接编写字节码文件内容是很麻烦的,哪有.java文件省事啊。程序员写.java文件,jdk将其编译解释成.class中字节码内容,JVM能够识别字节码的内容。就好比领导和老外交流,领导完全说中文,不用担心老外能不能理解,因为会有翻译人员将中文翻译成老外能听懂的话。

一般我们在编写.java文件的时候,都会使用相关的库,库中提供了很多api供我们使用。其实字节码也有相关的api的,学会了字节码的api,你可以直接跳过.java,直接编写.class文件。

字节码指令举例,这些指令就可以理解成源码中的api:

关于动态代理的功能与原理,请戳《JDK动态代理》《CGLIB动态代理》。


█ 操作

①编写一个类

public class Hello {public void sayHello() {System.out.println("hello...");}}

②通过javac编译Hello.java,生成Hello.class文件,用文本编辑器查看Hello.class内容(不同的文本编辑器查看Hello.class的内容可能会有不一样的效果,建议使用Sublime打开得到下图中的内容):

从图片可以看出,内容是16进制的。

③将上图中的内容复制出来,编写测试类

注意点:内容复制到测试类中后,要去掉上图内容中的所有空格和换行标记

public class MyTest {public static void main(String[] args) {String classInfo = "cafebabe00000034001f0a0006001109001200130800140a001500160700170700180100063c696e69743e010003282956010004436f646501000f4c696e654e" +"756d6265725461626c650100124c6f63" +"616c5661726961626c655461626c6501" +"0004746869730100074c48656c6c6f3b" +"01000873617948656c6c6f01000a536f" +"7572636546696c6501000a48656c6c6f" +"2e6a6176610c000700080700190c001a" +"001b01000868656c6c6f2e2e2e07001c" +"0c001d001e01000548656c6c6f010010" +"6a6176612f6c616e672f4f626a656374" +"0100106a6176612f6c616e672f537973" +"74656d0100036f75740100154c6a6176" +"612f696f2f5072696e7453747265616d" +"3b0100136a6176612f696f2f5072696e" +"7453747265616d0100077072696e746c" +"6e010015284c6a6176612f6c616e672f" +"537472696e673b295600210005000600" +"00000000020001000700080001000900" +"00002f00010001000000052ab70001b1" +"00000002000a00000006000100000006" +"000b0000000c000100000005000c000d" +"00000001000e00080001000900000037" +"0002000100000009b200021203b60004" +"b100000002000a0000000a0002000000" +"090008000a000b0000000c0001000000" +"09000c000d00000001000f0000000200" +"10";try {byte[] bytes = parseHexStr2Byte(classInfo);// 通过net.sf.cglib.core.ReflectUtils获取Class对象Class helloClass = ReflectUtils.defineClass("Hello", bytes, MyTest.class.getClassLoader());Object instance = helloClass.newInstance();Hello hello = (Hello)instance;hello.sayHello();} catch (Exception e) {e.printStackTrace();}}/*** 十六进制转二进制* @param hexStr* @return*/public static byte[] parseHexStr2Byte(String hexStr) {if (hexStr.length() < 1) {return null;}byte[] result = new byte[hexStr.length() / 2];for (int i = 0; i < hexStr.length() / 2; i++) {int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2),16);result[i] = (byte) (high * 16 + low);}return result;}}

上图主要就是将文本中的十六进制内容转换成二进制内容,然后通过net.sf.cglib.core.ReflectUtils的defineClass方法根据二进制内容获取Class对象。defineClass有三个参数,第一个参数是类名,要与十六进制中的类名相同,例子中因为是Hello.class的内容,所以第一个参数必须是Hello,如果有包名,要加上包名。第二个参数就是二进制字节数组了。第三个参数是类加载器。最后通过反射实例化Class对象得到Hello对象。

④运行测试类,得到结果:

从上面的例子总结到,整个例子都没有去加载磁盘上的.class文件,而是通过一个字符串内容完成的。所以只要我们知道了一个类编译成class之后的字节码内容之后,是完全可以创建类的对象的。而对于这个字符串的内容,我们也可以编写逻辑实现适配的改变。这样就实现了动态代理类的生成啦。

下面来看看.class文件的内容都是些什么玩意吧:

cafe babe 0000 0034 001f 0a00 0600 1109
0012 0013 0800 140a 0015 0016 0700 1707
0018 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 124c 6f63
616c 5661 7269 6162 6c65 5461 626c 6501
0004 7468 6973 0100 074c 4865 6c6c 6f3b
0100 0873 6179 4865 6c6c 6f01 000a 536f
7572 6365 4669 6c65 0100 0a48 656c 6c6f
2e6a 6176 610c 0007 0008 0700 190c 001a
001b 0100 0868 656c 6c6f 2e2e 2e07 001c
0c00 1d00 1e01 0005 4865 6c6c 6f01 0010
6a61 7661 2f6c 616e 672f 4f62 6a65 6374
0100 106a 6176 612f 6c61 6e67 2f53 7973
7465 6d01 0003 6f75 7401 0015 4c6a 6176
612f 696f 2f50 7269 6e74 5374 7265 616d
3b01 0013 6a61 7661 2f69 6f2f 5072 696e
7453 7472 6561 6d01 0007 7072 696e 746c
6e01 0015 284c 6a61 7661 2f6c 616e 672f
5374 7269 6e67 3b29 5600 2100 0500 0600
0000 0000 0200 0100 0700 0800 0100 0900
0000 2f00 0100 0100 0000 052a b700 01b1
0000 0002 000a 0000 0006 0001 0000 0006
000b 0000 000c 0001 0000 0005 000c 000d
0000 0001 000e 0008 0001 0009 0000 0037
0002 0001 0000 0009 b200 0212 03b6 0004
b100 0000 0200 0a00 0000 0a00 0200 0000
0900 0800 0a00 0b00 0000 0c00 0100 0000
0900 0c00 0d00 0000 0100 0f00 0000 0200
10

看看上面的内容,一头雾水是吧。既然我们不知道这个内容都是啥意思,那我们反其道而行。既然上面的内容和Hello.java的内容是对应的,那我们从Hello.java中找到内容去和上面的内容对应。

①找到字符串"Hello"对应的16进制

byte[] bytes1 = "Hello".getBytes();
String str = parseByte2HexStr(bytes1);
System.out.println(str);
/*** 二进制转十六进制* @param buf* @return*/
public static String parseByte2HexStr(byte buf[]) {StringBuffer sb = new StringBuffer();for (int i = 0; i < buf.length; i++) {String hex = Integer.toHexString(buf[i] & 0xFF);if (hex.length() == 1) {hex = '0' + hex;}sb.append(hex.toUpperCase());}return sb.toString();
}

②输出结果为:48656C6C6F。同样的办法,获取"sayHello"的16进制,得到:73617948656C6C6F

③去上面的16进制内容中查找,找到:

④找到了Hello的位置,将Hello之前的内容拿出来,转换成二进制,再转换成字符串

String s = "cafebabe00000034001f0a0006001109001200130800140a001500160700170700180100063c696e69743e010003282956010004436f646501000f4c696e654e756d6265725461626c650100124c6f63616c5661726961626c655461626c65010004746869730100074c";
byte[] bytess = parseHexStr2Byte(s);
System.out.println(new String(bytess));

得到下图的内容,发现出现了很多特殊的内容,明显不是public class字符串,可见在编译的时候对一些关键字做了特殊处理还有添加了很多信息。

(关于字节码的详细内容,请搜索资料查看,这里不做讲解)

⑤关于cafebabe

字节码内容必须以此开头,用于标记。JDK动态代理中,生成代理对象的字节码时,有这样一段代码:

其中var14.writeInt(-889275714);就是写cafebabe的内容。-889275714是cafebabe的十进制表示。此值是怎么得来的呢?cafebabe是16进制,转换成十进制是3405691582。这个值超过了Int的范围,转成int类型就是-889275714。通过Long.valueOf(3405691582L).intValue();可以验证。

java源码中的表示和字节码中表示的不同,即.java的语法与.class的语法对应关系。举例:

JAVA描述

字节码描述

boolean

Z

char

C

byte

B

short

S

int

I

float

F

long

J

double

D

Object

Ljava/lang/Object;

int[]

[I

Object[][]

[[Ljava/lang/Object;

void m(int i, float f)

(IF)V

int m(Object o)

(Ljava/lang/Object;)I

int[] m(int i, String s)

(ILjava/lang/String;)[I

Object m(int[] i)

([I)Ljava/lang/Object;

这篇关于学习动态代理前先看看-class字节码的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/419409

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

字节面试 | 如何测试RocketMQ、RocketMQ?

字节面试:RocketMQ是怎么测试的呢? 答: 首先保证消息的消费正确、设计逆向用例,在验证消息内容为空等情况时的消费正确性; 推送大批量MQ,通过Admin控制台查看MQ消费的情况,是否出现消费假死、TPS是否正常等等问题。(上述都是临场发挥,但是RocketMQ真正的测试点,还真的需要探讨) 01 先了解RocketMQ 作为测试也是要简单了解RocketMQ。简单来说,就是一个分

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

高效+灵活,万博智云全球发布AWS无代理跨云容灾方案!

摘要 近日,万博智云推出了基于AWS的无代理跨云容灾解决方案,并与拉丁美洲,中东,亚洲的合作伙伴面向全球开展了联合发布。这一方案以AWS应用环境为基础,将HyperBDR平台的高效、灵活和成本效益优势与无代理功能相结合,为全球企业带来实现了更便捷、经济的数据保护。 一、全球联合发布 9月2日,万博智云CEO Michael Wong在线上平台发布AWS无代理跨云容灾解决方案的阐述视频,介绍了

动态规划---打家劫舍

题目: 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 思路: 动态规划五部曲: 1.确定dp数组及含义 dp数组是一维数组,dp[i]代表

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]