CGLIB代理方法执行基本流程
CGLIB动态代理的细节
本篇想讲下CGLIB
动态代理的一些细节,因为前面的事务讲的是JDK的,我们知道JDK动态代理最终就是用了方法的反射调用,调用被代理对象的方法,而CGLIB
可不是那么简单,虽然最终是执行了代理对象的方法,但是不是反射哦,而是用了方法代理MethodProxy
和FastClass
来做的,这个是什么鬼,我们慢慢道来。
来个例子讲比较好理解
这个例子虽然只用了拦截器,拦截器怎么用的,过滤器索引怎么对应过滤器的这个就不讲了,百度就好了,我要讲里面的细节,要展示的秘密可是很多的哦,我们来看看。
public class CglibObj {
public void f1() {
System.out.println("f1");
}
public void f2() {
System.out.println("f2");
}
}
public class Test {
public static void main(String[] args) {
//把CGLIB生成的字节码文件保存到本地D:\cglib,到时候可以拖进idea里看
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\cglib");
MethodInterceptor m1 = new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("拦截器1 before");
methodProxy.invokeSuper(o, objects);
System.out.println("拦截器1 after");
return null;
}
};
MethodInterceptor m2 = new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("拦截器2 before");
methodProxy.invokeSuper(o, objects);
System.out.println("拦截器2 after");
return null;
}
};
//返回索引对应Callback数组里的拦截器索引
CallbackFilter callbackFilter = method -> {
if (method.getName().equals("f1"))
return 0;
else if (method.getName().equals("f2"))
return 1;
return 2;
};
CglibObj cglibObj1 = (CglibObj) Enhancer.create(CglibObj.class, null, callbackFilter, new Callback[]{m1, m2,NoOp.INSTANCE});
cglibObj1.f1();
cglibObj1.f1();
cglibObj1.f2();
CglibObj cglibObj2 = (CglibObj) Enhancer.create(CglibObj.class, null, callbackFilter, new Callback[]{m1, m2,NoOp.INSTANCE});
cglibObj2.f1();
cglibObj2.f2();
}
cglibObj1.f1()怎么执行的
我们直接debug
:
直接进了拦截器里面了:
增强类
这个是怎么回事呢,其实内部有生成一个代理类的,我们能看到,这个就是之所以最开始把字节码文件保存到本地的原因了,此时有2
个文件,记得数量,后面会变哦:
我们看到一个是EnhancerByCGLIB
增强类,另外一个是FastClassByCGLIB
的增强类,他们是什么呢。
CGLIB动态代理类
我们拖进来分析:
EnhancerByCGLIB
就是代理类,也就是用方法拦截器的类,我们来稍微看下里面的内容,我们可以看到这个类是继承我们的CglibObj
,还有个接口是spring
的,暂时不用管,可以看到有3
个拦截器,前面2
个是我们自定义的,后面是什么都不干的拦截器,还有2
个方法代理还2
个原方法,方法代理就是后面会利用FastClass
来调用方法的,后面会说:
从外面也可以看到拦截器:
方法增强
首先这个类初始化的时候会把属性都初始化了:
获得被代理类的原方法和创建了方法代理MethodProxy
,这里会有CGLIB$f1$0,CGLIB$f2$1
来表示对应的代理对象的f1,f2
的别名,后面获取方法索引会有用:
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("com.ww.proxy.CglibObj$$EnhancerByCGLIB$$ef630afc");
Class var1;
Method[] var10000 = ReflectUtils.findMethods(new String[]{"f1", "()V", "f2", "()V"}, (var1 = Class.forName("com.ww.proxy.CglibObj")).getDeclaredMethods());
CGLIB$f1$0$Method = var10000[0];//原f1
//代理f1
CGLIB$f1$0$Proxy = MethodProxy.create(var1, var0, "()V", "f1", "CGLIB$f1$0");
CGLIB$f2$1$Method = var10000[1];//原f2
//代理f2
CGLIB$f2$1$Proxy = MethodProxy.create(var1, var0, "()V", "f2", "CGLIB$f2$1");
}
f1/f2增强后
首先会获取拦截器,然后如果存在的话就执行intercept
方法,否则就是父类的方法,也就是原方法:
获取拦截器:
调用拦截器:
参数是对应的,代理对象,原方法,方法参数,方法代理MethodProxy
:
这里要注意这里的方法和代理方法是静态的,也就是说同一个类的不同实例调用的方法和代理方法是共享的,所以这里可以提供性能,不用每次都去获取方法,创建方法代理MethodProxy
:
现在我们知道CGLIB
是怎么调用拦截器了吧,会创建一个字节码增强类,里面会获取原方法,创建代理方法,然后覆盖原方法,里面获取拦截器,调用拦截器的intercept
方法。当然拦截器里面怎么调用也有讲究,否则可能会无限循环调用,还有个FastClassByCGLIB
还没说,这个是优化方法调用的,后面说。
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。