代理模式的核心思想说起来很简单:不直接访问目标对象,而是通过一个中间对象来间接访问,由这个中间对象负责控制访问的时机、方式和附加逻辑。这个中间对象就是代理,它和目标对象实现相同的接口,调用方感知不到差异。Spring AOP、MyBatis的Mapper接口、RPC框架的服务引用,背后几乎都是代理模式在支撑。要真正理解这些框架,绕不开代理模式本身以及JDK中Proxy的底层实现。

代理模式的基本结构与静态代理
代理模式的标准定义是:为其他对象提供一种代理以控制对这个对象的访问。它包含三个角色。第一个是抽象主题接口,定义了目标对象和代理对象共同的行为契约;第二个是真实主题,即实际干活的目标对象;第三个是代理对象,它持有目标对象的引用,在调用前后可以插入额外逻辑。
静态代理是最直观的实现方式:程序员手动编写一个代理类,在编译期就确定代理关系。下面用一个简单的订单保存例子来演示:
// 抽象主题接口
public interface OrderService {
void saveOrder(String orderNo);
}
// 真实主题
public class OrderServiceImpl implements OrderService {
@Override
public void saveOrder(String orderNo) {
System.out.println("保存订单:" + orderNo);
}
}
// 静态代理类
public class OrderServiceProxy implements OrderService {
private final OrderService target;
public OrderServiceProxy(OrderService target) {
this.target = target;
}
@Override
public void saveOrder(String orderNo) {
System.out.println("记录方法调用日志...");
target.saveOrder(orderNo);
System.out.println("记录方法执行耗时...");
}
}
静态代理的优点是结构清晰、易于调试,所有逻辑都是显式代码。但缺点也很致命:每个接口每个方法都要手写一遍转发逻辑,方法一多代码大量重复;而且一旦接口增加方法,代理类必须同步修改。如果项目里有几十个接口都需要统一的日志或事务控制,手写代理就完全不可接受了,这就引出了动态代理。
JDK动态代理的底层实现机制
JDK从1.3开始提供了动态代理支持,核心入口是java.lang.reflect.Proxy类和InvocationHandler接口。与静态代理不同,动态代理的代理类是在运行时动态生成的,开发者只需要编写横切逻辑,不需要为每个接口单独写代理类。
使用方式分两步:先实现InvocationHandler,在它的invoke方法中编写统一处理逻辑;再调用Proxy.newProxyInstance生成代理实例。看一个完整示例:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkProxyDemo {
public static void main(String[] args) {
OrderService target = new OrderServiceImpl();
OrderService proxy = (OrderService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {
System.out.println("前置处理:方法名 " + method.getName());
Object result = method.invoke(target, arguments);
System.out.println("后置处理");
return result;
}
});
proxy.saveOrder("SO-1001");
}
}
深入到实现层面,Proxy.newProxyInstance做了三件事。第一,它通过ProxyClassFactory在内存中生成代理类的字节码,代理类的命名规则是com.sun.proxy.$ProxyN这样的形式,并且默认继承自Proxy类,同时实现传入的全部接口。这也解释了为什么JDK动态代理要求目标对象必须实现接口——Java是单继承的,代理类已经继承了Proxy,无法再继承业务类。第二,通过传入的类加载器加载这份生成的字节码,得到Class对象。第三,通过反射调用代理类的构造器,把InvocationHandler实例作为参数传入,构造出代理对象。
生成的代理类反编译后大致是下面这个样子,可以清楚地看到每个方法体只是把调用转发给了handler:
public final class $Proxy0 extends Proxy implements OrderService {
private static Method m1;
private static Method m3; // saveOrder 对应的Method对象
public $Proxy0(InvocationHandler h) {
super(h);
}
@Override
public final void saveOrder(String orderNo) {
try {
// 所有方法调用统一转发给InvocationHandler
super.h.invoke(this, m3, new Object[]{orderNo});
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
理解这段生成代码非常重要,它能解释两个常见疑惑。一是为什么在invoke方法里调用proxy参数的方法会造成死循环——因为proxy就是代理对象本身,对它调用任何接口方法都会再次进入invoke。二是为什么hashCode、equals、toString这三个Object方法也会被拦截,而getClass等final方法不会被拦截,因为生成的代理类只重写了接口方法和这三个方法。
CGLIB代理与方案选型
JDK动态代理的接口限制在很多时候并不方便,比如要代理一个没有实现任何接口的普通类,就需要CGLIB。CGLIB的原理不同:它通过ASM字节码框架生成目标类的子类,代理对象就是子类实例,通过重写非final的方法来插入拦截逻辑。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxyDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderServiceImpl.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
System.out.println("CGLIB前置处理");
Object result = methodProxy.invokeSuper(obj, args);
System.out.println("CGLIB后置处理");
return result;
}
});
OrderServiceImpl proxy = (OrderServiceImpl) enhancer.create();
proxy.saveOrder("SO-2002");
}
}
两者的对比可以从几个维度展开。实现方式上,JDK代理基于接口,CGLIB基于继承;依赖上,JDK代理是JDK自带的,CGLIB需要额外引入第三方库。能力边界上,CGLIB无法代理final类和final方法,而JDK代理无法代理没有接口的类。性能方面,早期CGLIB创建代理较慢但方法调用快,JDK代理则相反,不过经过多个版本的优化,两者在调用层面的差距已经很小,性能一般不再是选型的决定因素。
Spring框架的选型策略值得参考:当目标类实现了接口时默认使用JDK动态代理,没有接口时自动切换到CGLIB,也可以通过配置强制使用CGLIB。从Spring Boot 2.x开始,默认策略已经改为优先使用CGLIB,这样能避免一个经典陷阱——JDK代理情况下,把目标对象强转成具体实现类会抛出ClassCastException,因为代理对象和实现类只是兄弟关系而非父子关系。
代理模式的典型应用场景
第一种是远程代理,这是RPC框架的基础。客户端拿到的一个服务接口引用,实际上是代理对象,调用方法时代理负责序列化参数、发起网络请求、把结果反序列化后返回,调用方完全感觉不到网络细节。Dubbo的服务引用、Feign的HTTP客户端都是这个思路。
第二种是AOP切面编程。事务管理、日志记录、权限校验、接口限流这些横切逻辑,如果写进业务代码会造成大量耦合。通过代理模式,这些逻辑被抽取到切面中,在方法调用的前后动态织入,业务代码保持纯粹。Spring的声明式事务@Transactional本质上就是通过代理在方法开启前获取连接、关闭自动提交,方法正常结束后提交,抛异常时回滚。
第三种是延迟加载,也叫虚代理。Hibernate和MyBatis的关联查询默认是懒加载的:实体返回时代理对象只填充主键,真正访问关联属性时代理才去数据库补查数据。这种方式避免了加载实体时连带着把整条关联链都查出来,对大对象的按需加载非常有效。
最后需要提醒一个常见坑:代理对象内部的自我调用不会走代理。比如同一个Service里方法A调用this.方法B(),即使方法B标注了@Transactional,事务也不会生效,因为此时调用发生在目标对象内部,绕过了代理。解决办法是通过AopContext.currentProxy()获取当前代理再调用,或者把方法拆分到不同的Bean中。理解这一点,恰恰说明了吃透Proxy实现原理对实际开发的直接价值。
代理模式动态代理Java Proxy修改时间:2026-09-16 06:42:35