cglib 代理
CGLIB代理是一种基于继承的代理方式,它可以在运行时动态生成代理类,代理类是目标类的一个子类,重写了目标类的方法,实现了对目标类的方法增强
模拟 cglib 代理
Target(用来做增强的方法)
1.创建Porxy类模拟真实代码
Porxy类是目标类的子类,重写了目标类的方法,实现了对目标类的方法增强
2.定义一个MethodInterceptor
**MethodInterceptor**类似于JDK动态代理的**InvocationHandler,通过set方法或者构造器注入**
3.在重写的方法中调用MethodInterceptor的intercept方法
这里是需要的一些参数:
- Object o:代理类对象(在这里本身为代理所以为this)
- Method method:当前执行的方法(方法对象只需要一次,我们定义为static)
- Object [ ] objects:当前执行方法的参数(看有无参数,无参new一个空数组,有参就new一个有值的数组)
- MethodProxy methodProxy:CGLIB库中的一个类,用于代表目标类中的一个方法 (第四个这里后面讲,先传null)
静态方法对象,在静态代码块中初始化
框里对应:代理对象、方法对象、方法参数(这里无参)、methodProxy。
下面两个方法笔者认为大家都能分析就不罗嗦了。
4.1这里用的是反射调用
测试类中可以试一下:
代码从上到下依次为创建代理对象、目标对象、注入MethodInterceptor、打印模拟前置增强、method方法对象反射调用被增强方法、利用proxy代理对象调用测试结果。
结果如下(都进行了前置打印增强):
4.2下面来讲一下methodProxy如何避免反射调用的
如何创建methodProxy对象
- 在Proxy中新增带原始功能的方法(没增强的方法,增强的方法就是上文第一次重写的save方法)
- 创建MethodProxy对象(与方法对象类似在静态代码块初始化,利用MethodProxy里的create方法创建出来)
- 参数如下(这里用无参的save生成MethodProxy演示):
- Class c1:目标对象(Target.class)
- Class c2:代理对象(Proxy.class)
- String desc:这里类似java字节码中的表示,偏底层(无参的这样写,第一个括号表示无参,V表示返回void ------> ( )V )
- String name1:增强功能的方法名( save )
- String name2:不带增强功能的方法名( saveSuper )
下面两个MethodProxy笔者就省略精简一点(第一个desc的阔号中的字母 I 表示参数为int类型,为 J 的表示参数为 long 类型 )。
最后把上文设置为null的MethodProxy传进去,回到测试类测试打印结果:
这个是结合目标用的( methdoProxy.invoke(target, args) ):
这个是结合目标用的,也就是说我们不用创捷Target的实例也能正常执行( methodProxy.invokeSuper(o, args); ):
结果如下(二者结果相同):
收获
和 jdk 动态代理原理查不多
- 回调的接口换了一下,InvocationHandler 改成了 MethodInterceptor
- 调用目标时有所改进,见下面代码片段
- method.invoke 是反射调用,必须调用到足够次数才会进行优化
- methodProxy.invoke 是不反射调用,它会正常(间接)调用目标对象的方法(Spring 采用)
- methodProxy.invokeSuper 也是不反射调用,它会正常(间接)调用代理对象的方法,可以省略目标对象
methodProxy原理
在methodProxy的上述两个方法中又会产生各自的代理类配合其使用,由于我们已经有一个代理Porxy来增强方法,后面俩个代理的父类叫FastClass所以我们为了分开表达,这里methodProxy方法中生成的代理我们就称作FastClass,目的为了区分增强代理与避免反射的代理。
在FastClass源码中我们发现有6个抽象方法,这里笔者只说两个关键的方法getIndex、invoke
模拟FastClass实现
由于FastClass中编译的步骤是直接生成字节码,看源码不太方便,即使拿到了源码逻辑也比较复杂难懂,这里我们模拟实现
创建两个用于避免反射调用的两个FastClass子类
本来我们是要让子类继承FastClass,但是里面抽象方法只说两个,所以这里挑两个发法模拟
- 第一个方法作用是获取目标方法的一个编号,参数为 signature(翻译为:签名) 包括方法名字、参数返回值
- 第二个方法作用是根据第一个方法返回的编号 正常调用 目标对象的方法
我们Proxy中调用invoke方法时,内部就会调用到FastClass的invok方法
配合目标用的笔者取类名为TargetFastClass
其实在我们创建methodProxy时就会生成我们的FastClass,自然每个生成的methodProxy都知道自己的签名,因为下面生成methodProxy传入的desc与name1就为该方法的signature
这里再源码中生成FastClass的时候会将这些签名定义好,同样以static修饰
这时方法签名定义好了以后,在getIndex中就可以判断这个签名对应哪个方法
// 获取目标方法的编号
/*
Target
save() 0
save(int) 1
save(long) 2
signature 包括方法名字、参数返回值
*/
当我们调用methodProxy时,参数里包含签名,就可以根据签名找到对应方法的编号,就会根据编号正常调用方法(在这里我们创建methodProxy时,也将目标已经参数传递过来,再转换一下类型即可调用)
我们再方法内测试一下:
结果:
配合目标用的笔者取类名为ProxyFastClass
这里我们将save换为saveSuper、invoke中传进来的参数由Target改为Proxy即可,原因时我们利用Proxy代理中正常调用的方法来实现调用
import org.springframework.cglib.core.Signature;
public class ProxyFastClass {
static Signature s0 = new Signature("saveSuper", "()V");
static Signature s1 = new Signature("saveSuper", "(I)V");
static Signature s2 = new Signature("saveSuper", "(J)V");
// 获取代理方法的编号
/*
Proxy
saveSuper() 0
saveSuper(int) 1
saveSuper(long) 2
signature 包括方法名字、参数返回值
*/
public int getIndex(Signature signature) {
if(s0.equals(signature)) {
return 0;
} else if(s1.equals(signature)) {
return 1;
} else if(s2.equals(signature)) {
return 2;
}
return -1; //表示没找到
}
// 在我们Proxy中调用invoke方法时,内部就会调用到FastClass的invok方法
// 根据 getIndex 返回的编号 正常调用 目标对象的方法
public Object invoke(int index, Object proxy, Object[] args) {
if(index == 0) {
((Proxy) proxy).saveSuper();
return null;
} else if(index == 1) {
((Proxy) proxy).saveSuper((int) args[0]);
return null;
} else if(index == 2) {
((Proxy) proxy).saveSuper((long) args[0]);
return null;
} else {
throw new RuntimeException("无此方法");
}
}
public static void main(String[] args) {
ProxyFastClass fastClass = new ProxyFastClass();
int index = fastClass.getIndex(new Signature("saveSuper", "(I)V"));
System.out.println(index);
fastClass.invoke(index, new Proxy(), new Object[]{1});
}
}
收获
- 当调用 MethodProxy 的 invoke 或 invokeSuper 方法时, 会动态生成两个类
- ProxyFastClass 配合代理对象一起使用, 避免反射
- TargetFastClass 配合目标对象一起使用, 避免反射 (Spring 用的这种)
- TargetFastClass 记录了 Target 中方法与编号的对应关系
- save(long) 编号 2
- save(int) 编号 1
- save() 编号 0
- 首先根据方法名和参数个数、类型, 用 switch 和 if 找到这些方法编号
- 然后再根据编号去调用目标方法, 又用了一大堆 switch 和 if, 但避免了反射
- ProxyFastClass 记录了 Proxy 中方法与编号的对应关系,不过 Proxy 额外提供了下面几个方法
- saveSuper(long) 编号 2,不增强,仅是调用 super.save(long)
- saveSuper(int) 编号 1,不增强, 仅是调用 super.save(int)
- saveSuper() 编号 0,不增强, 仅是调用 super.save()
- 查找方式与 TargetFastClass 类似
- 为什么有这么麻烦的一套东西呢?
- 避免反射, 提高性能, 代价是一个代理类配两个 FastClass 类, 代理类中还得增加仅调用 super 的一堆方法
- 用编号处理方法对应关系比较省内存, 另外, 最初获得方法顺序是不确定的, 这个过程没法固定死