cglib源码分析(四):cglib 动态代理原理分析
2021/12/16 1:11:02
本文主要是介绍cglib源码分析(四):cglib 动态代理原理分析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
本文分下面三个部分来分析cglib动态代理的原理。
- cglib 动态代理示例
- 代理类分析
- Fastclass 机制分析
一、cglib 动态代理示例
1 public class Target{ 2 public void f(){ 3 System.out.println("Target f()"); 4 } 5 public void g(){ 6 System.out.println("Target g()"); 7 } 8 } 9 10 public class Interceptor implements MethodInterceptor { 11 @Override 12 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { 13 System.out.println("I am intercept begin"); 14 //Note: 此处一定要使用proxy的invokeSuper方法来调用目标类的方法 15 proxy.invokeSuper(obj, args); 16 System.out.println("I am intercept end"); 17 return null; 18 } 19 } 20 21 public class Test { 22 public static void main(String[] args) { 23 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\code"); 24 //实例化一个增强器,也就是cglib中的一个class generator 25 Enhancer eh = new Enhancer(); 26 //设置目标类 27 eh.setSuperclass(Target.class); 28 // 设置拦截对象 29 eh.setCallback(new Interceptor()); 30 // 生成代理类并返回一个实例 31 Target t = (Target) eh.create(); 32 t.f(); 33 t.g(); 34 } 35 }
运行结果为:
I am intercept begin Target f() I am intercept end I am intercept begin Target g() I am intercept end
与JDK动态代理相比,cglib可以实现对一般类的代理而无需实现接口。在上例中通过下列步骤来生成目标类Target的代理类:
- 创建Enhancer实例
- 通过setSuperclass方法来设置目标类
- 通过setCallback 方法来设置拦截对象
- create方法生成Target的代理类,并返回代理类的实例
二、代理类分析
在示例代码中我们通过设置DebuggingClassWriter.DEBUG_LOCATION_PROPERTY的属性值来获取cglib生成的代理类。
通过之前分析的命名规则我们可以很容易的在F:\\code下面找到生成的代理类 Target$$EnhancerByCGLIB$$788444a0.class 。
使用jd-gui进行反编译(由于版本的问题,此处只能显示部分代码,可以结合javap的反编译结果来进行分析),由于cglib会代理Object中的finalize,equals, toString,hashCode,clone方法,为了清晰的展示代理类我们省略这部分代码,反编译的结果如下:
1 public class Target$$EnhancerByCGLIB$$788444a0 extends Target implements Factory 2 { 3 private boolean CGLIB$BOUND; 4 private static final ThreadLocal CGLIB$THREAD_CALLBACKS; 5 private static final Callback[] CGLIB$STATIC_CALLBACKS; 6 private MethodInterceptor CGLIB$CALLBACK_0; 7 private static final Method CGLIB$g$0$Method; 8 private static final MethodProxy CGLIB$g$0$Proxy; 9 private static final Object[] CGLIB$emptyArgs; 10 private static final Method CGLIB$f$1$Method; 11 private static final MethodProxy CGLIB$f$1$Proxy; 12 13 static void CGLIB$STATICHOOK1() 14 { 15 CGLIB$THREAD_CALLBACKS = new ThreadLocal(); 16 CGLIB$emptyArgs = new Object[0]; 17 Class localClass1 = Class.forName("net.sf.cglib.test.Target$$EnhancerByCGLIB$$788444a0"); 18 Class localClass2; 19 Method[] tmp60_57 = ReflectUtils.findMethods(new String[] { "g", "()V", "f", "()V" }, (localClass2 = Class.forName("net.sf.cglib.test.Target")).getDeclaredMethods()); 20 CGLIB$g$0$Method = tmp60_57[0]; 21 CGLIB$g$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "g", "CGLIB$g$0"); 22 CGLIB$f$1$Method = tmp60_57[1]; 23 CGLIB$f$1$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "f", "CGLIB$f$1"); 25 } 26 27 final void CGLIB$g$0() 28 { 29 super.g(); 30 } 31 32 public final void g() 33 { 34 MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0; 35 if (tmp4_1 == null) 36 { 37 CGLIB$BIND_CALLBACKS(this); 38 tmp4_1 = this.CGLIB$CALLBACK_0; 39 } 40 if (this.CGLIB$CALLBACK_0 != null) { 41 tmp4_1.intercept(this, CGLIB$g$0$Method, CGLIB$emptyArgs, CGLIB$g$0$Proxy); 42 } 43 else{ 44 super.g(); 45 } 46 } 47 }
代理类(Target$$EnhancerByCGLIB$$788444a0)继承了目标类(Target),至于代理类实现的factory接口与本文无关,残忍无视。
代理类为每个目标类的方法生成两个方法,例如针对目标类中的每个非private方法,代理类会生成两个方法,以g方法为例:一个是@Override的g方法,一个是CGLIB$g$0(CGLIB$g$0相当于目标类的g方法)。
我们在示例代码中调用目标类的方法t.g()时,实际上调用的是代理类中的g()方法。接下来我们着重分析代理类中的g方法,看看是怎么实现的代理功能。
当调用代理类的g方法时,先判断是否已经存在实现了MethodInterceptor接口的拦截对象,如果没有的话就调用CGLIB$BIND_CALLBACKS方法来获取拦截对象,CGLIB$BIND_CALLBACKS的反编译结果如下:
private static final void CGLIB$BIND_CALLBACKS(java.lang.Object); Code: 0: aload_0 1: checkcast #2; //class net/sf/cglib/test/Target$$EnhancerByCGLIB$$788444a0 4: astore_1 5: aload_1 6: getfield #212; //Field CGLIB$BOUND:Z 9: ifne 52 12: aload_1 13: iconst_1 14: putfield #212; //Field CGLIB$BOUND:Z 17: getstatic #24; //Field CGLIB$THREAD_CALLBACKS:Ljava/lang/ThreadLocal; 20: invokevirtual #215; //Method java/lang/ThreadLocal.get:()Ljava/lang/Object; 23: dup 24: ifnonnull 39 27: pop 28: getstatic #210; //Field CGLIB$STATIC_CALLBACKS:[Lnet/sf/cglib/proxy/Callback; 31: dup 32: ifnonnull 39 35: pop 36: goto 52 39: checkcast #216; //class "[Lnet/sf/cglib/proxy/Callback;" 42: aload_1 43: swap 44: iconst_0 45: aaload 46: checkcast #48; //class net/sf/cglib/proxy/MethodInterceptor 49: putfield #36; //Field CGLIB$CALLBACK_0:Lnet/sf/cglib/proxy/MethodInterceptor; 52: return
为了方便阅读,等价的代码如下:
private static final void CGLIB$BIND_CALLBACKS(Object o){ Target$$EnhancerByCGLIB$$788444a0 temp_1 = (Target$$EnhancerByCGLIB$$788444a0)o; Object temp_2; Callback[] temp_3 if(temp_1.CGLIB$BOUND == true){ return; } temp_1.CGLIB$BOUND = true; temp_2 = CGLIB$THREAD_CALLBACKS.get(); if(temp_2!=null){ temp_3 = (Callback[])temp_2; } else if(CGLIB$STATIC_CALLBACKS!=null){ temp_3 = CGLIB$STATIC_CALLBACKS; } else{ return; } temp_1.CGLIB$CALLBACK_0 = (MethodInterceptor)temp_3[0]; return; }
CGLIB$BIND_CALLBACKS 先从CGLIB$THREAD_CALLBACKS中get拦截对象,如果获取不到的话,再从CGLIB$STATIC_CALLBACKS来获取,如果也没有则认为该方法不需要代理。
那么拦截对象是如何设置到CGLIB$THREAD_CALLBACKS 或者 CGLIB$STATIC_CALLBACKS中的呢?
在Jdk动态代理中拦截对象是在实例化代理类时由构造函数传入的,在cglib中是调用Enhancer的firstInstance方法来生成代理类实例并设置拦截对象的。
firstInstance的调用轨迹为:
- Enhancer:firstInstance
- Enhancer:createUsingReflection
- Enhancer:setThreadCallbacks
- Enhancer:setCallbacksHelper
- Target$$EnhancerByCGLIB$$788444a0 : CGLIB$SET_THREAD_CALLBACKS
在第5步,调用了代理类的CGLIB$SET_THREAD_CALLBACKS来完成拦截对象的注入。下面我们看一下CGLIB$SET_THREAD_CALLBACKS的反编译结果:
public static void CGLIB$SET_THREAD_CALLBACKS(net.sf.cglib.proxy.Callback[]); Code: 0: getstatic #24; //Field CGLIB$THREAD_CALLBACKS:Ljava/lang/ThreadLocal; 3: aload_0 4: invokevirtual #207; //Method java/lang/ThreadLocal.set:(Ljava/lang/Object;)V 7: return
在CGLIB$SET_THREAD_CALLBACKS方法中调用了CGLIB$THREAD_CALLBACKS的set方法来保存拦截对象,在CGLIB$BIND_CALLBACKS方法中使用了CGLIB$THREAD_CALLBACKS的get方法来获取拦截对象,并保存到CGLIB$CALLBACK_0中。这样,在我们调用代理类的g方法时,就可以获取到我们设置的拦截对象,然后通过 tmp4_1.intercept(this, CGLIB$g$0$Method, CGLIB$emptyArgs, CGLIB$g$0$Proxy) 来实现代理。这里来解释一下intercept方法的参数含义:
@para1 obj :代理对象本身
@para2 method : 被拦截的方法对象
@para3 args:方法调用入参
@para4 proxy:用于调用被拦截方法的方法代理对象
这里会有一个疑问,为什么不直接反射调用代理类生成的(CGLIB$g$0)来间接调用目标类的被拦截方法,而使用proxy的invokeSuper方法呢?这里就涉及到了另外一个点— FastClass 。
三、Fastclass 机制分析
Jdk动态代理的拦截对象是通过反射的机制来调用被拦截方法的,反射的效率比较低,所以cglib采用了FastClass的机制来实现对被拦截方法的调用。
FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法,下面用一个小例子来说明一下,这样比较直观:
public class test10 { public static void main(String[] args){ Test tt = new Test(); Test2 fc = new Test2(); int index = fc.getIndex("f()V"); fc.invoke(index, tt, null); } } class Test{ public void f(){ System.out.println("f method"); } public void g(){ System.out.println("g method"); } } class Test2{ public Object invoke(int index, Object o, Object[] ol){ Test t = (Test) o; switch(index){ case 1: t.f(); return null; case 2: t.g(); return null; } return null; } public int getIndex(String signature){ switch(signature.hashCode()){ case 3078479: return 1; case 3108270: return 2; } return -1; } }
上例中,Test2是Test的Fastclass,在Test2中有两个方法getIndex和invoke。
在getIndex方法中对Test的每个方法建立索引,并根据入参(方法名+方法的描述符)来返回相应的索引。
Invoke根据指定的索引,以ol为入参调用对象O的方法。这样就避免了反射调用,提高了效率。
代理类(Target$$EnhancerByCGLIB$$788444a0)中与生成Fastclass相关的代码如下:
Class localClass1 = Class.forName("net.sf.cglib.test.Target$$EnhancerByCGLIB$$788444a0"); localClass2 = Class.forName("net.sf.cglib.test.Target"); CGLIB$g$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "g", "CGLIB$g$0");
MethodProxy中会对localClass1和localClass2进行分析并生成FastClass,然后再使用getIndex来获取方法g 和 CGLIB$g$0的索引,具体的生成过程将在后续进行介绍,这里介绍一个关键的内部类:
private static class FastClassInfo { FastClass f1; // net.sf.cglib.test.Target的fastclass FastClass f2; // Target$$EnhancerByCGLIB$$788444a0 的fastclass int i1; //方法g在f1中的索引 int i2; //方法CGLIB$g$0在f2中的索引 }
MethodProxy 中invokeSuper方法的代码如下:
FastClassInfo fci = fastClassInfo; return fci.f2.invoke(fci.i2, obj, args);
当调用invokeSuper方法时,实际上是调用代理类的CGLIB$g$0方法,CGLIB$g$0直接调用了目标类的g方法。所以,在第一节示例代码中我们使用invokeSuper方法来调用被拦截的目标类方法。
至此,我们已经了解cglib动态代理的工作原理,接下来会对cglib的相关源码进行分析。
转自:https://www.cnblogs.com/cruze/p/3865180.html
这篇关于cglib源码分析(四):cglib 动态代理原理分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享