您当前的位置:首页 > 电脑百科 > 程序开发 > 开源程序 > 百科

cglib动态代理的使用和分析

时间:2023-05-17 14:14:01  来源:今日头条  作者:老吾频道

cglib是什么

CGLIB是一个用于生成代理类的开源库,它与JDK自带的动态代理在一些方面有所不同,主要体现在以下几点:

  1. 接口要求:JDK动态代理要求被代理的类实现某个接口,而CGLIB则没有这个限制。CGLIB可以代理那些没有实现接口的类,使得它更加灵活,可以代理更广泛的类。
  2. 执行速度:CGLIB在目标方法的执行速度上更快。这是因为CGLIB采用了一种称为FastClass的机制,它通过生成的代理类直接调用目标方法,避免了一些额外的方法调用,从而提高了执行效率。相比之下,JDK动态代理需要通过反射调用目标方法,会引入一些性能开销。

需要注意的是,CGLIB通过生成继承自目标类的子类来实现代理,因此目标类和目标方法不能被声明为final,否则CGLIB无法生成代理类。

 

常见的动态代理

在代理类库中,我们经常接触到的是JDK动态代理和前文提到的CGLIB。这两个类库都能在运行时生成代理类,并被广泛应用于Spring的AOP(面向切面编程)中。

除了JDK动态代理和CGLIB之外,还存在其他一些第三方类库,如JAVAssist和AspectJ,它们不仅可以在运行时生成代理类,还可以在编译时生成代理类。这些类库提供了更多的灵活性和功能,但在本文中不作深入讨论。

Spring框架的AOP模块在实现上使用了JDK动态代理和CGLIB两种技术。对于实现了接口的类,Spring默认使用JDK动态代理来生成代理类;而对于没有实现接口的类,Spring会使用CGLIB来生成代理类。这样,Spring能够根据目标类的特性选择最合适的代理方式。

为什么要用动态代理

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

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

class RoleController {
	public ResponseBean create(RoleCreateDTO dto){
		String id = roleService.create(dto);
		return Response.of(id);
	}
	
	public ResponseBean update(RoleUpdateDTO dto){
		String id = roleService.update(dto);
		return Response.of(id);
	}
	
    public ResponseBean delete(RoleDeleteDTO dto){
		String id = roleService.delete(dto);
		return Response.of(id);
	}
	
	public ResponseBean getById(String id){
		RoleVO role = roleService.getById(id);
		return Response.of(role);
	}
}

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

class RoleController {
	public ResponseBean create(RoleCreateDTO dto){
		// 打印入参日志
		// ······
		ResponseBean response = Response.of(roleService.create(dto));
		// 打印出参日志
		// ······
		return response;
	}
	
	public ResponseBean update(RoleUpdateDTO dto){
		// 打印入参日志
		// ······
		ResponseBean response = Response.of(roleService.update(dto));
		// 打印出参日志
		// ······
		return response;
	}
	
    public ResponseBean delete(RoleDeleteDTO dto){
		// 打印入参日志
		// ······
		ResponseBean response = Response.of(roleService.delete(dto));
		// 打印出参日志
		// ······
		return response;
	}
	
	public ResponseBean getById(String id){
		// 打印入参日志
		// ······
		ResponseBean response = Response.of(roleService.getById(id));
		// 打印出参日志
		// ······
		return response;
	}
	
}

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

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

class RoleControllerCommonLogProxy extends RoleController {
	public ResponseBean create(RoleCreateDTO dto){
		// 打印入参日志
		// ······
		ResponseBean response = Response.of(roleService.create(dto));
		// 打印出参日志
		// ······
		return response;
	}
	
	public ResponseBean update(RoleUpdateDTO dto){
		// 打印入参日志
		// ······
		ResponseBean response = Response.of(roleService.update(dto));
		// 打印出参日志
		// ······
		return response;
	}
	
    public ResponseBean delete(RoleDeleteDTO dto){
		// 打印入参日志
		// ······
		ResponseBean response = Response.of(roleService.delete(dto));
		// 打印出参日志
		// ······
		return response;
	}
	
	public ResponseBean getById(String id){
		// 打印入参日志
		// ······
		ResponseBean response = Response.of(roleService.getById(id));
		// 打印出参日志
		// ······
		return response;
	}
	
}

在上面的例子中,我们通过间接访问RoleController的方式,即通过
RoleControllerCommonLogProxy来进行访问。这种方式其实就是代理,更准确地说是静态代理,与接下来要介绍的动态代理有所不同。

静态代理解决了代码耦合的问题,但也带来了新的挑战:需要手动创建和维护大量的代理类。每个控制器都需要增加一个代理类,这意味着在项目中会有许多*Proxy类存在。此外,当RoleController添加新的方法时,还需要在相应的代理类中进行实现。

在这种情况下,我们会想,如果代理类能够自动生成就太好了。于是,动态代理就应运而生。

使用动态代理,我们只需定义代理类的逻辑,它就能帮助我们生成相应的代理类(无论是在编译时还是运行时生成),而不需要手动创建。这样一来,我们可以更简便地实现面向切面编程(AOP)。

我们使用动态代理的根本目的是为了更加方便地实现AOP。通过动态代理,我们能够自动生成代理类,避免手动创建大量的代理类,并且能够灵活应对业务代码的变化。

 

自定义代理类的逻辑

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

需要注意的是,在使用CGLIB时,我们应该使用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;
    }
}

获取代理类

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

@Test
public void testBase() throws InterruptedException {
    // 设置输出代理类到指定路径,便于后面分析
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:/tracy/test");
    // 创建Enhancer对象
    Enhancer enhancer = new Enhancer();
    // 设置哪个类需要代理
    enhancer.setSuperclass(RoleController.class);
    // 设置怎么代理
    enhancer.setCallback(new LogInterceptor());
    // 获取代理类实例
    RoleController roleController = (RoleController) enhancer.create();
    // 测试代理类
    System.out.println("-------------");
    roleController.save();
    System.out.println("-------------");
    roleController.delete();
    System.out.println("-------------");
    roleController.update();
    System.out.println("-------------");
    roleController.find();
}

此外,我们还可以同时设置多个Callback。值得注意的是,设置多个Callback并不意味着一个方法可以被多个Callback拦截,而是表示目标类中的不同方法可以被不同的Callback拦截。因此,当设置了多个Callback时,CGLIB需要知道哪些方法应使用哪个Callback。为此,我们需要额外设置一个CallbackFilter来指定每个方法应使用的具体Callback。

通过设置多个Callback并配合CallbackFilter,我们可以更加灵活地对目标类的不同方法进行拦截和处理。每个Callback可以实现不同的逻辑,从而实现对不同方法的个性化处理。

需要注意的是,CallbackFilter的返回值是一个整数,用于表示使用哪个Callback。通常,我们会根据方法的名称、参数类型等信息进行判断,并返回对应的Callback索引。

(此处已添加书籍卡片,请到今日头条客户端查看)

代理类的源码

下面看看代理类文件的源码(这里仅展示 update 方法)。

//生成类的名字规则是:被代理classname + "$$"+classgeneratorname+"ByCGLIB"+"$$"+key的hashcode
public class RoleController$$EnhancerByCGLIB$$e6f193aa extends RoleController 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.tracy.cglib.RoleController$$EnhancerByCGLIB$$e6f193aa");
        final Class<?> forName2;
        final Method[] methods = ReflectUtils.findMethods(new String[]{"update", "()V", "find", "()V", "delete", "()V", "save", "()V"},
                (forName2 = Class.forName("cn.tracy.cglib.RoleController")).getDeclaredMethods());
        // 初始化目标类的update方法对象
        CGLIB$update$0$Method = methods[0];
        // 初始化代理类update方法对象
        CGLIB$update$0$Proxy = MethodProxy.create((Class) forName2, (Class) forName, "()V", "update", "CGLIB$update$0");
    }

    // 这个方法将直接调用RoleController的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, RoleController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0$Method, RoleController$$EnhancerByCGLIB$$e6f193aa.CGLIB$emptyArgs, RoleController$$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);  
                    // 获取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:方法输入参数
    public Object invoke(final int n, final Object o, final Object[] array) throws InvocationTargetException {
        final RoleController$$EnhancerByCGLIB$$e6f193aa roleController$$EnhancerByCGLIB$$e6f193aa = (RoleController$$EnhancerByCGLIB$$e6f193aa)o;
        try {
            switch (n) {
                case 0: {
                    return new Boolean(roleController$$EnhancerByCGLIB$$e6f193aa.equals(array[0]));
                }
                case 1: {
                    return roleController$$EnhancerByCGLIB$$e6f193aa.toString();
                }
                case 2: {
                    return new Integer(roleController$$EnhancerByCGLIB$$e6f193aa.hashCode());
                }
                case 3: {
                    return roleController$$EnhancerByCGLIB$$e6f193aa.clone();
                }
                // ·······
                case 24: {
                    // 通过匹配方法索引,直接调用该方法,这个方法里将直接调用目标类的方法
                    roleController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0();
                    return null;
                }
                // ·······

        }
        catch (Throwable t) {
            throw new InvocationTargetException(t);
        }
        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

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



Tags:cglib   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Cglib动态代理详解
CGLIB 简介 为没有实现接口的类提供代理 更好的性能CGLIB 原理 原理:动态生成需要代理的子类,子类重写代理类的非final方法。子类中采用方法拦截的奇数拦截所有父类的方法...【详细内容】
2023-08-20  Search: cglib  点击:(167)  评论:(0)  加入收藏
搞懂Java三种代理模式:静态代理、动态代理和cglib代理
Java静态代理Java中的静态代理是一种设计模式,它通过创建一个代理类来代替原始类,从而控制对原始类的访问。代理类和原始类都实现相同的接口,使得客户端在使用时无需关心具体的...【详细内容】
2023-08-09  Search: cglib  点击:(285)  评论:(0)  加入收藏
cglib动态代理的使用和分析
cglib是什么CGLIB是一个用于生成代理类的开源库,它与JDK自带的动态代理在一些方面有所不同,主要体现在以下几点: 接口要求:JDK动态代理要求被代理的类实现某个接口,而CGLIB则没有...【详细内容】
2023-05-17  Search: cglib  点击:(368)  评论:(0)  加入收藏
▌简易百科推荐
GitHub顶流"Web OS"——运行于浏览器的桌面操作系统、用户超100万、原生jQuery和JS编写
Puter 是近日在 GitHub 上最受欢迎的一款开源项目,正式开源还没到一周 &mdash;&mdash;star 数就已接近 7k。作者表示这个项目已开发 3 年,并获得了超过 100 万用户。根据介绍,P...【详细内容】
2024-03-10  OSC开源社区    Tags:GitHub   点击:(17)  评论:(0)  加入收藏
一文读懂 AutoGPT 开源 AI Agents
Hello folks,我是 Luga,今天我们继续来聊一下人工智能(AI)生态领域相关的技术 - AutoGPT AI Agents ,本文将聚焦在针对不同类型的 AutoGPT 技术进行解析,使得大家能够了解不同 A...【详细内容】
2023-11-27  架构驿站  微信公众号  Tags:AI Agents   点击:(253)  评论:(0)  加入收藏
了解一下开源许可协议
开源许可协议开源许可协议是指允许软件源代码公开、免费获取、使用、修改和分发的许可协议。开源许可协议的目的是促进软件的自由共享和协作,使得开发者可以共同改进和创造新...【详细内容】
2023-11-18  沐雨花飞蝶  微信公众号  Tags:开源   点击:(216)  评论:(0)  加入收藏
七个很实用的开源项目,我们一起学学吧!
本周特推的两个项目都是异常实用的项目,一个接棒上周的视频重制项目 video-retalking 这次则是直接将视频替换成另外一个语种;另外一个则是解决日志阅读问题的 tailspin,让你在...【详细内容】
2023-11-06  HelloGitHub  微信公众号  Tags:开源   点击:(384)  评论:(0)  加入收藏
八个适合程序员接私活赚钱的开源项目
智慧团购一套基于Spring Cloud和Vue.js的社区团购配送系统,经过真实的用户检验且完善的社区团购配送系统,社区团购配送系统包含管理台、集团总店(商家PC端)、城市合伙人、区域...【详细内容】
2023-10-13  前端充电宝  微信公众号  Tags:开源项目   点击:(272)  评论:(0)  加入收藏
八个优秀开源DevOps工具
DevOps(Development和Operations)是一组软件工程过程最佳实践,并非工具,旨在将制造世界的精益概念应用于软件世界。维基百科给出的定义是:“DevOps是一种重视软件开发人员(Dev)和IT...【详细内容】
2023-10-10  andflow  微信公众号  Tags:DevOps   点击:(291)  评论:(0)  加入收藏
开源存在风险的根本原因
漏洞仍然是可以预防的几乎所有(96%)的漏洞仍然是可以避免的。2023年本可以避免21亿次具有已知漏洞的OSS下载,因为有了更好的修复版本&mdash;&mdash;与2022年的百分比完全相同...【详细内容】
2023-10-09     企业网D1Net  Tags:开源   点击:(299)  评论:(0)  加入收藏
中国14岁初中生,开源Windows 12网页版,star数近2k
出品 | OSC开源社区(ID:oschina2013)前几天在网上冲浪,发现名为「Windows 12 网页版」的开源项目&mdash;&mdash;在网页端实现了Windows 12 的交互和 UI。项目亮点: 精美的 UI 设...【详细内容】
2023-09-07    OSC开源社区  Tags:开源   点击:(249)  评论:(0)  加入收藏
苹果开源FastViT:快速卷积Transformer的混合视觉架构
苹果此前在论文《FastViT: A Fast Hybrid Vision Transformer using Structural Reparameterization》中提出的 FastViT 架构已正式开源。论文地址:https://arxiv.org/pdf/23...【详细内容】
2023-08-16  OSC开源社区    Tags:FastViT   点击:(319)  评论:(0)  加入收藏
金融机构使用开源软件,有哪些潜在风险?
面对新技术,无法逃避,只有先行和后行,没有不执行。本文来自社区文章《论述金融机构使用开源软件的潜在风险》及对该文的评论交流,由社区同行分享,也欢迎大家参与探讨。@朱向东 中...【详细内容】
2023-08-14    IT168企业级  Tags:开源软件   点击:(280)  评论:(0)  加入收藏
相关文章
    无相关信息
站内最新
站内热门
站内头条