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,原因如下:
- 适用性更强(无需强制实现接口): CGLIB 通过生成子类来实现代理,不需要你的业务类必须实现某个接口。这符合现代开发中更倾向于直接面向类编程的趋势,减少了开发约束。
- 性能更优: 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 默认 | 非默认 | 默认 |
如何配置代理方式
- 通过配置文件设置
# 默认使用CGLIB,
# 如果设置为False,则切换为JAVA动态代理,但如果目标类没有实现接口,Spring 会自动降级回 CGLIB
spring:
aop:
proxy-target-class: true
- 通过注解设置
@EnableAspectJAutoProxy(proxyTargetClass = true)
CGLIB何时生成代理类
CGLIB 生成代理类是在 运行时(Runtime) 完成的,而不是在编译阶段,CGLIB 是一种运行时代码生成技术,
当你的 Spring Boot 应用启动,或者程序执行到动态代理创建逻辑时(例如调用 Enhancer.create()),CGLIB 才会通过 ASM 框架在内存中动态生成一个新的字节码文件(即代理类),类名类似UserServiceImpl$$EnhancerByCGLIB$$e073c71b
CGLIB 的核心组件是 Enhancer。当你调用 enhancer.create() 时,底层发生了以下事情:
- 触发: 程序运行到创建代理对象的代码。
- 生成字节码: CGLIB 使用 ASM 库在 JVM 内存中,根据你的目标类(父类),动态编写一个新的类(子类)的字节码。
- 加载: 通过类加载器将这个新生成的字节码加载到 JVM 中。
- 实例化: 创建该代理类的实例并返回。
Springboot 代理的自动选择策略
在 Spring Boot(实际上是 Spring Framework 的 AOP 模块)中,这个自动选择策略(即:判断是用 JDK 动态代理还是 CGLIB)的源码核心位于 DefaultAopProxyFactory 类中
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
public static final DefaultAopProxyFactory INSTANCE = new DefaultAopProxyFactory();
private static final long serialVersionUID = 7930414337282325166L;
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!config.isOptimize() && !config.isProxyTargetClass() && config.hasUserSuppliedInterfaces()) {
return new JdkDynamicAopProxy(config);
} else {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null && config.getProxiedInterfaces().length == 0) {
throw new AopConfigException("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) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
}
}
}
}
示例:
| 场景 | 目标类实现的接口 | hasUserSuppliedProxyInterfaces结果 |
Spring 的决策 |
|---|---|---|---|
| 场景 A | 实现了 UserService (你的业务接口) |
true | 默认使用 JDK 动态代理 |
| 场景 B | 什么接口都没实现 | false | 使用 CGLIB 代理 |
| 场景 C | 实现了 ApplicationContextAware |
false | 使用 CGLIB 代理 (因为这是 Spring 内部接口) |
| 场景 D | 实现了 SpringProxy (极少情况) |
false | 使用 CGLIB 代理 (因为这是 Spring 自己的标记) |
调用链路
在 Spring Boot 启动过程中,这个逻辑是在 Bean 初始化的后置处理器中被触发的:
- 入口:
AbstractAutoProxyCreator.postProcessAfterInitialization()。 - 包装判断:
wrapIfNecessary()方法被调用。 - 创建代理:如果发现当前 Bean 需要被 AOP 增强,则调用
createProxy()。 - 工厂决策:
ProxyFactory会调用上述的DefaultAopProxyFactory.createAopProxy()来获取具体的代理实例。ProxyFactory本身不直接实现创建逻辑,它继承自ProxyCreatorSupport,而ProxyCreatorSupport持有一个非常关键的组件:AopProxyFactory- 当你调用
proxyFactory.getProxy()时,它内部会调用父类ProxyCreatorSupport的createAopProxy()方法。 ProxyCreatorSupport.createAopProxy(): 它负责从配置中获取AopProxyFactory并调用其createAopProxy()方法- 默认情况下,
ProxyCreatorSupport中持有的AopProxyFactory实例就是DefaultAopProxyFactory
何时使用Objenesis
在 Spring 框架中,objenesis 相关的包(主要位于 org.springframework.objenesis)提供了一个非常核心且底层的功能:绕过构造函数来实例化对象。
关于选择 Objenesis 还是 反射(Reflection) 来实例化对象,主要发生在 AOP 代理创建和某些特殊 Bean 的实例化过程中。
Spring 的选择策略可以概括为:“Objenesis 优先,反射兜底”。
- 原因:
- 性能更高:Objenesis 通过直接操作字节码或 Unsafe 机制绕过构造函数,比反射调用构造函数更快。
- 兼容性更强:它不需要目标类有无参构造函数,甚至可以实例化
final类(在某些模式下)或构造函数为private的类。
详见ObjenesisCglibAopProxy类
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
Class<?> proxyClass = enhancer.createClass();
Object proxyInstance = null;
if (objenesis.isWorthTrying()) {
try {
proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache());
} catch (Throwable ex) {
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 (Throwable ex) {
throw new AopConfigException("Unable to instantiate proxy using Objenesis, and regular proxy instantiation via default constructor fails as well", ex);
}
}
((Factory)proxyInstance).setCallbacks(callbacks);
return proxyInstance;
}
与反射的区别
| 特性 | Objenesis (首选) | 反射 (兜底) |
|---|---|---|
| 实现原理 | 利用 JVM 底层 API (如 Unsafe/ReflectionFactory) 绕过构造函数。 |
基于 java.lang.reflect.Constructor 的标准反射机制。 |
| 性能 | 极高。直接分配内存,不执行构造函数逻辑。 | 较低。涉及 JNI 调用和安全检查。 |
| 构造函数调用 | 不调用。对象创建后,成员变量保持 JVM 默认值(null/0)。 | 调用。会执行构造函数中的代码逻辑。 |
| 适用场景 | 代理类(Proxy)、序列化框架、需要绕过复杂构造逻辑的场景。 | 普通的 Bean 实例化、需要执行构造函数初始化逻辑的场景。 |
| 风险 | 对象状态可能不完整(未初始化);在某些受限的 JVM 环境(如高版本 JDK 的安全管理器)下可能被禁止。 | 安全、标准,但受限于构造函数的可见性和参数要求。 |
对象何时初始化
对象实例属性的初始化被推迟到了 Spring 的**属性填充(Populate)和初始化(Initialize)**阶段
- 依赖注入(DI):Spring 使用反射机制(
BeanUtils/ReflectionUtils)扫描@Autowired、@Value等注解,并通过set方法或直接Field.set()将依赖的对象或值注入进去。 - 显式赋值:如果需要特定的初始值,通常需要在
@PostConstruct注解的方法中,或者InitializingBean的afterPropertiesSet()方法中手动赋值。
使用 CGLIB 时的注意事项
- 不能代理
final类: 因为 CGLIB 是通过继承实现的,如果类被声明为final,无法被继承,代理就会失败。 - 不能代理
final方法: 同理,如果方法被声明为final,子类无法重写该方法,因此无法织入切面逻辑。 - 构造函数调用: 由于是子类代理,创建代理对象时可能会调用目标类的构造函数,如果构造函数中有复杂的逻辑或副作用,需要留意。
- 依赖引入: 在 Spring Boot 中你通常不需要手动引入,因为
spring-boot-starter-aspectj、spring-core-中已经包含了 CGLIB 和 ASM 的依赖。
使用示例
import org.junit.jupiter.api.Test;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
@interface Author {
String name();
int age() default 1;
}
@Test
public void jdkProxyTest() {
UserService userService = new UserServiceImpl();
UserService proxy = (UserService) new JdkProxyHandler(userService).getProxy();
proxy.saveUser("张三");
}
@Test
public void cglibProxyTest() {
UserService userService = new UserServiceImpl();
UserService proxy = (UserService) new CglibProxyInterceptor(userService).getProxy();
proxy.saveUser("张三");
}
}
interface UserService {
void saveUser(String name);
void deleteUser(Long id);
}
class UserServiceImpl implements UserService {
@Override
public void saveUser(String name) {
System.out.println("业务逻辑:正在保存用户 -> " + name);
}
@Override
public void deleteUser(Long id) {
System.out.println("业务逻辑:正在删除用户 ID -> " + id);
}
}
class JdkProxyHandler implements InvocationHandler {
private Object target;
public JdkProxyHandler(Object target) {
this.target = target;
}
/**
* 获取代理对象
*/
public Object getProxy() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 目标对象实现的接口
this ); // 当前的 InvocationHandler
}
/**
* 拦截方法调用
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[JDK代理] 方法执行前:日志开始...");
// 执行目标方法
Object result = method.invoke(target, args);
System.out.println("[JDK代理] 方法执行后:日志结束。");
return result;
}
}
class CglibProxyInterceptor implements MethodInterceptor {
// 被代理的对象
private Object target;
public CglibProxyInterceptor(Object target) {
this.target = target;
}
/**
* 获取代理对象
*/
public Object getProxy() {
// 1. 创建增强器
Enhancer enhancer = new Enhancer();
// 2. 设置父类(即目标类)
enhancer.setSuperclass(target.getClass());
// 3. 设置回调(即拦截器)
enhancer.setCallback(this);
// 4. 创建子类(代理对象)
return enhancer.create();
}
/**
* 拦截方法调用
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("[CGLIB代理] 方法执行前:日志开始...");
// 注意:这里通常使用 proxy.invokeSuper(obj, args) 而不是反射
// proxy.invokeSuper 调用的是父类的方法,性能更好
Object result = proxy.invokeSuper(obj, args);
System.out.println("[CGLIB代理] 方法执行后:日志结束。");
return result;
}
}
更多推荐

所有评论(0)