☰
从 $Proxy0 到 $$EnhancerByCGLIB:动态代理底层揭秘
2026/10/8 2:00:34 网站建设 项目流程

1.代理

在调用目标类时先调用代理类,然后通过代理类调用目标类,也就是说从直接调用转换成间接调用。优势就是我们可以在调用目标类前后进行额外的功能添加。

静态代理和动态代理

静态代理:硬编码方式,就是系统运行就生成了代理类的字节码文件

动态代理,不需要硬编码,而是在系统运行期间通过反射自动生成代理对象

方式一(JDK 动态代理)

JDK 动态代理要求目标对象必须实现至少一个接口。运行时,JDK 会根据接口规范动态生成代理类,创建代理对象,并在调用原始方法前后织入增强逻辑,从而实现对目标对象功能的扩展。

1. 定义接口

public interface UserService { void save(); String getUserByName(String name); }

2. 创建需要代理的对象

public class UserServiceImpl implements UserService { @Override public void save() { System.out.println("A_Thinks 执行 save()"); } @Override public String getUserByName(String name) { System.out.println("A_Thinks 执行 getUserByName()"); return "Hello " + name; } }

3. 创建处理器

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class MyInvocationHandler implements InvocationHandler { private final Object target; public MyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke( Object proxy, Method method, Object[] args) throws Throwable { System.out.println("方法执行前"); Object result = method.invoke(target, args); System.out.println("方法执行后"); return result; } }

注意这里的private final Object target;就是UserServiceImpl

4. 测试效果

// 4. 测试类 public class ProxyDemo { public static void main(String[] args) { // 创建真实对象 UserService realService = new UserServiceImpl(); // 创建代理对象(动态代理) UserService proxyService = (UserService) Proxy.newProxyInstance( realService.getClass().getClassLoader(), // 类加载器 realService.getClass().getInterfaces(), // 要代理的接口列表 new LogInvocationHandler(realService) // 处理器 ); // 通过代理对象调用方法(实际会进入InvocationHandler.invoke) proxyService.save(); } }

5. 原理

调用 Proxy.newProxyInstance(类加载器, 接口, 处理器) 创建代理对象时,实际上会在运行时生成一个名为$ProxyX(如 $Proxy0)的代理类。该类继承自 java.lang.reflect.Proxy,并实现了传入的所有接口,因此它天然具备了这些接口定义的全部方法。

当我们调用代理对象的任意方法时,其内部本质上都会转发为调用 InvocationHandler.invoke(...),从而将方法执行统一交由处理器来调度,实现动态代理的效果。

由此可见,JDK 动态代理必须依赖接口数组来生成字节码,这也解释了为什么目标对象必须实现接口。从 newProxyInstance 的参数设计也可以看出,生成代理类字节码仅需接口数组即可,无需目标对象的其他信息。

其实现原理也不难推测:JDK 动态代理会遍历所有接口中的方法,并为每个方法生成类似 return (String) super.h.invoke(this, m0, new Object[]{var1}); 的转发代码,以此将调用统一路由到 InvocationHandler。


手写$ProxyX

现在尝试手写$ProxyX理解下JDK到底实现了个什么样的对象。

真正的 JDK $Proxy0 是 JVM/JDK 动态生成的,我们现在自己写一个简化版:

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class $Proxy0 implements UserService { private final InvocationHandler h; public $Proxy0(InvocationHandler h) { this.h = h; } @Override public void save() { try { Method method = UserService.class.getMethod("save"); h.invoke( this, method, null ); } catch (Throwable e) { throw new RuntimeException(e); } } @Override public String getUserByName(String name) { try { Method method = UserService.class.getMethod( "getUserByName", String.class ); return (String) h.invoke( this, method, new Object[]{name} ); } catch (Throwable e) { throw new RuntimeException(e); } } }

然后手动创建代理

public class Test { public static void main(String[] args) { // 真实对象 UserService target = new UserServiceImpl(); // InvocationHandler InvocationHandler handler = new MyInvocationHandler(target); // 手动创建代理对象 UserService proxy = new $Proxy0(handler); proxy.save(); String result = proxy.getUserByName("Tom"); System.out.println(result); } }

方法执行前
A_Thinks 执行 save()
方法执行后

方法执行前
A_Thinks 执行 getUserByName()
方法执行后

Hello Tom

现在把整个过程拆开

你调用:proxy.getUserByName("Tom");

这里的:Proxy

实际上就是:new $Proxy0(handler)

所以实际上执行的是:$Proxy0.getUserByName("Tom");

进入:

@Override public String getUserByName(String name) { Method method = UserService.class.getMethod( "getUserByName", String.class ); return (String) h.invoke( this, method, new Object[]{name} ); }

最后:

method.invoke(target, args) ↓ UserServiceImpl.getUserByName("Tom")

方式二(CGLIB 动态代理)

CGLIB 动态代理不要求目标对象实现接口。它通过继承目标类的方式生成代理子类,利用字节码技术在运行时动态创建代理对象。因此,它可以为任何非 final 的类创建代理。但需要注意的是,由于CGLIB基于继承机制,CGLIB 无法代理被 final 修饰的方法。

由于该技术是脱离JDK的,所以我们需要引入依赖。

<dependency> <groupId>cglib</groupId> <artifactId>cglib-nodep</artifactId> <version>3.3.0</version> </dependency>

注意:如果使用 Spring,则无需单独引入。Spring 已经将 CGLIB 重新打包进 spring-core(包名为 org.springframework.cglib),下面的示例代码只需把 import 换成 org.springframework.cglib.proxy.* 即可,用法完全一致。

1. 创建待代理类

该目标类不需要实现任何接口。

public class UserServiceImpl { public void save() { System.out.println("A_Thinks 执行 save()"); } public String getUserByName(String name) { System.out.println("A_Thinks 执行 getUserByName()"); return "Hello " + name; } }

2. 创建方法拦截器

JDK 动态代理的处理器是 InvocationHandler,CGLIB 中对应的是 MethodInterceptor:

import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept( Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("方法执行前"); // 调用目标类的原始方法(重点,见下方说明) Object result = proxy.invokeSuper(obj, args); System.out.println("方法执行后"); return result; } }

intercept 的四个参数:

obj:生成的代理对象本身(即目标类的子类实例)

method:目标类中被拦截的方法

args:方法参数 proxy:Method

Proxy,方法代理对象,封装了方法的快速调用机制

关于调用目标方法,有一个非常容易踩的坑:

必须用 proxy.invokeSuper(obj, args),它表示调用父类(目标类)的原始方法。

千万不能写成 proxy.invoke(obj, args),因为 obj 是代理子类对象,invoke 会再次触发 intercept,造成无限递归,最终栈溢出。

另一种写法是 method.invoke(target, args),即通过反射调用持有的目标对象,也能工作,但会走反射,性能不如 invokeSuper 的 FastClass 机制。

3. 测试效果

import net.sf.cglib.proxy.Enhancer; public class CglibProxyDemo { public static void main(String[] args) { // 1. 创建增强器 Enhancer enhancer = new Enhancer(); // 2. 设置父类(也就是目标类),CGLIB 会生成它的子类 enhancer.setSuperclass(UserServiceImpl.class); // 3. 设置回调(方法拦截器) enhancer.setCallback(new MyMethodInterceptor()); // 4. 创建代理对象(本质是目标类的子类实例) UserServiceImpl proxy = (UserServiceImpl) enhancer.create(); // 通过代理对象调用方法(实际会进入 MethodInterceptor.intercept) proxy.save(); String result = proxy.getUserByName("Tom"); System.out.println(result); } } 输出: 方法执行前 A_Thinks 执行 save() 方法执行后 方法执行前 A_Thinks 执行 getUserByName() 方法执行后 Hello Tom

4. 原理

调用 enhancer.create() 时,CGLIB 会基于 ASM 在运行时生成一个目标类的子类字节码,类名形如 UserServiceImpl$$EnhancerByCGLIB$$xxxx。这个子类重写了父类中所有非 final、非 private 的方法,每个重写的方法内部都会转发到 MethodInterceptor.intercept(...),以此实现增强。 所以 CGLIB 代理对象本质上是目标类的子类实例,这与 JDK 动态代理(代理对象是接口的实现类、与目标类没有继承关系)是根本区别。

这也解释了 CGLIB 的两个限制: final 类不能被继承,因此无法被代理; final 方法不能被子类重写,因此不会被增强(调用时直接执行原方法,拦截器不生效)。

另一个值得注意的点是性能:JDK 动态代理的调用链路最终走 method.invoke 反射,而 CGLIB 除了生成代理子类外,还会生成 FastClass。FastClass 会给目标类和代理类的每个方法分配一个数字索引,调用时通过 switch(index) 直接定位并调用目标方法,完全避开反射开销。MethodProxy.invokeSuper 走的就是这条路。

这也是早期认为 CGLIB 调用性能优于 JDK 动态代理的原因(JDK 8 之后 JDK 动态代理做了大量优化,二者差距已经很小)。

5. 手写生成的代理子类

2.参考前面手写 $Proxy0 的思路,我们手写一个简化版的 CGLIB 生成子类,看看 CGLIB 到底生成了什么:

import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class UserServiceImpl$$EnhancerByCGLIB$$xxxx extends UserServiceImpl { private MethodInterceptor interceptor; public void setInterceptor(MethodInterceptor interceptor) { this.interceptor = interceptor; } @Override public void save() { try { Method method = UserServiceImpl.class.getMethod("save"); MethodProxy proxy = MethodProxy.create( UserServiceImpl.class, // 目标类 this.getClass(), // 代理子类 "()V", // 方法描述符 "save", // 原方法名 "CGLIB$save$0" // 子类中生成的桥接方法名 ); interceptor.intercept( this, method, null, proxy ); } catch (Throwable e) { throw new RuntimeException(e); } } @Override public String getUserByName(String name) { try { Method method = UserServiceImpl.class.getMethod( "getUserByName", String.class ); MethodProxy proxy = MethodProxy.create( UserServiceImpl.class, this.getClass(), "(Ljava/lang/String;)Ljava/lang/String;", "getUserByName", "CGLIB$getUserByName$1" ); return (String) interceptor.intercept( this, method, new Object[]{name}, proxy ); } catch (Throwable e) { throw new RuntimeException(e); } } // CGLIB 还会在子类中生成对应的桥接方法, // invokeSuper 最终就是通过 FastClass 索引调用这里, // 再直接调 super.xxx(),绕开反射: final void CGLIB$save$0() { super.save(); } final String CGLIB$getUserByName$1(String name) { return super.getUserByName(name); } }

我让ai生成了一个流转示例如下参考

2.区别

对比维度JDK 动态代理CGLIB 动态代理
生成方式代理类继承Proxy,并实现目标对象的接口代理类继承目标类,是目标类的子类
目标对象要求必须实现接口无需实现接口
限制只能代理接口中定义的方法目标类不能是final,final方法无法被重写增强
核心 APIProxy.newProxyInstance()+InvocationHandlerEnhancer+MethodInterceptor
方法拦截入口InvocationHandler.invoke()MethodInterceptor.intercept()
目标方法调用通常通过Method.invoke()调用目标方法通常通过MethodProxy.invokeSuper()调用父类原始方法
代理类类型$Proxy0、$Proxy1……XXX$$EnhancerByCGLIB$$xxxx
类型关系代理对象是接口的实现类,与目标类不存在继承关系代理对象是目标类的子类
强转方式一般只能转成目标接口类型可以转成目标类类型
典型场景面向接口编程、JDK 原生代理没有接口的普通类代理

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询