源码详解系列(一)–cglib动态代理的使用和分析

2023-06-14 0 264

甚么是cglib

单纯而言,cglib 是用以手动聚合全权类的。与 JDK 便携式的静态全权较之,有几点相同:

JDK 静态全权明确要求被全权类与此同时实现某一USB,而 cglib 渗杂明确要求。

在最终目标方式的继续执行速率上,虽然选用了FastClass监督机制,cglib 更慢(以内部空间换天数,前面会说到)。

常用的静态全权有甚么样?

他们碰触较为多的通常是 JDK 静态全权和责任编辑说到的 cglib,这三个C#都是运转时聚合全权类。spring-aop 与此同时选用了这三种C#。

除此之外,除了javassit和aspectJ等服务器端C#, 它既能校对时聚合全权类,也能在运转时聚合全权类。责任编辑不作扩充,钟爱的能科学研究下。

为甚么要用静态全权

为的是让教给的小东西能逐步形成管理体系,他们须要问许多难题,比如,它是用以化解甚么难题的,不必它泡果,它较之其它有甚么样优劣,之类。这儿,他们须要先思索为甚么要选用静态全权。

为的是更快地答疑那个难题,这里透过三个单纯的范例来逐渐表明。

具体而言,我有三个使用者有关的Controller。

class UserController { public Response create(UserCreateDTO dto){ String id = userService.create(dto); return Response.of(id); } public Response update(UserUpdateDTO dto){ String id = userService.update(dto); return Response.of(id); } public Response delete(UserDeleteDTO dto){ String id = userService.delete(dto); return Response.of(id); } public Response getById(String id){ UserVO user = userService.getById(id); return Response.of(user); } // zzs001······ }

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

class UserController { public Response create(UserCreateDTO dto){ // 打印入参日志 // ······ Response response = Response.of(userService.create(dto)); // 打印出参日志 // ······ return response; } public Response update(UserUpdateDTO dto){ // 打印入参日志 // ······ Response response = Response.of(userService.update(dto)); // 打印出参日志 // ······ return response; } public Response delete(UserDeleteDTO dto){ // 打印入参日志 // ······ Response response = Response.of(userService.delete(dto)); // 打印出参日志 // ······ return response; } public Response getById(String id){ // 打印入参日志 // ······ Response response = Response.of(userService.getById(id)); // 打印出参日志 // ······ return response; } // zzs001······ }

明显能看出来,这种做法有三个的难题:一是须要手动添加大量重复代码,二是代码耦合度较高。

当然,难题要三个个化解,具体而言,针对第二个难题,我创建了三个UserControllerCommonLogProxy来专门处理请求日志,如下:

class UserControllerCommonLogProxy extends UserController { public Response create(UserCreateDTO dto){ // 打印入参日志 // ······ Response response = super.create(dto); // 打印出参日志 // ······ return response; } public Response update(UserUpdateDTO dto){ // 打印入参日志 // ······ Response response = super.update(dto); // 打印出参日志 // ······ return response; } public Response delete(UserDeleteDTO dto){ // 打印入参日志 // ······ Response response = super.delete(dto); // 打印出参日志 // ······ return response; } public Response getById(String id){ // 打印入参日志 // ······ Response response = super.getById(id); // 打印出参日志 // ······ return response; } // 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>

定义全权类的逻辑

具体而言,他们须要在某一地方定义好全权类的逻辑,在责任编辑的范例中,全权的逻辑是在方式继续执行前打印入参,方式继续执行后打印出参。他们能透过与此同时实现MethodInterceptorUSB来定义这些逻辑。根据 aop 联盟的标准(能自行了解下),MethodInterceptor属于一种Advice。

须要注意一点,这儿要透过proxy.invokeSuper来调用最终目标类的方式,而不是选用method.invoke,不然会出现栈溢出等难题。如果你非要调用method.invoke,你须要把最终目标类对象作为LogInterceptor的成员属性,在调用method.invoke时将它作为入参,而不是选用MethodInterceptor.intercept的入参 obj,但是,我不推荐你这么做,因为你将无法享受到 cglib 全权类继续执行快的优势(然而还是许多人这么做)。

public class LogInterceptor implements MethodInterceptor { // 这儿传入的obj是全权类对象,而不是最终目标类对象 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.err.println(“打印” + method.getName() + “方式的入参”); // 注意,这儿要调用proxy.invokeSuper,而不是method.invoke,不然会出现栈溢出等难题 Object obj2 = proxy.invokeSuper(obj, args); System.err.println(“打印” + method.getName() + “方式的出参”); return obj2; } }

@Test public void testBase() throws InterruptedException { // 设置输出全权类到指定路径,便于前面预测 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, “D:/growUp/test”); // 创建Enhancer对象 Enhancer enhancer = new Enhancer(); // 设置哪个类须要全权 enhancer.setSuperclass(UserController.class); // 设置怎么全权 enhancer.setCallback(new LogInterceptor()); UserController userController = (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是用以干嘛的?

源码详解系列(一)–cglib动态代理的使用和分析

全权类的源代码

下面看看全权类文件的源代码(责任编辑选用Luyten作为反校对工具,考虑篇幅难题,这儿仅展示 update 方式)。

在静态代码块继续执行时,会初始化最终目标类 update 方式对应的Method对象,也会初始化全权类 update 方式对应的MethodProxy对象。那个MethodProxy对象非常重要,透过它才能选用FastClass。

除此之外,他们须要注意三个方式,三个是update方式,该方式中会去调用他们定义的MethodInterceptor的intercept方式,另三个是CGLIB$update$0方式,该方式直接调用UserController的update方式。前面他们会发现,update方式饶了一圈回来最终会调用CGLIB$update$0方式。

//聚合类的名字规则是:被全权classname + “$$”+classgeneratorname+”ByCGLIB”+”$$”+key的hashcodepublic class UserController$$EnhancerByCGLIB$$e6f193aa extends UserController implements Factory { private boolean CGLIB$BOUND; public static Object CGLIB$FACTORY_DATA; private static final ThreadLocal CGLIB$THREAD_CALLBACKS; private static final Callback[] CGLIB$STATIC_CALLBACKS; //他们一开始传入的MethodInterceptor对象 zzs001 private MethodInterceptor CGLIB$CALLBACK_0; private static Object CGLIB$CALLBACK_FILTER; //最终目标类的update方式对象 private static final Method CGLIB$update$0$Method; //全权类的update方式对象 private static final MethodProxy CGLIB$update$0$Proxy; private static final Object[] CGLIB$emptyArgs; static void CGLIB$STATICHOOK1() { CGLIB$THREAD_CALLBACKS = new ThreadLocal(); CGLIB$emptyArgs = new Object[0]; final Class<?> forName = Class.forName(“cn.zzs.cglib.UserController$$EnhancerByCGLIB$$e6f193aa”); final Class<?> forName2; final Method[] methods = ReflectUtils.findMethods(new String[]{“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方式 final void CGLIB$update$0() { super.update(); } public final void update() { MethodInterceptor cglib$CALLBACK_2; MethodInterceptor cglib$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方式。

public Object invokeSuper(Object obj, Object[] args) throws Throwable { try { //初始化,创建了三个FastClass类对象 init(); FastClassInfo fci = fastClassInfo; // 这儿将直接调用全权类的CGLIB$update$0方式,而不是透过反射调用 // fci.f2:全权类的FastClass对象,fci.i2为CGLIB$update$0方式对应的索引,obj为当前的全权类对象,args为update方式的参数列表 return fci.f2.invoke(fci.i2, obj, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } } private void init(){ if (fastClassInfo == null){ synchronized (initLock){ if (fastClassInfo == null){ CreateInfo ci = createInfo; FastClassInfo fci = new FastClassInfo(); // 创建最终目标类的FastClass对象 fci.f1 = helper(ci, ci.c1); // 创建全权类的FastClass对象 fci.f2 = helper(ci, ci.c2); fci.i1 = fci.f1.getIndex(sig1); fci.i2 = fci.f2.getIndex(sig2); fastClassInfo = fci; createInfo = null; } } } }

FastClass的作用

打开全权类的FastClass文件,能看到,透过方式索引他们能匹配到CGLIB$update$0方式,并且直接调用它,而不须要像 JDK 静态全权一样透过反射的方式调用,极大提高了继续执行效率。

//传入参数: //n:方式索引 //o:全权类实例 //array:方式输入参数 public Object invoke(final int n, final Object o, final Object[] array) throws InvocationTargetException { final UserController$$EnhancerByCGLIB$$e6f193aa userController$$EnhancerByCGLIB$$e6f193aa = (UserController$$EnhancerByCGLIB$$e6f193aa)o; try { switch (n) { case 0: { return new Boolean(userController$$EnhancerByCGLIB$$e6f193aa.equals(array[0])); } case 1: { return userController$$EnhancerByCGLIB$$e6f193aa.toString(); } case 2: { return new Integer(userController$$EnhancerByCGLIB$$e6f193aa.hashCode()); } case 3: { return userController$$EnhancerByCGLIB$$e6f193aa.clone(); } // ······· case 24: { // 透过匹配方式索引,直接调用该方式,那个方式里将直接调用最终目标类的方式 userController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0(); return null; } // ······· } catch (Throwable t) { throw new InvocationTargetException(t); } throw new IllegalArgumentException(“Cannot find matching method/constructor”); }

透过上面的预测,他们找到了 cglib 全权类继续执行起来更慢的原因。

结语

以上基本讲完 cglib 的选用和源代码预测。

最后,感谢阅读。

2021-09-26更改

有关源代码请移步:https://github.com/ZhangZiSheng001/cglib-demo责任编辑为原创文章,转载请附上原文出处链接:https://www.cnblogs.com/ZhangZiSheng001/p/11917086.html

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务