代理模式
1. 代理模式概述
1.1 什么是代理模式
代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用。
核心思想:用户不能直接使用目标对象,而是构造出一个代理对象,由代理对象作为中转,代理对象负责调用目标对象真正的行为,从而把结果返回给用户。
1.2 生活中的代理例子
就像明星和经纪人的关系:
- 静态代理:明星亲自挑选固定的经纪人,关系确定且稳定
- 动态代理:明星委托给代理公司,根据不同需求动态分配不同的经纪人

如上图所示,用户不能直接使用目标对象,而是构造出一个代理对象,由代理对象作为中转,代理对象负责调用目标对象真正的行为,从而把结果返回给用户。
也就是说,代理的关键点就是代理对象和目标对象的关系。
代理其实就和经纪人一样,比如你是一个明星,有很多粉丝。你的流量很多,经常会有很多金主来找你洽谈合作等,你自己肯定忙不过来,因为你要处理的不只是谈合作这件事情,你还要懂才艺、拍戏、维护和粉丝的关系、营销等。为此,你找了一个经纪人,你让他负责和金主谈合作这件事,经纪人做事很认真负责,它圆满的完成了任务,于是,金主找你谈合作就变成了金主和你的经纪人谈合作,你就有更多的时间来忙其他事情了。如下图所示

这是一种静态代理,因为这个代理(经纪人)
是你自己亲自挑选的。
但是后来随着你的业务逐渐拓展,你无法选择每个经纪人,所以你索性交给了代理公司来帮你做。如果你想在 B 站火一把,那就直接让代理公司帮你找到负责营销方面的代理人,如果你想维护和粉丝的关系,那你直接让代理公司给你找一些托儿就可以了,那么此时的关系图会变为如下

此时你几乎所有的工作都是由代理公司来进行打理,而他们派出谁来帮你做这些事情你就不得而知了,这得根据实际情况来定,因为代理公司也不只是负责你一个明星,而且每个人所擅长的领域也不同,所以你只有等到有实际需求后,才会给你指定对应的代理人,这种情况就叫做动态代理
。
1.3 代理模式的角色
- 抽象主题(Subject):定义了真实主题和代理主题的共同接口
- 真实主题(RealSubject):定义了代理所代表的真实对象
- 代理(Proxy):保存一个引用使得代理可以访问实体,并提供一个与Subject的接口相同的接口
2. 静态代理
2.1 静态代理介绍
静态代理是在编译时就确定代理关系的代理模式。需要手动编写代理类,代理类和目标类实现相同的接口。
2.2 实现步骤
- 定义抽象主题接口
- 创建真实主题类实现接口
- 创建代理类实现相同接口
- 在代理类中持有真实主题的引用
- 在代理方法中添加额外逻辑并调用真实主题方法
2.3 代码实现
步骤1:定义接口
java
package com.example.staticproxy;
/**
* 用户服务接口
*/
public interface UserService {
/**
* 根据用户ID查询用户信息
*/
String getUserById(Long userId);
/**
* 保存用户信息
*/
boolean saveUser(String userName);
}
步骤2:实现目标类
java
package com.example.staticproxy;
/**
* 用户服务实现类(被代理的目标对象)
*/
public class UserServiceImpl implements UserService {
@Override
public String getUserById(Long userId) {
// 模拟数据库查询
System.out.println("正在查询用户ID: " + userId);
return "用户" + userId + "的信息";
}
@Override
public boolean saveUser(String userName) {
// 模拟保存用户
System.out.println("正在保存用户: " + userName);
return true;
}
}
步骤3:创建代理类
java
package com.example.staticproxy;
import java.util.Date;
/**
* 用户服务静态代理类
* 在不修改原有代码的基础上,增加额外功能(如日志、权限检查等)
*/
public class UserServiceProxy implements UserService {
private UserService target; // 被代理的目标对象
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public String getUserById(Long userId) {
before("getUserById", userId);
String result = target.getUserById(userId); // 实际调用真实主题角色的方法
after("getUserById", result);
return result;
}
@Override
public boolean saveUser(String userName) {
before("saveUser", userName);
boolean result = target.saveUser(userName); // 实际调用真实主题角色的方法
after("saveUser", result);
return result;
}
/**
* 前置处理:在执行方法之前执行
*/
private void before(String methodName, Object... args) {
System.out.println(String.format("【staticproxy】log start time [%s] - 执行方法: %s, 参数: %s",
new Date(), methodName, java.util.Arrays.toString(args)));
}
/**
* 后置处理:在执行方法之后执行
*/
private void after(String methodName, Object result) {
System.out.println(String.format("【staticproxy】log end time [%s] - 方法: %s, 返回结果: %s",
new Date(), methodName, result));
}
}
步骤4:使用示例
java
public class StaticProxyDemo {
public static void main(String[] args) {
// 1. 创建目标对象
UserService userService = new UserServiceImpl();
// 2. 创建代理对象,将目标对象传入
UserService proxy = new UserServiceProxy(userService);
// 3. 通过代理对象调用方法
String user = proxy.getUserById(1001L);
boolean success = proxy.saveUser("张三");
}
}
2.4 静态代理特点
✅ 优点:
- 性能最好,没有反射开销
- 代码清晰,易于理解和调试
- 编译时确定,类型安全
❌ 缺点:
- 代码冗余,每个接口都需要写一个代理类
- 维护困难,接口变化时代理类也要修改
- 灵活性差,代理关系在编译时就确定
🎯 适用场景:
- 代理关系固定,不需要动态变化
- 对性能要求极高的场景
- 需要在编译时确定代理关系
3. JDK 动态代理
3.1 JDK 动态代理介绍
- ✅ JDK自带 - 从JDK 1.3开始就内置在Java标准库中
- 📦 包路径 :
java.lang.reflect.Proxy
和java.lang.reflect.InvocationHandler
- 🚫 限制:只能代理实现了接口的类
3.2 实现步骤
- 目标类必须实现接口
- 创建
InvocationHandler
实现类 - 使用
Proxy.newProxyInstance()
创建代理对象 - 通过代理对象调用方法
3.3 代码实现
步骤1:创建 InvocationHandler
java
package com.example.jdkproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* JDK动态代理处理器
* 实现InvocationHandler接口,定义代理对象的行为
*/
public class JdkProxyHandler implements InvocationHandler {
private Object target; // 被代理的目标对象
public JdkProxyHandler(Object target) {
this.target = target;
}
/**
* 代理对象的所有方法调用都会转发到这个方法
* @param proxy 代理对象本身
* @param method 被调用的方法
* @param args 方法参数
* @return 方法执行结果
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before(method.getName(), args);
Object result = method.invoke(target, args); // 执行目标方法
after(method.getName(), result);
return result;
}
/**
* 前置处理:在执行方法之前执行
*/
private void before(String methodName, Object... args) {
System.out.println(String.format("【jdkproxy】log start time [%s] - 执行方法: %s, 参数: %s",
new java.util.Date(), methodName, java.util.Arrays.toString(args)));
}
/**
* 后置处理:在执行方法之后执行
*/
private void after(String methodName, Object result) {
System.out.println(String.format("【jdkproxy】log end time [%s] - 方法: %s, 返回结果: %s",
new java.util.Date(), methodName, result));
}
}
步骤2:创建代理工厂
java
package com.example.jdkproxy;
import java.lang.reflect.Proxy;
/**
* JDK动态代理工厂
* 用于创建动态代理对象
*/
public class JdkProxyFactory {
/**
* 创建代理对象
* @param target 被代理的目标对象
* @return 代理对象
*/
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target) {
// 获取目标对象的类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
// 获取目标对象实现的所有接口
Class<?>[] interfaces = target.getClass().getInterfaces();
// 创建InvocationHandler
JdkProxyHandler handler = new JdkProxyHandler(target);
// 使用Proxy.newProxyInstance创建代理对象
return (T) Proxy.newProxyInstance(classLoader, interfaces, handler);
}
}
步骤3:使用示例
java
public class JdkProxyDemo {
public static void main(String[] args) {
// 1. 创建目标对象
UserService userService = new UserServiceImpl();
// 2. 使用工厂创建代理对象
UserService proxy = JdkProxyFactory.createProxy(userService);
// 3. 查看代理对象的类信息
System.out.println("目标对象类型: " + userService.getClass().getName());
System.out.println("代理对象类型: " + proxy.getClass().getName());
System.out.println("代理对象是否是Proxy的实例: " + (proxy instanceof java.lang.reflect.Proxy));
// 4. 通过代理对象调用方法
String user = proxy.getUserById(2001L);
boolean success = proxy.saveUser("李四");
}
}
3.4 JDK 动态代理特点
✅ 优点:
- 不需要手动编写代理类
- 一个 InvocationHandler 可以代理多个接口
- 灵活性高,运行时动态生成
- JDK 原生支持,无需额外依赖
❌ 缺点:
- 只能代理实现了接口的类
- 性能比静态代理差(反射调用)
- 调试相对困难
🎯 适用场景:
- 目标对象实现了接口
- 需要代理多个不同的类
- Spring AOP 的默认选择
- 需要运行时动态决定代理行为
4. CGLIB 动态代理
4.1 CGLIB(Code Generation Library)
- ❌ 不是JDK自带 - 需要额外添加依赖
- 📝 全称 :Code Generation Library(代码生成库)
- 🏢 开发者:最初由Apache开发,现在主要由Spring团队维护
- ⚡ 原理:通过字节码技术动态生成子类来实现代理。代理类继承于目标类,每次调用代理类的方法都会在拦截器中进行拦截,拦截器中再调用目标类的方法。
注:由于JDK动态代理只能代理实现了接口的类,故在 Springboot2.x 版本都是使用CGLIB动态代理。
4.2 实现步骤
- 添加 CGLIB 依赖
- 创建
MethodInterceptor
实现类 - 使用
Enhancer
创建代理对象 - 通过代理对象调用方法
4.3 代码实现
步骤1:目标类(无需接口)
java
package com.example.cglibproxy;
/**
* 产品服务类(没有接口,用于演示CGLIB代理)
*/
public class ProductService {
/**
* 根据产品ID查询产品信息
*/
public String getProductById(Long productId) {
System.out.println("正在查询产品ID: " + productId);
return "产品" + productId + "的详细信息";
}
/**
* 添加新产品
*/
public boolean addProduct(String productName, double price) {
System.out.println("正在添加产品: " + productName + ", 价格: " + price);
return true;
}
/**
* final方法,无法被代理
*/
public final String getFinalMethod() {
return "这是final方法,CGLIB无法代理";
}
}
步骤2:创建方法拦截器
java
package com.example.cglibproxy;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* CGLIB代理拦截器
* 实现MethodInterceptor接口,定义代理对象的行为
*/
public class CglibProxyInterceptor implements MethodInterceptor {
/**
* 拦截目标方法的执行
* @param obj 代理对象
* @param method 被拦截的方法
* @param args 方法参数
* @param proxy 用于调用原始方法的代理
* @return 方法执行结果
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
before(method.getName(), args);
// 调用原始方法
// 注意:这里使用proxy.invokeSuper而不是method.invoke
// proxy.invokeSuper调用的是父类(原始类)的方法
Object result = proxy.invokeSuper(obj, args);
after(method.getName(), result);
return result;
}
/**
* 前置处理:在执行方法之前执行
*/
private void before(String methodName, Object... args) {
System.out.println(String.format("【cglibproxy】log start time [%s] - 执行方法: %s, 参数: %s",
new java.util.Date(), methodName, java.util.Arrays.toString(args)));
}
/**
* 后置处理:在执行方法之后执行
*/
private void after(String methodName, Object result) {
System.out.println(String.format("【cglibproxy】log end time [%s] - 方法: %s, 返回结果: %s",
new java.util.Date(), methodName, result));
}
}
步骤3:创建代理工厂
java
package com.example.cglibproxy;
import net.sf.cglib.proxy.Enhancer;
/**
* CGLIB代理工厂
* 用于创建CGLIB动态代理对象
*/
public class CglibProxyFactory {
/**
* 创建CGLIB代理对象
* @param targetClass 目标类的Class对象
* @return 代理对象
*/
@SuppressWarnings("unchecked")
public static <T> T createProxy(Class<T> targetClass) {
// 创建Enhancer对象,用于生成代理类
Enhancer enhancer = new Enhancer();
// 设置父类(被代理的类)
enhancer.setSuperclass(targetClass);
// 设置回调函数(拦截器)
enhancer.setCallback(new CglibProxyInterceptor());
// 创建代理对象
return (T) enhancer.create();
}
}
步骤4:使用示例
java
public class CglibProxyDemo {
public static void main(String[] args) {
// 1. 创建代理对象(注意:传入的是Class对象,不是实例)
ProductService proxy = CglibProxyFactory.createProxy(ProductService.class);
// 2. 查看代理对象的类信息
System.out.println("代理对象类型: " + proxy.getClass().getSimpleName());
System.out.println("父类: " + proxy.getClass().getSuperclass().getSimpleName());
// 3. 通过代理对象调用方法
String product = proxy.getProductById(3001L);
boolean success = proxy.addProduct("iPhone 15", 7999.0);
// 4. 演示final方法无法被代理
String finalResult = proxy.getFinalMethod();
System.out.println("final方法结果: " + finalResult);
System.out.println("注意:final方法没有被代理拦截!");
}
}
4.4 CGLIB 动态代理特点
✅ 优点:
- 可以代理没有接口的类
- 性能比JDK动态代理好(直接方法调用)
- 功能强大,支持多种代理模式
- 可以拦截所有public和protected方法
❌ 缺点:
- 需要额外的CGLIB依赖
- 无法代理final类和final方法
- 无法代理private方法
- 创建代理对象的开销较大
🎯 适用场景:
- 目标对象没有实现接口
- 需要代理第三方类库中的类
- Spring AOP中的类代理
- 需要拦截所有方法调用的场景
5. 三种代理模式对比
5.1 对比表格
特性 | 静态代理 | JDK动态代理 | CGLIB动态代理 |
---|---|---|---|
实现方式 | 手动编写代理 | 基于接口 | 基于继承 |
代理对象 | 必须有接口 | 必须有接口 | 任何类 |
生成时机 | 编译时 | 运行时 | 运行时 |
性能 | 最好 | 中等 | 较好 |
灵活性 | 低 | 高 | 高 |
代码维护 | 繁琐 | 简单 | 简单 |
依赖 | 无 | JDK原生 | 需要CGLIB |
限制 | 代码冗余 | 必须有接口 | 无法代理final |
5.2 性能对比
根据 proxy-demo 中的性能测试:
makefile
性能测试结果(100000次调用):
原始调用: 2ms
静态代理: 5ms (慢了 3ms)
JDK动态代理: 45ms (慢了 43ms)
CGLIB代理: 12ms (慢了 10ms)
性能排序:静态代理 > CGLIB代理 > JDK动态代理
5.3 使用建议
- 静态代理:适用于代理关系固定、对性能要求极高的场景
- JDK动态代理:适用于有接口的类,Spring AOP默认选择
- CGLIB代理:适用于没有接口的类,功能更强大
5.4 注意事项
- JDK动态代理只能代理实现了接口的类
- CGLIB代理无法代理final类和final方法
- 性能方面:静态代理 > CGLIB代理 > JDK动态代理
- Java 9+环境下CGLIB需要额外的JVM参数支持
6. 实际应用场景
6.1 Spring AOP
- JDK动态代理:当目标对象实现了接口时使用
- CGLIB代理:当目标对象没有实现接口时使用
6.2 RPC框架
- 客户端代理:将远程调用伪装成本地调用
- 服务端代理:统一处理请求和响应
6.3 缓存代理
- 在方法调用前检查缓存
- 缓存未命中时调用真实方法并缓存结果
6.4 权限控制
- 在方法调用前进行权限检查
- 无权限时拒绝访问
6.5 日志记录
- 记录方法调用的参数和返回值
- 统计方法执行时间
7. 总结
代理模式是一种非常实用的设计模式,它在不修改原有代码的基础上,为对象提供额外的功能。三种代理方式各有特点:
- 静态代理:简单直接,性能最好,但缺乏灵活性
- JDK动态代理:基于接口,灵活性高,Spring AOP的默认选择
- CGLIB动态代理:功能最强大,可以代理任何类,但有一些限制
抽象思维:创建目标对象 - 创建代理对象 - 通过代理对象调用方法