学习动态代理前先看看-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

相关文章

vue基于ElementUI动态设置表格高度的3种方法

《vue基于ElementUI动态设置表格高度的3种方法》ElementUI+vue动态设置表格高度的几种方法,抛砖引玉,还有其它方法动态设置表格高度,大家可以开动脑筋... 方法一、css + js的形式这个方法需要在表格外层设置一个div,原理是将表格的高度设置成外层div的高度,所以外层的div需要

SpringBoot实现动态插拔的AOP的完整案例

《SpringBoot实现动态插拔的AOP的完整案例》在现代软件开发中,面向切面编程(AOP)是一种非常重要的技术,能够有效实现日志记录、安全控制、性能监控等横切关注点的分离,在传统的AOP实现中,切... 目录引言一、AOP 概述1.1 什么是 AOP1.2 AOP 的典型应用场景1.3 为什么需要动态插

Go语言使用Buffer实现高性能处理字节和字符

《Go语言使用Buffer实现高性能处理字节和字符》在Go中,bytes.Buffer是一个非常高效的类型,用于处理字节数据的读写操作,本文将详细介绍一下如何使用Buffer实现高性能处理字节和... 目录1. bytes.Buffer 的基本用法1.1. 创建和初始化 Buffer1.2. 使用 Writ

VUE动态绑定class类的三种常用方式及适用场景详解

《VUE动态绑定class类的三种常用方式及适用场景详解》文章介绍了在实际开发中动态绑定class的三种常见情况及其解决方案,包括根据不同的返回值渲染不同的class样式、给模块添加基础样式以及根据设... 目录前言1.动态选择class样式(对象添加:情景一)2.动态添加一个class样式(字符串添加:情

SpringCloud配置动态更新原理解析

《SpringCloud配置动态更新原理解析》在微服务架构的浩瀚星海中,服务配置的动态更新如同魔法一般,能够让应用在不重启的情况下,实时响应配置的变更,SpringCloud作为微服务架构中的佼佼者,... 目录一、SpringBoot、Cloud配置的读取二、SpringCloud配置动态刷新三、更新@R

如何用Python绘制简易动态圣诞树

《如何用Python绘制简易动态圣诞树》这篇文章主要给大家介绍了关于如何用Python绘制简易动态圣诞树,文中讲解了如何通过编写代码来实现特定的效果,包括代码的编写技巧和效果的展示,需要的朋友可以参考... 目录代码:效果:总结 代码:import randomimport timefrom math

Java中JSON字符串反序列化(动态泛型)

《Java中JSON字符串反序列化(动态泛型)》文章讨论了在定时任务中使用反射调用目标对象时处理动态参数的问题,通过将方法参数存储为JSON字符串并进行反序列化,可以实现动态调用,然而,这种方式容易导... 需求:定时任务扫描,反射调用目标对象,但是,方法的传参不是固定的。方案一:将方法参数存成jsON字

.NET利用C#字节流动态操作Excel文件

《.NET利用C#字节流动态操作Excel文件》在.NET开发中,通过字节流动态操作Excel文件提供了一种高效且灵活的方式处理数据,本文将演示如何在.NET平台使用C#通过字节流创建,读取,编辑及保... 目录用C#创建并保存Excel工作簿为字节流用C#通过字节流直接读取Excel文件数据用C#通过字节

提示:Decompiled.class file,bytecode version如何解决

《提示:Decompiled.classfile,bytecodeversion如何解决》在处理Decompiled.classfile和bytecodeversion问题时,通过修改Maven配... 目录问题原因总结问题1、提示:Decompiled .class file,China编程 bytecode

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

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