ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

CGLIB 实现动态代理详解

2026/10/2 23:47:11 拓冰建站 浏览量
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 完整的代理体系。