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

相关文章

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

Java调用C#动态库的三种方法详解

《Java调用C#动态库的三种方法详解》在这个多语言编程的时代,Java和C#就像两位才华横溢的舞者,各自在不同的舞台上展现着独特的魅力,然而,当它们携手合作时,又会碰撞出怎样绚丽的火花呢?今天,我们... 目录方法1:C++/CLI搭建桥梁——Java ↔ C# 的“翻译官”步骤1:创建C#类库(.NET

MyBatis编写嵌套子查询的动态SQL实践详解

《MyBatis编写嵌套子查询的动态SQL实践详解》在Java生态中,MyBatis作为一款优秀的ORM框架,广泛应用于数据库操作,本文将深入探讨如何在MyBatis中编写嵌套子查询的动态SQL,并结... 目录一、Myhttp://www.chinasem.cnBATis动态SQL的核心优势1. 灵活性与可

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

Mybatis嵌套子查询动态SQL编写实践

《Mybatis嵌套子查询动态SQL编写实践》:本文主要介绍Mybatis嵌套子查询动态SQL编写方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、实体类1、主类2、子类二、Mapper三、XML四、详解总结前言MyBATis的xml文件编写动态SQL

SpringBoot实现Kafka动态反序列化的完整代码

《SpringBoot实现Kafka动态反序列化的完整代码》在分布式系统中,Kafka作为高吞吐量的消息队列,常常需要处理来自不同主题(Topic)的异构数据,不同的业务场景可能要求对同一消费者组内的... 目录引言一、问题背景1.1 动态反序列化的需求1.2 常见问题二、动态反序列化的核心方案2.1 ht

golang实现动态路由的项目实践

《golang实现动态路由的项目实践》本文主要介绍了golang实现动态路由项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习... 目录一、动态路由1.结构体(数据库的定义)2.预加载preload3.添加关联的方法一、动态路由1

重新对Java的类加载器的学习方式

《重新对Java的类加载器的学习方式》:本文主要介绍重新对Java的类加载器的学习方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍1.1、简介1.2、符号引用和直接引用1、符号引用2、直接引用3、符号转直接的过程2、加载流程3、类加载的分类3.1、显示

Python Selenium动态渲染页面和抓取的使用指南

《PythonSelenium动态渲染页面和抓取的使用指南》在Web数据采集领域,动态渲染页面已成为现代网站的主流形式,本文将从技术原理,环境配置,核心功能系统讲解Selenium在Python动态... 目录一、Selenium技术架构解析二、环境搭建与基础配置1. 组件安装2. 驱动配置3. 基础操作模

Java实现按字节长度截取字符串

《Java实现按字节长度截取字符串》在Java中,由于字符串可能包含多字节字符,直接按字节长度截取可能会导致乱码或截取不准确的问题,下面我们就来看看几种按字节长度截取字符串的方法吧... 目录方法一:使用String的getBytes方法方法二:指定字符编码处理方法三:更精确的字符编码处理使用示例注意事项方