news 2026/1/15 6:13:43

Spring boot 4 探究基于CGLIB的动态代理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring boot 4 探究基于CGLIB的动态代理

CGLIB 是 Spring 实现 AOP 的核心底层技术之一,它基于 ASM 字节码框架,在运行时生成目标类的子类来实现代理。相比于 JDK 动态代理(基于接口),CGLIB 可以代理普通的 Java 类,灵活性更高。

springboot 【spring-core-7.0.2.jar】已经将cglib,asm相关的代码作为其core代码的一部分

从Springboot 2.x以后,默认统一使用 CGLIB, 针对有接口的则切换到JdkDynamicAopProxy,原因如下:

  1. 适用性更强(无需强制实现接口):CGLIB 通过生成子类来实现代理,不需要你的业务类必须实现某个接口。这符合现代开发中更倾向于直接面向类编程的趋势,减少了开发约束。
  2. 性能更优:JDK 动态代理底层依赖 Java 反射(InvocationHandler),性能相对较低;而 CGLIB 底层使用 ASM 框架直接操作字节码,通过FastClass机制进行方法调用,跳过了反射的性能损耗,执行效率更高。
    在 Spring AOP 中,使用注解(如@Transactional)来声明需求,Spring 底层则会选择使用JDK 动态代理CGLIB来实现这个需求

CGLIB底层实现原理

CGLIB 并不是 Spring 自己写的,而是一个强大的第三方字节码生成库(底层依赖 ASM 框架)。

  • 核心机制:继承。
    CGLIB 在运行时动态地生成一个被代理类的子类final类除外,因为不能被继承)。
  • 拦截逻辑:
    这个生成的子类会**重写(Override)**父类中所有的非final方法。在重写的方法中,它会插入拦截器逻辑(MethodInterceptor)。
  • 执行流程:
    当你调用代理对象的方法时,实际上调用的是子类重写后的方法。子类在执行时,会先通知拦截器(执行切面逻辑,如日志、事务),然后再通过反射调用父类(即目标对象)的原始方法。

简单总结:CGLIB 就是通过“继承你的类,重写你的方法”来实现功能增强的

与JDK动态代理的区别

对比维度JDK 动态代理CGLIB 动态代理
实现机制Java 反射 API (Proxy+InvocationHandler)字节码操作(ASM 框架,生成子类)
对接口的依赖必须实现接口不需要实现接口(直接代理类)
性能相对较低(依赖反射调用)较高(通过 FastClass 调用,接近原生)
限制只能代理接口中定义的方法不能代理final类或final方法
Spring Boot 默认非默认默认

如何配置代理方式

  1. 通过配置文件设置
# 默认使用CGLIB,# 如果设置为False,则切换为JAVA动态代理,但如果目标类没有实现接口,Spring 会自动降级回 CGLIBspring:aop:proxy-target-class:true
  1. 通过注解设置@EnableAspectJAutoProxy(proxyTargetClass = true)

CGLIB何时生成代理类

CGLIB 生成代理类是在运行时(Runtime)完成的,而不是在编译阶段,CGLIB 是一种运行时代码生成技术

当你的 Spring Boot 应用启动,或者程序执行到动态代理创建逻辑时(例如调用Enhancer.create()),CGLIB 才会通过 ASM 框架在内存中动态生成一个新的字节码文件(即代理类),类名类似UserServiceImpl$$EnhancerByCGLIB$$e073c71b

CGLIB 的核心组件是Enhancer。当你调用enhancer.create()时,底层发生了以下事情:

  1. 触发:程序运行到创建代理对象的代码。
  2. 生成字节码:CGLIB 使用 ASM 库在 JVM 内存中,根据你的目标类(父类),动态编写一个新的类(子类)的字节码。
  3. 加载:通过类加载器将这个新生成的字节码加载到 JVM 中。
  4. 实例化:创建该代理类的实例并返回。

Springboot 代理的自动选择策略

在 Spring Boot(实际上是 Spring Framework 的 AOP 模块)中,这个自动选择策略(即:判断是用 JDK 动态代理还是 CGLIB)的源码核心位于DefaultAopProxyFactory类中

publicclassDefaultAopProxyFactoryimplementsAopProxyFactory,Serializable{publicstaticfinalDefaultAopProxyFactoryINSTANCE=newDefaultAopProxyFactory();privatestaticfinallongserialVersionUID=7930414337282325166L;publicAopProxycreateAopProxy(AdvisedSupportconfig)throwsAopConfigException{if(!config.isOptimize()&&!config.isProxyTargetClass()&&config.hasUserSuppliedInterfaces()){returnnewJdkDynamicAopProxy(config);}else{Class<?>targetClass=config.getTargetClass();if(targetClass==null&&config.getProxiedInterfaces().length==0){thrownewAopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");}else{return(AopProxy)(targetClass!=null&&!targetClass.isInterface()&&!Proxy.isProxyClass(targetClass)&&!ClassUtils.isLambdaClass(targetClass)?newObjenesisCglibAopProxy(config):newJdkDynamicAopProxy(config));}}}}

示例:

场景目标类实现的接口hasUserSuppliedProxyInterfaces
结果
Spring 的决策
场景 A实现了UserService(你的业务接口)true默认使用JDK 动态代理
场景 B什么接口都没实现false使用CGLIB代理
场景 C实现了
ApplicationContextAware
false使用CGLIB代理 (因为这是 Spring 内部接口)
场景 D实现了
SpringProxy(极少情况)
false使用CGLIB代理 (因为这是 Spring 自己的标记)

调用链路

在 Spring Boot 启动过程中,这个逻辑是在 Bean 初始化的后置处理器中被触发的:

  1. 入口AbstractAutoProxyCreator.postProcessAfterInitialization()
  2. 包装判断wrapIfNecessary()方法被调用。
  3. 创建代理:如果发现当前 Bean 需要被 AOP 增强,则调用createProxy()
  4. 工厂决策ProxyFactory会调用上述的DefaultAopProxyFactory.createAopProxy()来获取具体的代理实例。
    1. ProxyFactory本身不直接实现创建逻辑,它继承自ProxyCreatorSupport,而ProxyCreatorSupport持有一个非常关键的组件:AopProxyFactory
    2. 当你调用proxyFactory.getProxy()时,它内部会调用父类ProxyCreatorSupportcreateAopProxy()方法。
    3. ProxyCreatorSupport.createAopProxy(): 它负责从配置中获取AopProxyFactory并调用其createAopProxy()方法
    4. 默认情况下,ProxyCreatorSupport中持有的AopProxyFactory实例就是DefaultAopProxyFactory

何时使用Objenesis

在 Spring 框架中,objenesis相关的包(主要位于org.springframework.objenesis)提供了一个非常核心且底层的功能:绕过构造函数来实例化对象

关于选择Objenesis还是反射(Reflection)来实例化对象,主要发生在 AOP 代理创建和某些特殊 Bean 的实例化过程中。

Spring 的选择策略可以概括为:“Objenesis 优先,反射兜底”

  • 原因
    • 性能更高:Objenesis 通过直接操作字节码或 Unsafe 机制绕过构造函数,比反射调用构造函数更快。
    • 兼容性更强:它不需要目标类有无参构造函数,甚至可以实例化final类(在某些模式下)或构造函数为private的类。
      详见ObjenesisCglibAopProxy
protectedObjectcreateProxyClassAndInstance(Enhancerenhancer,Callback[]callbacks){Class<?>proxyClass=enhancer.createClass();ObjectproxyInstance=null;if(objenesis.isWorthTrying()){try{proxyInstance=objenesis.newInstance(proxyClass,enhancer.getUseCache());}catch(Throwableex){logger.debug("Unable to instantiate proxy using Objenesis, falling back to regular proxy construction",ex);}}if(proxyInstance==null){try{Constructor<?>ctor=this.constructorArgs!=null?proxyClass.getDeclaredConstructor(this.constructorArgTypes):proxyClass.getDeclaredConstructor();ReflectionUtils.makeAccessible(ctor);proxyInstance=this.constructorArgs!=null?ctor.newInstance(this.constructorArgs):ctor.newInstance();}catch(Throwableex){thrownewAopConfigException("Unable to instantiate proxy using Objenesis, and regular proxy instantiation via default constructor fails as well",ex);}}((Factory)proxyInstance).setCallbacks(callbacks);returnproxyInstance;}

与反射的区别

特性Objenesis (首选)反射 (兜底)
实现原理利用 JVM 底层 API (如Unsafe/ReflectionFactory) 绕过构造函数。基于java.lang.reflect.Constructor的标准反射机制。
性能极高。直接分配内存,不执行构造函数逻辑。较低。涉及 JNI 调用和安全检查。
构造函数调用不调用。对象创建后,成员变量保持 JVM 默认值(null/0)。调用。会执行构造函数中的代码逻辑。
适用场景代理类(Proxy)、序列化框架、需要绕过复杂构造逻辑的场景。普通的 Bean 实例化、需要执行构造函数初始化逻辑的场景。
风险对象状态可能不完整(未初始化);在某些受限的 JVM 环境(如高版本 JDK 的安全管理器)下可能被禁止。安全、标准,但受限于构造函数的可见性和参数要求。

对象何时初始化

对象实例属性的初始化被推迟到了 Spring 的**属性填充(Populate)初始化(Initialize)**阶段

  1. 依赖注入(DI):Spring 使用反射机制(BeanUtils/ReflectionUtils)扫描@Autowired@Value等注解,并通过set方法或直接Field.set()将依赖的对象或值注入进去。
  2. 显式赋值:如果需要特定的初始值,通常需要在@PostConstruct注解的方法中,或者InitializingBeanafterPropertiesSet()方法中手动赋值。

使用 CGLIB 时的注意事项

  1. 不能代理final类:因为 CGLIB 是通过继承实现的,如果类被声明为final,无法被继承,代理就会失败。
  2. 不能代理final方法:同理,如果方法被声明为final,子类无法重写该方法,因此无法织入切面逻辑。
  3. 构造函数调用:由于是子类代理,创建代理对象时可能会调用目标类的构造函数,如果构造函数中有复杂的逻辑或副作用,需要留意。
  4. 依赖引入:在 Spring Boot 中你通常不需要手动引入,因为spring-boot-starter-aspectjspring-core-中已经包含了 CGLIB 和 ASM 的依赖。

使用示例

importorg.junit.jupiter.api.Test;importorg.springframework.cglib.proxy.Enhancer;importorg.springframework.cglib.proxy.MethodInterceptor;importorg.springframework.cglib.proxy.MethodProxy;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;importjava.lang.reflect.Proxy;publicclassProxyTest{@interfaceAuthor{Stringname();intage()default1;}@TestpublicvoidjdkProxyTest(){UserServiceuserService=newUserServiceImpl();UserServiceproxy=(UserService)newJdkProxyHandler(userService).getProxy();proxy.saveUser("张三");}@TestpublicvoidcglibProxyTest(){UserServiceuserService=newUserServiceImpl();UserServiceproxy=(UserService)newCglibProxyInterceptor(userService).getProxy();proxy.saveUser("张三");}}interfaceUserService{voidsaveUser(Stringname);voiddeleteUser(Longid);}classUserServiceImplimplementsUserService{@OverridepublicvoidsaveUser(Stringname){System.out.println("业务逻辑:正在保存用户 -> "+name);}@OverridepublicvoiddeleteUser(Longid){System.out.println("业务逻辑:正在删除用户 ID -> "+id);}}classJdkProxyHandlerimplementsInvocationHandler{privateObjecttarget;publicJdkProxyHandler(Objecttarget){this.target=target;}/** * 获取代理对象 */publicObjectgetProxy(){returnProxy.newProxyInstance(target.getClass().getClassLoader(),// 类加载器target.getClass().getInterfaces(),// 目标对象实现的接口this);// 当前的 InvocationHandler}/** * 拦截方法调用 */@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{System.out.println("[JDK代理] 方法执行前:日志开始...");// 执行目标方法Objectresult=method.invoke(target,args);System.out.println("[JDK代理] 方法执行后:日志结束。");returnresult;}}classCglibProxyInterceptorimplementsMethodInterceptor{// 被代理的对象privateObjecttarget;publicCglibProxyInterceptor(Objecttarget){this.target=target;}/** * 获取代理对象 */publicObjectgetProxy(){// 1. 创建增强器Enhancerenhancer=newEnhancer();// 2. 设置父类(即目标类)enhancer.setSuperclass(target.getClass());// 3. 设置回调(即拦截器)enhancer.setCallback(this);// 4. 创建子类(代理对象)returnenhancer.create();}/** * 拦截方法调用 */@OverridepublicObjectintercept(Objectobj,Methodmethod,Object[]args,MethodProxyproxy)throwsThrowable{System.out.println("[CGLIB代理] 方法执行前:日志开始...");// 注意:这里通常使用 proxy.invokeSuper(obj, args) 而不是反射// proxy.invokeSuper 调用的是父类的方法,性能更好Objectresult=proxy.invokeSuper(obj,args);System.out.println("[CGLIB代理] 方法执行后:日志结束。");returnresult;}}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/14 10:30:04

GLM-4.6V-Flash-WEB模型在JavaScript前端交互中的应用设想

GLM-4.6V-Flash-WEB模型在JavaScript前端交互中的应用设想 如今&#xff0c;用户早已不满足于静态网页和简单的表单提交。他们希望网站能“看懂”图片、理解问题&#xff0c;甚至像真人一样对话——比如上传一张超市小票&#xff0c;直接问&#xff1a;“哪些商品最划算&#x…

作者头像 李华
网站建设 2026/1/6 1:47:42

VS Code在企业级开发中的实战应用

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个企业级VS Code工作区模板&#xff0c;包含&#xff1a;1. 多项目统一配置&#xff1b;2. 集成调试工具链&#xff08;Docker、Kubernetes&#xff09;&#xff1b;3. 团队…

作者头像 李华
网站建设 2026/1/6 1:47:23

为什么现代网站必须优先考虑移动端体验?

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个响应式网页设计模板&#xff0c;优先适配移动端设备&#xff0c;包含自适应布局、触摸友好的交互元素和移动优化的内容展示。要求使用HTML5和CSS3实现&#xff0c;确保在手…

作者头像 李华
网站建设 2026/1/14 15:21:48

AI如何帮你快速通过GitHub学生认证

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个Python脚本&#xff0c;自动处理GitHub学生认证流程。脚本应包含以下功能&#xff1a;1. 自动填写GitHub学生认证表单&#xff1b;2. 上传学生证或录取通知书等证明文件&a…

作者头像 李华
网站建设 2026/1/14 2:50:07

1小时搭建你的测试面试模拟系统

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 在快马平台上快速实现一个最小可行产品&#xff08;MVP&#xff09;的测试面试模拟器&#xff0c;要求&#xff1a;1. 基础问答功能&#xff08;至少20道常见测试题&#xff09; 2…

作者头像 李华
网站建设 2026/1/15 2:19:04

零基础入门:NAVICAT17的安装与基本使用指南

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个面向新手的NAVICAT17入门教程应用&#xff0c;包含从安装到基本操作的逐步指导。应用应提供交互式教程&#xff0c;允许用户在模拟环境中练习连接数据库、执行SQL查询、浏…

作者头像 李华