🏠 一、从生活中的"中介"说起
在生活中,我们经常遇到"代理"的场景:
- 买房租房:你不会直接找房东,而是通过房产中介
- 明星代言:品牌方不会直接联系明星,而是通过经纪人
- 网购下单:你买书时,可能有"打折"和"送代金券"等附加服务
这些场景有一个共同点:真正做事的人(房东、明星、出版社)并不直接面对客户,而是通过一个"代理人"来处理事务。
这个代理人不仅能完成核心任务,还能在前后做很多额外的事情------比如中介带你参观房源、经纪人谈判价格、书店搞促销活动。
在软件世界中,代理模式就是这种思想的体现。
🎯 二、什么是代理模式?
定义
代理模式(Proxy Pattern):为其他对象提供一种代理以控制对这个对象的访问。
核心价值
- 控制访问:客户端不能直接访问真实对象,必须通过代理
- 增强功能:代理可以在调用真实对象前后添加额外操作
- 解耦职责:将核心业务逻辑与辅助逻辑分离
生活类比
| 现实场景 | 真实对象 | 代理对象 | 代理做的事 |
|---|---|---|---|
| 租房 | 房东 | 房产中介 | 带看房源、签合同、收租金 |
| 代言 | 明星 | 经纪人 | 谈价格、安排档期、接电话 |
| 买书 | 出版社 | 书店 | 打折、送代金券、卖书 |
👥 三、代理模式的三种角色
代理模式包含三个核心角色,它们各司其职:
scss
┌─────────────┐
│ Subject │ (抽象主题角色)
│ <<interface>> │
└─────────────┘
△
│ 实现
│
┌───┴─────────────────┐
│ │
┌───▼───────┐ ┌────▼─────────┐
│RealSubject│ │ Proxy │
│(真实主题) │ │ (代理主题) │
└───────────┘ └──────────────┘
1. Subject(抽象主题角色)
- 是什么:真实主题与代理主题的共同接口
- 作用:定义了真实对象和代理对象的公共方法
- 好处:客户端面向接口编程,不关心具体是真实对象还是代理对象
2. RealSubject(真实主题角色)
- 是什么:定义了代理所代表的真实对象
- 作用:执行真正的业务逻辑
- 特点:实现了 Subject 接口
3. Proxy(代理主题角色)
- 是什么:持有对真实主题的引用
- 作用:在调用真实对象前后可以执行额外操作
- 特点:同样实现 Subject 接口,控制对真实对象的访问
🔧 四、静态代理实战:书店卖书案例
场景描述
假设有一个"卖书"的业务:
- RealSubject:出版社直接卖书
- Proxy:书店作为代理,在卖书前"打折",卖书后"送代金券"
代码实现
Step 1:定义抽象主题接口
java
package com.renxin.proxy;
/**
* 抽象主题角色 - 定义卖书的公共接口
*
* @author xuzhennan
* @since 2026-03-22
*/
public interface Subject {
/**
* 卖书方法
*/
void sailBook();
}
Step 2:实现真实主题
java
package com.renxin.proxy;
/**
* 真实主题角色 - 出版社直接卖书
*
* @author xuzhennan
* @since 2026-03-22
*/
public class RealSubject implements Subject {
@Override
public void sailBook() {
System.out.println("卖书");
}
}
Step 3:实现代理主题
java
package com.renxin.proxy;
/**
* 代理主题角色 - 书店代理卖书
* 在卖书前后增加打折和送代金券的逻辑
*
* @author xuzhennan
* @since 2026-03-22
*/
public class ProxySubject implements Subject {
private RealSubject realSubject;
public RealSubject getRealSubject() {
return realSubject;
}
public void setRealSubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void sailBook() {
// 前置增强:打折
dazhe();
// 调用真实对象的方法
this.realSubject.sailBook();
// 后置增强:赠送代金券
give();
}
/**
* 打折促销
*/
private void dazhe() {
System.out.println("打折");
}
/**
* 赠送代金券
*/
private void give() {
System.out.println("赠送代金券");
}
}
Step 4:客户端测试
java
package com.renxin.proxy;
/**
* 测试类 - 验证代理模式
*
* @author xuzhennan
* @since 2026-03-22
*/
public class MainClass {
public static void main(String[] args) {
// 创建真实对象
RealSubject realSubject = new RealSubject();
// 创建代理对象,并注入真实对象
ProxySubject proxySubject = new ProxySubject();
proxySubject.setRealSubject(realSubject);
// 通过代理调用方法
proxySubject.sailBook();
}
}
运行结果
打折
卖书
赠送代金券
代码执行流程图
scss
客户端
│
▼
ProxySubject.sailBook()
│
├─► dazhe() 【前置增强】
│ └─► "打折"
│
├─► realSubject.sailBook() 【调用真实对象】
│ └─► "卖书"
│
└─► give() 【后置增强】
└─► "赠送代金券"
🚀 五、动态代理实战:InvocationHandler 的魔法
静态代理的局限性
上面的静态代理虽然实现了功能,但有一个明显的问题:
一个代理类只能服务于一个接口
如果你有 10 个接口需要代理,就要写 10 个代理类------这显然太笨重了。
动态代理的解决方案
Java 提供了 动态代理 机制,可以在运行时动态生成代理类,无需手动编写。
核心三要素:
- InvocationHandler 接口:定义了代理对象的调用逻辑
- invoke 方法:代理对象的所有方法调用都会转到这里
- Proxy.newProxyInstance():动态生成代理对象
代码实现
Step 1:复用 Subject 和 RealSubject
java
// 与静态代理相同
public interface Subject {
void sailBook();
}
public class RealSubject implements Subject {
public void sailBook() {
System.out.println("卖书");
}
}
Step 2:创建调用处理器
java
package com.renxin.news;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 动态代理调用处理器
* 所有通过代理对象的方法调用都会被转发到 invoke 方法
*
* @author xuzhennan
* @since 2026-03-22
*/
public class MyHandler implements InvocationHandler {
private RealSubject realSubject;
public void setRealSubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
/**
* 代理对象的所有方法调用都会经过这个方法
*
* @param proxy 动态生成的代理对象
* @param method 被调用的方法
* @param args 方法参数
* @return 方法执行结果
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
// 前置增强
dazhe();
// 调用真实对象的方法
result = method.invoke(realSubject, args);
// 后置增强
give();
return result;
}
private void dazhe() {
System.out.println("打折!");
}
private void give() {
System.out.println("赠送代金券!");
}
}
Step 3:客户端使用动态代理
java
package com.renxin.news;
import java.lang.reflect.Proxy;
/**
* 动态代理测试类
*
* @author xuzhennan
* @since 2026-03-22
*/
public class MainClass {
public static void main(String[] args) {
// 创建真实对象
RealSubject realSubject = new RealSubject();
// 创建调用处理器
MyHandler myHandler = new MyHandler();
myHandler.setRealSubject(realSubject);
// 动态生成代理对象
Subject proxySubject = (Subject) Proxy.newProxyInstance(
RealSubject.class.getClassLoader(), // 类加载器
realSubject.getClass().getInterfaces(), // 实现的接口
myHandler // 调用处理器
);
// 调用代理对象的方法
proxySubject.sailBook();
}
}
运行结果
打折!
卖书
赠送代金券!
动态代理原理图
scss
┌─────────────────────────────────────────────────────┐
│ 客户端调用 proxySubject.sailBook() │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Proxy.newProxyInstance() 动态生成的代理对象 │
│ (实现了 Subject 接口) │
└─────────────────────────────────────────────────────┘
│
▼ 所有方法调用
┌─────────────────────────────────────────────────────┐
│ MyHandler.invoke(proxy, method, args) │
│ │
│ 1. dazhe() ──► "打折!" │
│ 2. method.invoke() ──► "卖书" │
│ 3. give() ──► "赠送代金券!" │
└─────────────────────────────────────────────────────┘
⚖️ 六、静态代理 vs 动态代理
| 对比维度 | 静态代理 | 动态代理 |
|---|---|---|
| 代码量 | 每个接口需要写一个代理类 | 一个 InvocationHandler 处理所有接口 |
| 灵活性 | 编译期确定,不够灵活 | 运行期动态生成,非常灵活 |
| 维护成本 | 新增接口需新增代理类 | 无需修改,自动适配 |
| 性能 | 直接调用,性能高 | 反射调用,有轻微性能损耗 |
| 适用场景 | 接口少、逻辑固定 | 接口多、逻辑统一 |
| 实现方式 | 手写代理类 | JDK 动态代理 / CGLIB |
选择建议
kotlin
项目需求 ──► 代理的接口数量少? ──► 是 ──► 静态代理
│
└──► 否 ──► 需要统一处理所有方法? ──► 是 ──► 动态代理
│
└──► 否 ──► 考虑 CGLIB/Byte Buddy 等字节码增强库
💼 七、代理模式的实际应用场景
1. Spring AOP(面向切面编程)
最经典的应用! Spring 通过动态代理实现 AOP:
java
// 事务管理
@Transactional
public void saveUser(User user) {
userDao.save(user);
}
// Spring 会动态生成代理,在方法前后开启/提交事务
原理:
- 前置增强:开启事务
beginTransaction() - 执行方法:
saveUser() - 后置增强:提交事务
commit()
2. RPC 远程调用
Dubbo、gRPC 等框架通过代理屏蔽网络通信细节:
java
// 客户端就像调用本地方法一样
UserService userService = rpcProxy.create(UserService.class);
User user = userService.getUserById(123); // 实际是远程调用
3. 延迟加载(懒加载)
Hibernate 中的实体关联加载:
java
User user = session.get(User.class, 1L);
// 此时 orders 是代理对象,不会查询数据库
List<Order> orders = user.getOrders();
// 只有真正访问时才查询数据库
orders.size();
4. 权限控制
java
public class AdminProxy implements AdminService {
private AdminService realAdmin;
private User currentUser;
public void deleteUser(Long userId) {
if (!currentUser.isAdmin()) {
throw new AccessDeniedException("无权操作");
}
realAdmin.deleteUser(userId);
}
}
5. 缓存代理
java
public class CacheProxy implements DataService {
private DataService realService;
private Map<String, Object> cache = new HashMap<>();
public Data getData(String key) {
if (cache.containsKey(key)) {
return cache.get(key); // 命中缓存
}
Data data = realService.getData(key);
cache.put(key, data); // 写入缓存
return data;
}
}
🤔 八、总结与思考
代理模式的核心价值
- 单一职责:真实对象只关注核心业务,代理对象处理辅助逻辑
- 开闭原则:新增代理类不会修改原有代码
- 控制访问:代理可以决定是否允许访问真实对象