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 Tom4. 原理
调用 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方法无法被重写增强 |
| 核心 API | Proxy.newProxyInstance()+InvocationHandler | Enhancer+MethodInterceptor |
| 方法拦截入口 | InvocationHandler.invoke() | MethodInterceptor.intercept() |
| 目标方法调用 | 通常通过Method.invoke()调用目标方法 | 通常通过MethodProxy.invokeSuper()调用父类原始方法 |
| 代理类类型 | $Proxy0、$Proxy1…… | XXX$$EnhancerByCGLIB$$xxxx |
| 类型关系 | 代理对象是接口的实现类,与目标类不存在继承关系 | 代理对象是目标类的子类 |
| 强转方式 | 一般只能转成目标接口类型 | 可以转成目标类类型 |
| 典型场景 | 面向接口编程、JDK 原生代理 | 没有接口的普通类代理 |