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

相关文章

MySQL中动态生成SQL语句去掉所有字段的空格的操作方法

《MySQL中动态生成SQL语句去掉所有字段的空格的操作方法》在数据库管理过程中,我们常常会遇到需要对表中字段进行清洗和整理的情况,本文将详细介绍如何在MySQL中动态生成SQL语句来去掉所有字段的空... 目录在mysql中动态生成SQL语句去掉所有字段的空格准备工作原理分析动态生成SQL语句在MySQL

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

C#如何动态创建Label,及动态label事件

《C#如何动态创建Label,及动态label事件》:本文主要介绍C#如何动态创建Label,及动态label事件,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#如何动态创建Label,及动态label事件第一点:switch中的生成我们的label事件接着,

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

MyBatis 动态 SQL 优化之标签的实战与技巧(常见用法)

《MyBatis动态SQL优化之标签的实战与技巧(常见用法)》本文通过详细的示例和实际应用场景,介绍了如何有效利用这些标签来优化MyBatis配置,提升开发效率,确保SQL的高效执行和安全性,感... 目录动态SQL详解一、动态SQL的核心概念1.1 什么是动态SQL?1.2 动态SQL的优点1.3 动态S

Oracle存储过程里操作BLOB的字节数据的办法

《Oracle存储过程里操作BLOB的字节数据的办法》该篇文章介绍了如何在Oracle存储过程中操作BLOB的字节数据,作者研究了如何获取BLOB的字节长度、如何使用DBMS_LOB包进行BLOB操作... 目录一、缘由二、办法2.1 基本操作2.2 DBMS_LOB包2.3 字节级操作与RAW数据类型2.

mybatis-plus 实现查询表名动态修改的示例代码

《mybatis-plus实现查询表名动态修改的示例代码》通过MyBatis-Plus实现表名的动态替换,根据配置或入参选择不同的表,本文主要介绍了mybatis-plus实现查询表名动态修改的示... 目录实现数据库初始化依赖包配置读取类设置 myBATis-plus 插件测试通过 mybatis-plu

Java进阶学习之如何开启远程调式

《Java进阶学习之如何开启远程调式》Java开发中的远程调试是一项至关重要的技能,特别是在处理生产环境的问题或者协作开发时,:本文主要介绍Java进阶学习之如何开启远程调式的相关资料,需要的朋友... 目录概述Java远程调试的开启与底层原理开启Java远程调试底层原理JVM参数总结&nbsMbKKXJx

基于Canvas的Html5多时区动态时钟实战代码

《基于Canvas的Html5多时区动态时钟实战代码》:本文主要介绍了如何使用Canvas在HTML5上实现一个多时区动态时钟的web展示,通过Canvas的API,可以绘制出6个不同城市的时钟,并且这些时钟可以动态转动,每个时钟上都会标注出对应的24小时制时间,详细内容请阅读本文,希望能对你有所帮助...