Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

源码详解系列(一) ------ cglib的使用和分析

NotificationsYou must be signed in to change notification settings

ZhangZiSheng001/cglib-demo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

简单来说,cglib 就是用来自动生成代理类的。与 JDK 自带的动态代理相比,有以下几点不同:

  1. JDK 动态代理要求被代理类实现某个接口,而 cglib 无该要求。

  2. 在目标方法的执行速度上,由于采用了FastClass机制,cglib 更快(以空间换时间,后面会讲到)。

常见的动态代理有哪些?

我们接触比较多的一般是 JDK 动态代理和本文讲到的 cglib,这两个类库都是运行时生成代理类。spring-aop 同时使用了这两种类库。

另外,还有javassitaspectJ等第三方类库, 它们既能编译时生成代理类,也能在运行时生成代理类。本文不作扩展,感兴趣的可以研究下。

为什么要用动态代理

为了让学到的东西能够形成体系,我们需要问很多问题,例如,它是用来解决什么问题的,不用它行不行,它相比其他有哪些优缺点,等等。这里,我们需要先思考为什么要使用动态代理。

为了更好地解答这个问题,这里通过一个简单的例子来逐步说明。

首先,我有一个用户相关的Controller

classUserController {publicResponsecreate(UserCreateDTOdto){Stringid =userService.create(dto);returnResponse.of(id);}publicResponseupdate(UserUpdateDTOdto){Stringid =userService.update(dto);returnResponse.of(id);}publicResponsedelete(UserDeleteDTOdto){Stringid =userService.delete(dto);returnResponse.of(id);}publicResponsegetById(Stringid){UserVOuser =userService.getById(id);returnResponse.of(user);}// zzs001······}

为了方便监控跟踪,我希望将每个方法的入参、出参、当前登录人等等信息打印出来。简单的做法就是直接在每个方法里嵌入打印日志的代码,如下:

classUserController {publicResponsecreate(UserCreateDTOdto){// 打印入参日志// ······Responseresponse =Response.of(userService.create(dto));// 打印出参日志// ······returnresponse;}publicResponseupdate(UserUpdateDTOdto){// 打印入参日志// ······Responseresponse =Response.of(userService.update(dto));// 打印出参日志// ······returnresponse;}publicResponsedelete(UserDeleteDTOdto){// 打印入参日志// ······Responseresponse =Response.of(userService.delete(dto));// 打印出参日志// ······returnresponse;}publicResponsegetById(Stringid){// 打印入参日志// ······Responseresponse =Response.of(userService.getById(id));// 打印出参日志// ······returnresponse;}// zzs001······}

明显可以看出来,这种做法有两个的问题:一是需要手动添加大量重复代码,二是代码耦合度较高

当然,问题要一个个解决,首先,针对第二个问题,我创建了一个UserControllerCommonLogProxy来专门处理请求日志,如下:

classUserControllerCommonLogProxyextendsUserController {publicResponsecreate(UserCreateDTOdto){// 打印入参日志// ······Responseresponse =super.create(dto);// 打印出参日志// ······returnresponse;}publicResponseupdate(UserUpdateDTOdto){// 打印入参日志// ······Responseresponse =super.update(dto);// 打印出参日志// ······returnresponse;}publicResponsedelete(UserDeleteDTOdto){// 打印入参日志// ······Responseresponse =super.delete(dto);// 打印出参日志// ······returnresponse;}publicResponsegetById(Stringid){// 打印入参日志// ······Responseresponse =super.getById(id);// 打印出参日志// ······returnresponse;}// zzs001······}

上面例子中,我不直接访问UserController,而是通过UserControllerCommonLogProxy来间接访问。其实,这就是代理,严格来说属于静态代理,和接下来要讲的动态代理不太一样。

静态代理解决了代码耦合的问题,但这种做法产生了一个新的问题:需要手动创建和维护大量的代理类。我需要为每一个Controller都增加一个Proxy,项目中将会有大量的*Proxy,而且,当UserController增加方法时,需要在对应的Proxy中实现。

这个时候,我们会想,要是代理类能自动生成该多好。于是,动态代理就派上用场了。

我们只要定义好代理类的逻辑,动态代理就能帮我们生成对应的代理类(可以在编译时生成,也可以在运行时生成),而不需要我们手动创建

所以,我们用动态代理,本质上是为了更简单方便地实现 AOP

如何使用cglib

还是继续开篇的例子,我需要打印UserController的入参、出参等信息。

工程环境

JDK:1.8.0_231

maven:3.6.3

IDE:Spring Tool Suite 4.6.1.RELEASE

引入依赖

项目类型 Maven Project,打包方式 jar

<!-- cglib-->    <dependency>        <groupId>cglib</groupId>        <artifactId>cglib</artifactId>        <version>3.2.5</version>    </dependency><!-- junit-->    <dependency>        <groupId>junit</groupId>        <artifactId>junit</artifactId>        <version>4.12</version>        <scope>test</scope>    </dependency>

定义代理类的逻辑

首先,我们需要在某个地方定义好代理类的逻辑,在本文的例子中,代理的逻辑就是在方法执行前打印入参,方法执行后打印出参。我们可以通过实现MethodInterceptor接口来定义这些逻辑。根据 aop 联盟的标准(可以自行了解下),MethodInterceptor属于一种Advice

需要注意一点,这里要通过proxy.invokeSuper来调用目标类的方法,而不是使用method.invoke,不然会出现栈溢出等问题。如果你非要调用method.invoke,你需要把目标类对象作为LogInterceptor的成员属性,在调用method.invoke时将它作为入参,而不是使用MethodInterceptor.intercept的入参 obj,但是,我不推荐你这么做,因为你将无法享受到 cglib 代理类执行快的优势(然而还是很多人这么做)。

publicclassLogInterceptorimplementsMethodInterceptor {// 这里传入的obj是代理类对象,而不是目标类对象publicObjectintercept(Objectobj,Methodmethod,Object[]args,MethodProxyproxy)throwsThrowable {System.err.println("打印" +method.getName() +"方法的入参");// 注意,这里要调用proxy.invokeSuper,而不是method.invoke,不然会出现栈溢出等问题Objectobj2 =proxy.invokeSuper(obj,args);System.err.println("打印" +method.getName() +"方法的出参");returnobj2;    }}

获取代理类

我们主要通过Enhancer来配置、获取代理类对象,下面的代码挺好理解的,我们需要告诉 cglib,我要代理谁,代理的逻辑放在哪里

@TestpublicvoidtestBase()throwsInterruptedException {// 设置输出代理类到指定路径,便于后面分析System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"D:/growUp/test");// 创建Enhancer对象Enhancerenhancer =newEnhancer();// 设置哪个类需要代理enhancer.setSuperclass(UserController.class);// 设置怎么代理enhancer.setCallback(newLogInterceptor());// 获取代理类实例UserControlleruserController = (UserController)enhancer.create();// 测试代理类System.out.println("-------------");userController.save();System.out.println("-------------");userController.delete();System.out.println("-------------");userController.update();System.out.println("-------------");userController.find();}

我们也可以同时设置多个Callback,需要注意的是,设置了多个Callback不是说一个方法可以被多个Callback拦截,而是说目标类中不同的方法可以被不同的Callback拦截。所以,当设置了多个Callback时,cglib 需要知道哪些方法使用哪个Callback,我们需要额外设置CallbackFilter来指定每个方法使用的是哪个Callback。项目中我也提供了例子。

运行结果

运行上面的测试方法,可以看到,我们使用 cglib 很简单地实现了代理,不但较好地解耦合,而且减少了大量重复代码。

-------------打印save方法的入参增加用户打印save方法的出参-------------打印delete方法的入参删除用户打印delete方法的出参-------------打印update方法的入参修改用户打印update方法的出参-------------打印find方法的入参查找用户打印find方法的出参

代理类源码分析

接下来我们来看看 cglib 的源码。

cglib如何生成代理类的源码就不分析了,感兴趣的可以自行研究(cglib 的源码可读性还是很强的),我们只要记住两点就行,1. cglib 的代理类会缓存起来,不会重复创建;2. 使用的是 asm 来生成Class文件。

我们直接来看代理类方法执行的源码。

代理类文件

在上面例子中,我们指定的文件夹下生成了三个文件,一个代理类文件,两个FastClass文件。

通过 debug 可以发现,代理类文件是调用Enhancer.create的时候生成的,而两个FastClass文件是第一次调用MethodProxy.invokeSuper的时候才生成。这两个FastClass是用来干嘛的?

zzs_cglib_01

代理类的源码

下面看看代理类文件的源码(本文采用Luyten作为反编译工具,考虑篇幅问题,这里仅展示 update 方法)。

在静态代码块执行时,会初始化目标类 update 方法对应的Method对象,也会初始化代理类 update 方法对应的MethodProxy对象。这个MethodProxy对象非常重要,通过它才能使用FastClass

另外,我们需要注意两个方法,一个是update方法,该方法中会去调用我们定义的MethodInterceptorintercept方法,另一个是CGLIB$update$0方法,该方法直接调用UserControllerupdate方法。后面我们会发现,update方法饶了一圈回来最终会调用CGLIB$update$0方法。

//生成类的名字规则是:被代理classname + "$$"+classgeneratorname+"ByCGLIB"+"$$"+key的hashcodepublicclassUserController$$EnhancerByCGLIB$$e6f193aaextendsUserControllerimplementsFactory {privatebooleanCGLIB$BOUND;publicstaticObjectCGLIB$FACTORY_DATA;privatestaticfinalThreadLocalCGLIB$THREAD_CALLBACKS;privatestaticfinalCallback[]CGLIB$STATIC_CALLBACKS;//我们一开始传入的MethodInterceptor对象  zzs001privateMethodInterceptorCGLIB$CALLBACK_0;privatestaticObjectCGLIB$CALLBACK_FILTER;//目标类的update方法对象privatestaticfinalMethodCGLIB$update$0$Method;//代理类的update方法对象privatestaticfinalMethodProxyCGLIB$update$0$Proxy;privatestaticfinalObject[]CGLIB$emptyArgs;staticvoidCGLIB$STATICHOOK1() {CGLIB$THREAD_CALLBACKS =newThreadLocal();CGLIB$emptyArgs =newObject[0];finalClass<?>forName =Class.forName("cn.zzs.cglib.UserController$$EnhancerByCGLIB$$e6f193aa");finalClass<?>forName2;finalMethod[]methods =ReflectUtils.findMethods(newString[]{"update","()V","find","()V","delete","()V","save","()V"},                (forName2 =Class.forName("cn.zzs.cglib.UserController")).getDeclaredMethods());// 初始化目标类的update方法对象CGLIB$update$0$Method =methods[0];// 初始化代理类update方法对象CGLIB$update$0$Proxy =MethodProxy.create((Class)forName2, (Class)forName,"()V","update","CGLIB$update$0");    }// 这个方法将直接调用UserController的update方法finalvoidCGLIB$update$0() {super.update();    }publicfinalvoidupdate() {MethodInterceptorcglib$CALLBACK_2;MethodInterceptorcglib$CALLBACK_0;if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 =this.CGLIB$CALLBACK_0)) ==null) {CGLIB$BIND_CALLBACKS(this);cglib$CALLBACK_2 = (cglib$CALLBACK_0 =this.CGLIB$CALLBACK_0);        }//一般走这里,即调用我们传入MethodInterceptor对象的intercept方法if (cglib$CALLBACK_0 !=null) {cglib$CALLBACK_2.intercept((Object)this,UserController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0$Method,UserController$$EnhancerByCGLIB$$e6f193aa.CGLIB$emptyArgs,UserController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0$Proxy);return;        }super.update();    }}

创建FastClass文件

MethodProxy.invokeSuper(Object, Object[])方法中,我们会发现,两个FastClass文件是在init方法中生成的。当然,它们也只会创建一次。

我们用到的主要是代理类的FastClass,通过它,我们可以直接调用到CGLIB$update$0方法,相当于可以直接调用目标类的update方法。

publicObjectinvokeSuper(Objectobj,Object[]args)throwsThrowable {try {//初始化,创建了两个FastClass类对象init();FastClassInfofci =fastClassInfo;// 这里将直接调用代理类的CGLIB$update$0方法,而不是通过反射调用// fci.f2:代理类的FastClass对象,fci.i2为CGLIB$update$0方法对应的索引,obj为当前的代理类对象,args为update方法的参数列表returnfci.f2.invoke(fci.i2,obj,args);        }catch (InvocationTargetExceptione) {throwe.getTargetException();        }    }privatevoidinit(){if (fastClassInfo ==null){synchronized (initLock){if (fastClassInfo ==null){CreateInfoci =createInfo;FastClassInfofci =newFastClassInfo();// 创建目标类的FastClass对象fci.f1 =helper(ci,ci.c1);// 创建代理类的FastClass对象fci.f2 =helper(ci,ci.c2);// 获取update方法的索引fci.i1 =fci.f1.getIndex(sig1);// 获取CGLIB$update$0方法的索引,这个很重要fci.i2 =fci.f2.getIndex(sig2);fastClassInfo =fci;createInfo =null;                  }              }          }      }

FastClass的作用

打开代理类的FastClass文件,可以看到,通过方法索引我们可以匹配到CGLIB$update$0方法,并且直接调用它,而不需要像 JDK 动态代理一样通过反射的方式调用,极大提高了执行效率。

//传入参数://n:方法索引//o:代理类实例//array:方法输入参数publicObjectinvoke(finalintn,finalObjecto,finalObject[]array)throwsInvocationTargetException {finalUserController$$EnhancerByCGLIB$$e6f193aauserController$$EnhancerByCGLIB$$e6f193aa = (UserController$$EnhancerByCGLIB$$e6f193aa)o;try {switch (n) {case0: {returnnewBoolean(userController$$EnhancerByCGLIB$$e6f193aa.equals(array[0]));                }case1: {returnuserController$$EnhancerByCGLIB$$e6f193aa.toString();                }case2: {returnnewInteger(userController$$EnhancerByCGLIB$$e6f193aa.hashCode());                }case3: {returnuserController$$EnhancerByCGLIB$$e6f193aa.clone();                }// ·······case24: {// 通过匹配方法索引,直接调用该方法,这个方法里将直接调用目标类的方法userController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0();returnnull;                }// ·······        }catch (Throwablet) {thrownewInvocationTargetException(t);        }thrownewIllegalArgumentException("Cannot find matching method/constructor");    }

通过上面的分析,我们找到了 cglib 代理类执行起来更快的原因。

结语

以上基本讲完 cglib 的使用和源码分析。

最后,感谢阅读。

2021-09-26更改

相关源码请移步:https://github.com/ZhangZiSheng001/cglib-demo

本文为原创文章,转载请附上原文出处链接:https://www.cnblogs.com/ZhangZiSheng001/p/11917086.html

Releases

No releases published

Packages

No packages published

Languages


[8]ページ先頭

©2009-2025 Movatter.jp