学习动态代理前先看看

编程入门 行业动态 更新时间:2024-10-07 14:24:31

学习动态代理<a href=https://www.elefans.com/category/jswz/34/1756753.html style=前先看看"/>

学习动态代理前先看看

关键词:动态代理    字节码

 


我们利用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;

更多推荐

学习动态代理前先看看

本文发布于:2024-02-06 13:41:06,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1749397.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:前先   动态

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!