CGLIB 实现动态代理详解
CGLIB 实现动态代理详解一、什么是 CGLIBCGLIBCode Generation Library是一个高性能的字节码生成库它通过继承目标类生成子类来实现代理。与 JDK 动态代理基于接口不同CGLIB 不需要目标类实现任何接口。CGLIB 底层使用 ASM 字节码框架在运行时动态生成目标类的子类重写所有非 final 方法在方法中插入拦截逻辑。Spring AOP 在目标类没有接口时默认使用 CGLIB。二、核心组件组件全限定名作用Enhancernet.sf.cglib.proxy.Enhancer代理类生成器设置父类、回调创建代理对象MethodInterceptornet.sf.cglib.proxy.MethodInterceptor方法拦截器所有代理方法调用转发到intercept()MethodProxynet.sf.cglib.proxy.MethodProxy方法代理用于调用父类的原始方法Callbacknet.sf.cglib.proxy.Callback回调接口的顶层接口MethodInterceptor是它的子接口CallbackFilternet.sf.cglib.proxy.CallbackFilter回调过滤器为不同方法指定不同的回调三、使用前提CGLIB 通过继承实现代理有以下限制目标类不能是 final 类final 类无法被继承。被代理的方法不能是 final 或 privatefinal 方法无法被重写private 方法子类不可见。目标类需要有无参构造方法CGLIB 创建子类时会调用父类构造方法。如果目标类只有有参构造方法可以通过Enhancer的setConstructorFilter或 Objenesis 绕过。四、完整代码示例1. 目标类publicclassUserService{publicvoidsave(Useruser){System.out.println(保存用户user.getName());}publicUserfindById(Longid){System.out.println(查询用户id);returnnewUser(id,张三);}publicfinalvoidfinalMethod(){// final 方法CGLIB 无法代理System.out.println(final 方法);}}2. MethodInterceptor 实现importnet.sf.cglib.proxy.MethodInterceptor;importnet.sf.cglib.proxy.MethodProxy;importjava.lang.reflect.Method;publicclassLogMethodInterceptorimplementsMethodInterceptor{OverridepublicObjectintercept(Objectobj,Methodmethod,Object[]args,MethodProxyproxy)throwsThrowable{// 前置增强System.out.println(【日志】调用方法method.getName());longstartSystem.currentTimeMillis();// 调用父类目标类的原始方法Objectresultproxy.invokeSuper(obj,args);// 后置增强longcostSystem.currentTimeMillis()-start;System.out.println(【日志】执行完成耗时costms);returnresult;}}3. 生成代理对象importnet.sf.cglib.proxy.Enhancer;publicclassClient{publicstaticvoidmain(String[]args){// 创建 EnhancerEnhancerenhancernewEnhancer();// 设置父类目标类enhancer.setSuperclass(UserService.class);// 设置回调拦截器enhancer.setCallback(newLogMethodInterceptor());// 创建代理对象UserServiceproxy(UserService)enhancer.create();// 通过代理调用proxy.save(newUser(1L,张三));proxy.findById(1L);}}输出【日志】调用方法save 保存用户张三 【日志】执行完成耗时0ms 【日志】调用方法findById 查询用户1 【日志】执行完成耗时0ms4. 带参数的构造方法如果目标类没有无参构造方法可以在create()时传入构造参数publicclassUserService{privateStringprefix;publicUserService(Stringprefix){this.prefixprefix;}publicvoidsave(Useruser){System.out.println(prefix 保存用户user.getName());}}EnhancerenhancernewEnhancer();enhancer.setSuperclass(UserService.class);enhancer.setCallback(newLogMethodInterceptor());// 指定构造参数类型和值UserServiceproxy(UserService)enhancer.create(newClass[]{String.class},// 构造参数类型newObject[]{【系统】}// 构造参数值);五、intercept 方法四个参数publicObjectintercept(Objectobj,Methodmethod,Object[]args,MethodProxyproxy)参数含义说明obj代理对象CGLIB 生成的子类实例method被拦截的方法通过反射获取方法名、注解、参数类型args方法参数无参时为 nullproxy方法代理用于调用父类的原始方法关键点调用父类方法必须用proxy.invokeSuper(obj, args)不能用method.invoke(obj, args)。因为obj是代理对象用method.invoke(obj)会递归调用代理方法导致死循环。// ❌ 错误递归调用死循环Objectresultmethod.invoke(obj,args);// ✅ 正确调用父类原始方法Objectresultproxy.invokeSuper(obj,args);六、CallbackFilter为不同方法指定不同回调如果不同方法需要不同的增强逻辑可以用CallbackFilter。publicclassMyCallbackFilterimplementsCallbackFilter{Overridepublicintaccept(Methodmethod){if(method.getName().startsWith(find)){return0;// 查询方法用第一个回调}return1;// 其他方法用第二个回调}}EnhancerenhancernewEnhancer();enhancer.setSuperclass(UserService.class);enhancer.setCallbacks(newCallback[]{newQueryInterceptor(),// 索引 0查询拦截器newUpdateInterceptor()// 索引 1增删改拦截器});enhancer.setCallbackFilter(newMyCallbackFilter());UserServiceproxy(UserService)enhancer.create();七、底层原理1. 字节码生成CGLIB 通过 ASM 框架动态生成目标类的子类。生成的子类结构大致如下publicclassUserService$$EnhancerByCGLIB$$abc123extendsUserService{privateMethodInterceptorinterceptor;// 静态方法初始化 Method 对象staticvoidCGLIB$STATICHOOK1(){// 获取 save、findById 等方法的 Method 对象}Overridepublicvoidsave(Useruser){// 调用拦截器interceptor.intercept(this,CGLIB$save$0$Method,newObject[]{user},CGLIB$save$0$Proxy);}OverridepublicUserfindById(Longid){return(User)interceptor.intercept(this,CGLIB$findById$1$Method,newObject[]{id},CGLIB$findById$1$Proxy);}// FastClass 机制直接调用父类方法finalvoidCGLIB$save$0(Useruser){super.save(user);}finalUserCGLIB$findById$1(Longid){returnsuper.findById(id);}}2. FastClass 机制CGLIB 为代理类生成一个 FastClass通过索引直接调用方法避免反射开销。publicclassUserService$$EnhancerByCGLIB$$abc123$$FastClassByCGLIB$$def456extendsFastClass{OverridepublicObjectinvoke(intindex,Objectobj,Object[]args){UserService$$EnhancerByCGLIB$$abc123 proxy(UserService$$EnhancerByCGLIB$$abc123)obj;switch(index){case0:proxy.CGLIB$save$0((User)args[0]);returnnull;case1:returnproxy.CGLIB$findById$1((Long)args[0]);default:thrownewIllegalArgumentException();}}OverridepublicintgetIndex(Stringname,Class[]parameterTypes){// 根据方法名和参数类型返回索引}}MethodProxy.invokeSuper()内部通过 FastClass 调用父类方法比反射快很多。3. 生成过程1. Enhancer.setSuperclass() 设置父类 2. Enhancer.setCallback() 设置拦截器 3. Enhancer.create() 触发代理类生成 ├── 生成代理类字节码ASM ├── 生成 FastClass 字节码 ├── 通过 ClassLoader 加载代理类 └── 反射创建代理类实例注入 Callback八、CGLIB 与 JDK 动态代理对比对比维度JDK 动态代理CGLIB代理方式基于接口基于继承目标类要求必须实现接口不能是 final 类代理类实现同一接口继承目标类方法限制只能代理接口中定义的方法不能代理 final/private 方法生成速度快慢ASM 生成字节码调用速度反射调用稍慢FastClass较快依赖JDK 内置需要 CGLIB 库构造方法不调用目标类构造方法调用目标类构造方法选择建议目标类有接口优先用 JDK 动态代理。目标类无接口只能用 CGLIB。需要代理实现类中额外的方法不在接口中用 CGLIB。目标类是 final 类或方法有 finalJDK 动态代理通过接口或无法代理。九、Spring AOP 中的 CGLIB1. 代理选择DefaultAopProxyFactory根据条件决定使用哪种代理publicAopProxycreateAopProxy(AdvisedSupportconfig){if(config.isOptimize()||config.isProxyTargetClass()||hasNoUserSuppliedProxyInterfaces(config)){Class?targetClassconfig.getTargetClass();if(targetClass.isInterface()||Proxy.isProxyClass(targetClass)){returnnewJdkDynamicAopProxy(config);}returnnewObjenesisCglibAopProxy(config);}else{returnnewJdkDynamicAopProxy(config);}}proxyTargetClass true强制 CGLIB。目标类没有接口CGLIB。目标类是接口本身JDK 动态代理。其他情况JDK 动态代理。2. Spring Boot 默认使用 CGLIBSpring Boot 2.x 之后AOP 自动配置默认proxyTargetClass trueConfigurationEnableAspectJAutoProxy(proxyTargetClasstrue)publicclassAopAutoConfiguration{}所以 Spring Boot 项目中即使目标类实现了接口默认也使用 CGLIB 代理。3. ObjenesisCglibAopProxySpring 使用的ObjenesisCglibAopProxy集成了 Objenesis可以在不调用目标类构造方法的情况下创建代理实例。这解决了目标类没有无参构造方法的问题。publicclassObjenesisCglibAopProxyextendsCglibAopProxy{OverrideprotectedObjectcreateProxyClassAndInstance(Enhancerenhancer,Callback[]callbacks){Class?proxyClassenhancer.createClass();ObjectproxyInstanceobjenesis.newInstance(proxyClass);((Factory)proxyInstance).setCallbacks(callbacks);returnproxyInstance;}}Objenesis 通过Unsafe或 JVM 内部 API 直接分配对象内存跳过构造方法调用。十、常见问题1. final 方法无法代理publicclassUserService{publicfinalvoidfinalMethod(){// CGLIB 生成的子类无法重写 final 方法增强不生效}}final 方法不会被 CGLIB 代理调用时直接执行父类方法。2. 内部调用不触发代理与 JDK 动态代理一样同一个类中方法 A 调用方法 BB 的增强不会生效。因为this是原始对象不是代理对象。3. 构造方法被调用CGLIB 创建代理类实例时会调用目标类的无参构造方法。如果目标类的构造方法有副作用如初始化资源会被执行两次一次是原始对象一次是代理对象。4. 代理类名带EnhancerByCGLIBEnhancerByCGLIBEnhancerByCGLIB从容器中获取的 Bean 是代理对象getClass().getName()会显示UserService$$EnhancerByCGLIB$$xxx。这是正常现象。5. 代理类的注入问题如果目标类中的字段是通过Autowired注入的CGLIB 代理对象也会继承这些字段。但代理类本身不会重新注入它使用的是父类原始对象的字段值。6. 性能考虑CGLIB 生成代理类较慢但调用快。对于单例 Bean代理类只在容器启动时生成一次运行时调用开销很小。对于 prototype Bean每次创建都要生成代理类开销较大。十一、手动引入 CGLIBSpring 已经集成了 CGLIB但如果需要单独使用可以引入依赖dependencygroupIdcglib/groupIdartifactIdcglib/artifactIdversion3.3.0/version/dependencySpring 5.x 之后CGLIB 被重新打包到org.springframework.cglib包下避免与其他库的 CGLIB 冲突。十二、总结维度核心要点本质通过继承目标类生成子类重写方法实现代理核心类Enhancer、MethodInterceptor、MethodProxy调用父类proxy.invokeSuper(obj, args)底层ASM 生成字节码 FastClass 优化调用限制不能代理 final 类、final/private 方法与 JDK 对比CGLIB 基于继承JDK 基于接口Spring AOP无接口时用 CGLIBSpring Boot 默认 CGLIB常见问题内部调用失效、final 方法无法代理、构造方法被调用CGLIB 动态代理是 Spring AOP 的另一半基石。当目标类没有实现接口时CGLIB 通过继承生成子类在子类中拦截方法调用。理解Enhancer、MethodInterceptor和MethodProxy的协作方式以及invokeSuper()为什么不能用method.invoke()替代就能掌握 CGLIB 的核心机制。配合 JDK 动态代理两者共同构成了 Spring AOP 完整的代理体系。

相关新闻

MFC CFileDialog深度定制:标题/按钮/Logo/DPI全方案

MFC CFileDialog深度定制:标题/按钮/Logo/DPI全方案

简介:本资源是一份面向Windows平台MFC开发者的CFileDialog对话框深度定制实战源码包,聚焦商业级应用中常见的文件选择界面个性化需求,如添加自定义控件、扩展过滤逻辑、响应用户交互及适配Vista及以上系统的IFileDialogCustomize接口。压缩包…

2026/10/27 11:11:53 阅读更多 →
Claude Code新UI实操:桌面版、VS Code扩展与第三方模型接入

Claude Code新UI实操:桌面版、VS Code扩展与第三方模型接入

“Claude Code终于有好用的UI了!”这句话最近在各开发群里出现的频率相当高,而且这次不是段子,是Anthropic真的把产品补全了。我大概从年初开始用Claude Code,一路从纯终端环境用到现在,中途确实被折腾得不轻&#xff…

2026/10/21 0:21:38 阅读更多 →
CompozyOS是什么:AI Agent操作系统CompozyOS让Claude Code成为团队的完整指南

CompozyOS是什么:AI Agent操作系统CompozyOS让Claude Code成为团队的完整指南

CompozyOS是什么:AI Agent操作系统CompozyOS让Claude Code成为团队的完整指南 【免费下载链接】compozy An operating system for AI agents. Plug in the agent CLIs you already use (Claude Code, Codex, Gemini CLI, Cursor) and they become a team: they spli…

2026/10/21 10:21:41 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/26 12:00:21 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/27 4:06:49 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/27 7:21:53 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/26 12:00:21 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/27 6:20:47 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/27 4:06:50 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/26 12:00:19 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/27 7:21:50 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/26 8:34:37 阅读更多 →