一、为什么需要CGLIB?
在Java动态代理体系中,JDK原生动态代理存在一个短板:仅支持基于接口的代理。如果一个普通Java类、没有实现任何接口,JDK代理将完全无法使用。
为了解决这个问题,诞生了CGLIB(Code Generation Library) ,它是一个高性能的第三方字节码生成开源框架。不同于JDK代理的接口代理机制,CGLIB基于继承+字节码增强 实现动态代理,可以直接代理普通实体类,也是Spring AOP默认的底层实现(无接口场景下)。
简单总结两者分工:
-
JDK动态代理:只能代理实现了接口的类,基于反射实现
-
CGLIB动态代理:可代理所有普通非final类,基于字节码生成实现
二、CGLIB概述
2.1 基本定义
CGLIB 是一款基于 ASM 字节码操作框架 的代码生成工具。它可以在程序运行期动态生成目标类的子类,通过重写目标类的非final方法,拦截方法调用,在原生方法前后织入自定义增强逻辑(日志、事务、权限校验、性能监控等),实现代理效果。
2.2 特性
-
无接口限制:无需目标类实现任何接口,适配所有普通业务类
-
底层字节码增强:直接操作Class字节码生成代理类,底层依赖ASM,性能优于原生反射
-
继承机制约束:基于子类继承实现,因此无法代理被final修饰的类、方法
-
非侵入式增强:无需修改目标类源码,即可对方法进行功能增强
三、CGLIB原理拆解
CGLIB的底层逻辑非常简单来说就三个组件+一套执行流程。
3.1 三大组件
1. Enhancer(增强器)
CGLIB的入口类,作用是动态生成代理子类。相当于JDK代理中的Proxy工具类,用于创建代理对象实例。可以配置目标类、回调拦截器、父类属性等参数。
2. MethodInterceptor(方法拦截器)
CGLIB的回调接口,等同于JDK代理的InvocationHandler。所有被代理的方法调用都会触发该接口的intercept方法,开发者在此编写前置增强、后置增强、异常处理、原方法调用逻辑。
3. MethodProxy(方法代理)
不同于JDK原生反射调用方法,MethodProxy是CGLIB内置的方法调用工具。通过FastClass机制,为类的所有方法生成唯一索引,调用方法时直接通过索引定位,规避反射开销,大幅提升代理执行效率。
3.2 完整执行原理
-
程序运行时,Enhancer 获取目标类的字节码信息;
-
借助 ASM 框架 动态生成目标类的子类(代理类);
-
代理子类重写目标父类中所有非final、非private、非static的方法;
-
所有重写的方法都会绑定 MethodInterceptor 拦截逻辑;
-
外部调用代理对象方法时,优先进入拦截器,执行自定义增强逻辑;
-
通过 MethodProxy.invokeSuper() 调用父类原生方法,完成方法执行。
3.3 FastClass优化机制
JDK动态代理全程依赖Java反射调用方法,反射存在方法解析、权限校验等额外开销,性能较差。
而CGLIB引入 FastClass 机制:运行时为目标类和代理类分别生成FastClass对象,给每一个方法分配唯一int索引。调用方法时直接根据索引定位方法,完全绕过反射,这也是CGLIB性能优于JDK代理的原因。
四、CGLIB完整代码实战
接下来通过完整可运行代码,实现CGLIB代理,完成方法调用前后打印日志的增强功能。
4.1 引入Maven依赖
Spring3.2之后已内置CGLIB,SpringBoot项目无需手动引入,原生Java项目需要手动导入依赖:
XML
<!-- CGLIB依赖 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
4.2 编写目标业务类(无接口)
创建普通业务类,不实现任何接口,验证CGLIB无接口代理能力:
java
/**
* 目标业务类(无任何接口)
*/
public class UserService {
// 普通业务方法
public void addUser(String username) {
System.out.println("新增用户:" + username);
}
// final方法,无法被CGLIB代理
public final void deleteUser() {
System.out.println("删除用户");
}
}
4.3 自定义方法拦截器
实现MethodInterceptor接口,编写通用增强逻辑:
java
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 自定义CGLIB方法拦截器
*/
public class CglibProxyInterceptor implements MethodInterceptor {
/**
* @param o 代理对象
* @param method 目标方法
* @param objects 方法参数
* @param methodProxy 方法代理对象
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 前置增强
System.out.println("【CGLIB代理】方法执行前置:记录调用日志");
// 调用目标原生方法
Object result = methodProxy.invokeSuper(o, objects);
// 后置增强
System.out.println("【CGLIB代理】方法执行后置:记录执行结果");
return result;
}
}
4.4 创建代理工厂、测试调用
java
import net.sf.cglib.proxy.Enhancer;
/**
* CGLIB代理工厂
*/
public class CglibProxyFactory {
// 获取代理对象
public static <T> T getProxy(Class<T> clazz) {
// 1. 创建增强器对象
Enhancer enhancer = new Enhancer();
// 2. 设置父类(目标类)
enhancer.setSuperclass(clazz);
// 3. 设置方法拦截器
enhancer.setCallback(new CglibProxyInterceptor());
// 4. 创建并返回代理对象
return (T) enhancer.create();
}
// 测试
public static void main(String[] args) {
// 获取代理对象
UserService userServiceProxy = getProxy(UserService.class);
// 调用可代理方法
userServiceProxy.addUser("张三");
System.out.println("==========分割线==========");
// 调用final方法(无法被增强)
userServiceProxy.deleteUser();
}
}
4.5 运行结果
java
【CGLIB代理】方法执行前置:记录调用日志
新增用户:张三
【CGLIB代理】方法执行后置:记录执行结果
==========分割线==========
删除用户
结果说明:普通方法成功被拦截增强,final修饰的方法无法被代理,无增强逻辑,完美印证CGLIB的约束特性。
五、CGLIB VS JDK动态代理
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 实现原理 | 基于接口实现,实现目标类的接口 | 基于继承实现,生成目标类的子类 |
| 依赖条件 | 目标类必须实现接口 | 无需接口,不能代理final类/方法 |
| 底层技术 | Java原生反射 | ASM字节码增强+FastClass |
| 性能 | 反射开销大,性能较低 | 无反射开销,性能更高 |
| JDK版本 | JDK原生自带,无需引入依赖 | 第三方框架,低版本Spring需手动引入 |
| Spring使用规则 | 目标类实现接口时默认使用 | 目标类无接口时默认使用 |
六、CGLIB优缺点总结
6.1 优点
-
适用范围广:不依赖接口,可代理所有普通业务类
-
性能优异:基于字节码生成和FastClass机制,规避反射开销,效率高于JDK代理
-
非侵入式:无需修改业务代码,灵活实现方法增强,适配AOP切面编程
6.2 缺点
-
继承限制 :基于子类继承,无法代理 final类、final方法、static方法、private方法
-
依赖第三方:非JDK原生能力,需要引入额外依赖
-
启动开销:运行时动态生成字节码,首次创建代理对象耗时高于JDK代理,适合单例常驻对象,不适合频繁创建销毁的对象
七、误区汇总
1. CGLIB为什么不能代理final方法和final类?
CGLIB代理的原理是继承目标类、重写目标方法。而final修饰的类无法被继承,final修饰的方法无法被重写,因此无法完成代理增强。
2. CGLIB和JDK代理哪个更快?为什么?
常规场景下CGLIB更快。JDK代理基于反射调用方法,存在大量校验开销;CGLIB通过FastClass机制为方法生成索引,直接定位调用,规避反射损耗。仅首次生成代理类时CGLIB开销更大。
3. Spring AOP什么时候用JDK代理?什么时候用CGLIB?
-
当目标类实现了接口:Spring默认使用JDK动态代理
-
当目标类没有实现接口:Spring自动降级为CGLIB代理
-
可通过配置强制全局使用CGLIB代理
4. CGLIB可以代理private/static方法吗?
不可以。private方法仅本类可见、无法被子类重写;static方法属于类不属于对象,无法被子类重写,因此均无法被拦截增强。
八、总结
-
CGLIB是基于ASM字节码增强、继承机制的动态代理框架,弥补了JDK接口代理的短板;
-
组件:Enhancer(创建代理)、MethodInterceptor(方法拦截)、MethodProxy(高效调用);
-
约束:无法代理final、private、static修饰的类和方法;
-
是Spring AOP、Mybatis等主流框架的底层技术,是Java后端开发者必须掌握的基础知识点。