编程自学指南:java程序设计开发,Java 代理模式(Proxy)详解
一、课程信息
学习目标
- 理解代理模式的核心思想:控制对象访问
- 掌握静态代理与动态代理的实现方法
- 能通过代理模式实现日志、事务等横切功能
- 区分代理模式与装饰器、适配器的差异
二、课程导入:生活中的代理
🌰 场景 1:租房中介
- 租客(客户端)→ 中介(代理)→ 房东(真实对象)
- 代理作用:过滤无效房源、收取中介费、协调双方
🌰 场景 2:明星经纪人
- 活动方(客户端)→ 经纪人(代理)→ 明星(真实对象)
- 代理作用:筛选活动、谈判价格、处理合同
三、核心概念与分类
1. 模式定义
为其他对象提供一种代理,以控制对原对象的访问。
核心:通过代理间接访问目标对象,添加额外逻辑
2. 核心角色
角色名称 | 作用 | 租房案例类比 |
---|---|---|
抽象主题 | 定义代理与真实对象的共同接口 | Rentable 接口(有 rent () 方法) |
真实主题 | 实际业务逻辑的实现者 | Landlord 类(房东) |
代理主题 | 代理对象,持有真实主题引用 | Agent 类(中介) |
3. 代理分类
🔨 静态代理
- 代理类在编译时就已经存在
- 需手动为每个真实主题创建代理类
🌀 动态代理
- 代理类在运行时动态生成
- 一个代理类可代理多个真实主题
四、静态代理实现
🔧 案例 1:租房中介(核心案例)
步骤 1:定义抽象主题
// 租房接口
interface Rentable {
void rentHouse(); // 租房方法
}
步骤 2:实现真实主题
// 真实主题:房东
class Landlord implements Rentable {
private String houseAddress;
public Landlord(String houseAddress) {
this.houseAddress = houseAddress;
}
@Override
public void rentHouse() {
System.out.println("出租房屋:" + houseAddress);
}
}
步骤 3:实现代理主题
// 代理主题:中介
class Agent implements Rentable {
private Rentable landlord; // 持有真实主题引用
public Agent(Rentable landlord) {
this.landlord = landlord;
}
@Override
public void rentHouse() {
beforeRent(); // 代理额外逻辑
landlord.rentHouse(); // 调用真实主题方法
afterRent(); // 代理额外逻辑
}
private void beforeRent() {
System.out.println("中介带租客看房...");
}
private void afterRent() {
System.out.println("中介收取5%中介费");
}
}
步骤 4:客户端调用
public class RentClient {
public static void main(String[] args) {
Rentable landlord = new Landlord("阳光小区1栋");
Rentable agent = new Agent(landlord); // 代理包装真实对象
agent.rentHouse();
// 输出:
// 中介带租客看房...
// 出租房屋:阳光小区1栋
// 中介收取5%中介费
}
}
五、动态代理(JDK 代理)
🔧 案例 2:日志代理(核心案例)
步骤 1:定义业务接口
interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
}
步骤 2:实现真实主题
class RealCalculator implements Calculator {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public int subtract(int a, int b) {
return a - b;
}
}
步骤 3:实现 InvocationHandler
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
class LogProxy implements InvocationHandler {
private Object target; // 被代理的目标对象
public LogProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用前记录日志
System.out.println("调用方法:" + method.getName() + ",参数:" + Arrays.toString(args));
// 调用真实方法
Object result = method.invoke(target, args);
// 调用后记录日志
System.out.println("方法返回:" + result);
return result;
}
}
步骤 4:生成代理对象
import java.lang.reflect.Proxy;
public class DynamicProxyDemo {
public static void main(String[] args) {
Calculator realCalc = new RealCalculator();
// 创建InvocationHandler
InvocationHandler handler = new LogProxy(realCalc);
// 动态生成代理对象
Calculator proxy = (Calculator) Proxy.newProxyInstance(
Calculator.class.getClassLoader(),
new Class<?>[] {Calculator.class},
handler
);
int sum = proxy.add(3, 5); // 调用代理方法
}
}
六、代理模式 vs 装饰器模式
特性 | 代理模式 | 装饰器模式 |
---|---|---|
核心目的 | 控制对原对象的访问 | 扩展原对象的功能 |
调用方式 | 代理→原对象(间接调用) | 装饰器→原对象(增强调用) |
透明性 | 客户端感知不到代理的存在 | 客户端知道装饰器的存在 |
七、JDK 代理 vs CGLIB 代理
特性 | JDK 代理 | CGLIB 代理 |
---|---|---|
依赖 | 必须有接口 | 无需接口,通过继承实现 |
性能 | 较高(基于接口反射) | 较低(基于继承和字节码生成) |
常用场景 | Spring AOP(有接口时) | Spring AOP(无接口时) |
八、适用场景与优缺点
✅ 优点
- 解耦:客户端与真实主题解耦
- 扩展性:方便添加日志、事务等通用功能
- 保护:控制对敏感资源的访问
⚠️ 缺点
- 静态代理:类数量爆炸(每个接口对应一个代理类)
- 动态代理:对原始类有侵入性(需实现接口)
🔥 适用场景
- 日志记录:记录方法调用信息
- 事务管理:在方法前后开启 / 提交事务
- 远程代理:调用远程服务(如 RPC 框架)
九、课堂练习
练习 1:实现事务代理
需求:
- 为
UserService
接口添加事务代理 - 方法执行前输出 "开启事务",执行后输出 "提交事务"
练习 2:修复代理错误
错误代码:
Proxy.newProxyInstance(
RealCalculator.class.getClassLoader(),
new Class<?>[] {RealCalculator.class}, // 错误:应为接口
handler
);
十、课程总结
知识图谱:
plaintext
代理模式
↳ 核心:控制访问,中间层添加逻辑
↳ 分类:静态代理(编译时)、动态代理(运行时)
↳ 动态代理核心:InvocationHandler、Proxy.newProxyInstance
↳ 应用:日志、事务、远程调用
口诀记忆:
“代理模式像中介,访问对象要经过,
静态代理手动写,动态代理自动造,
日志事务加中间,系统解耦真奇妙!”
十一、课后作业
必做 1:实现缓存代理
需求:
- 为
DataService
接口的getData(String key)
方法添加缓存 - 首次调用时查询数据库,后续调用直接从缓存获取
必做 2:分析 Spring AOP 代理
任务
- 配置 Spring AOP,为
UserService
添加日志切面 - 说明使用的是 JDK 代理还是 CGLIB 代理