阅读完本文你能 get 到的知识点
很多同学估计会对这个词有点陌生,但随着你关注的博主越来越多,知道的也越来越多,马上这篇文章就带你走进 Javassist 的世界
Javassist 和 ASM 一样是操作字节码的框架, Javassist 诞生于 1999 年,多少有点年头
使用 Javassist 可以在运行时定义一个新类,可以在 JVM 加载类文件时修改类文件
而且 Javassist 提供不同类型的API: 源码级别 和 字节码级别
本文使用源码级别的 API ,所以你甚至可以在不懂字节码的前提下使用它,入手相对简单
但在性能上略逊于 ASM
话不多说,先来回顾一下我们平时是怎么使用 JDK 动态代理的
JDK 提供一个类 Proxy 用于生成代理类
调用类方法 newProxyInstance,传入的参数
InvocationHandler 是一个接口类,定义了调用方法
调用 Proxy.newProxyInstance 生成代理对象, 传入参数接口InvocationHandler实现类的对象处理代理的逻辑
在动手写代码之前,我们先花几分钟在脑海中设想一下我们需要生成的代理类是什么样子的?
这里先揭晓了
假设我们定义了一个接口类 LoginService
那么我们需要生成一个大概是这样的代理类
从上面生成的代理类入手,我们生成的类
继承了父类 MObject
实现了需要代理的接口 LoginService
生成了类成员变量 LoginService_0, 这个对应 接口的定义的方法
实现了需要代理的接口方法 login
还有带参数 MInvocationHandler 的构造方法
MObject 是生成的代理类需要继承的父类,它的作用是存储了 MInvocationHandler(处理程序接口)
同 JDK 自带的接口 InvocationHandler ,用于实现代理方法的处理逻辑
同 JDK 自带的类 Proxy
提供生成代理对象的方法 newProxyInstance
在经过代码设计之后,我们的脑海里应该有思路了,那就开始动手了
整个过程中比较重要的部分应该就是 MProxy 类了
在 MProxy 里面我们需要实现两大功能:
这一步相对简单,为了防止生成的代理类重名
这里拼接了所有需要代理的接口全限定类名,通过字符串 "_" 连接
首先我们需要根据新的类名生成一个空的类,注意类名不要重复了,不然会污染了原有的类
这里同样通过 ClassPool 的 get 方法获取到所有传入接口的 CtClass 定义
再调用 setInterfaces 方法给生成的类设置多个接口
因为我们调用 MInvocationHandler 的 invoke 方法时需要传入的第二个参数是被代理方法的 Method实例
所以将这个方法的存储到类成员变量中
CtField 代表着一个变量
传入类型、变量名 生成一个 CtField 实例
通过 setModifiers 方法设置变量的修饰符为 static + private
因为这里还要设置变量的值
调用 getFieldInitCode 生成初始化代码
为了获取 Method 对象 ,这里生成了反射的代码去获取
实例: Class.forName(类名).getMethod(方法名, Class<?>... 方法的参数类型);
生成了类成员变量之后,接下来该到实现接口的方法了
这里需要实现接口的方法
可以通过 CtNewMethod.copy 方法去拷贝需要实现的方法,不要直接使用原来的 CtMethod , 防止污染
拿到新的 CtMethod ,我们需要设置它的方法体、设置修饰符为 public
重点来看看怎么生成方法体代码
这里根据方法返回的类型调用不同的方法
那按顺序来看,不需要返回值的
那我们需要生成的代码是长这样的
super.h.invoke(this, 对应的类成员变量, new Object[]{方法参数});
这个有个语法需要知道: $0 代表这方法的第一个参数,懂字节码的应该知道非构造方法的第一个入参是 一个隐式的 this ,指向对象本身
new Object[]{} 里面就可以用 11 12 代表着方法的参数了
返回值是基本数据类型的,需要调用调用包装类型对应的拆箱方法 如
Boolean.parseBoolean()
所以和上面生成步骤的区别在于 前后生成了对于基本类型的 parse 代码
最后的返回其它类型的也比较简单
直接生成强转的代码 如 (String)
以上步骤走完,前期准备工作算是做完了,接下来就要根据生成的字节码来实例化对象了
要根据字节码来生成对象,第一步我们需要编写自定义的类加载器,通过类加载器加载字节码
在 MProxy 中调用 MClassLoader 加载并实例化对象
好了,上面的代码已经编写完了,那么现在就来对比一下 JDK 自带的 Proxy 和我们自己实现的 Proxy 的效果
这里按照 Java 的基本数据类型 以及它们对应的包装类 定义了16个接口方法
这两个代理类的实现是一样的
区别在于实现的接口一个是我们定义的 MInvocationHandler
另一个是 JDK 的 InvocationHandler
Main 类分别生成了 Proxy 和 MProxy 的代理对象
然后执行代理对象的各个方法
来看看实现的效果,左边是 JDK 的动态代理,右边是使用 Javassist 实现的动态代理
作者:MinXie
链接:https://juejin.cn/post/7168030376080703495
页面更新:2024-03-31
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号