设计模式之避免浪费_Proxy代理模式_必要时生成实例
前言
Github:https://github.com/HealerJean
一、代理模式(Proxy Pattern)
1. 模式概述
代理模式 是一种 结构型设计模式,它为其他对象提供一个 代理或占位符,以控制对这个对象的访问。
核心思想:“我不直接见你,先让代理人处理。”
代理模式 = 中介 + 增强 + 控制
- 它不是“多此一举”,而是“安全可控的封装”。
类比理解:
- 房产中介:你不能直接联系房东,需通过中介看房、谈价;
- 公司前台:访客不能直接进办公室,需登记并由前台通知;
- 网络代理服务器:客户端不直连目标服务器,而是通过代理中转。
2. 使用场景
代理模式适用于以下情况:
- 控制访问权限(如远程、安全、智能引用);
- 延迟初始化(创建开销大的对象时按需加载);
- 添加日志、缓存、事务等横切逻辑;
- 隐藏目标对象的复杂性(如远程服务调用)。
📌 典型应用:
- Spring AOP(基于 JDK/CGLIB 代理);
- MyBatis 延迟加载;
- RMI 远程方法调用;
- 图片懒加载(先显示占位图,再加载真实图片)。
3、示例程序:买房代理系统(优化版)
1)接口与目标类
/**
* 买房接口(Subject)
*/
public interface BuyHouse {
void buyHouse(String address);
}
/**
* 真实买房人(Real Subject)
*/
public class BuyHouseImpl implements BuyHouse {
@Override
public void buyHouse(String address) {
System.out.println("【真实操作】正在购买房产: " + address);
}
}
2)静态代理(增强版)
/**
* 静态代理(Static Proxy)
* 在调用前后添加业务逻辑
*/
public class BuyHouseProxy implements BuyHouse {
private final BuyHouse target;
public BuyHouseProxy(BuyHouse target) {
this.target = target;
}
@Override
public void buyHouse(String address) {
// 前置处理:身份验证
System.out.println("【代理】验证客户购房资格...");
// 调用真实对象
target.buyHouse(address);
// 后置处理:记录日志
System.out.println("【代理】已记录本次购房行为到审计日志");
}
}
3)JDK 动态代理(通用日志代理)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 通用日志动态代理处理器
*/
public class LoggingInvocationHandler implements InvocationHandler {
private final Object target;
public LoggingInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("【JDK代理】调用方法: " + method.getName() + ", 参数: " + Arrays.toString(args));
Object result = method.invoke(target, args);
System.out.println("【JDK代理】方法执行完毕");
return result;
}
}
public class JdkProxyTest {
public static void main(String[] args) {
BuyHouse real = new BuyHouseImpl();
InvocationHandler handler = new LoggingInvocationHandler(real);
BuyHouse proxy = (BuyHouse) Proxy.newProxyInstance(
BuyHouse.class.getClassLoader(),
new Class[]{BuyHouse.class},
handler
);
proxy.buyHouse("北京市朝阳区");
}
}
4)CGLIB 动态代理(无接口场景)
适用场景:目标类 没有实现接口,JDK 代理无法使用。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* CGLIB 代理:无需接口,通过继承实现
*/
public class CglibProxy implements Method特派员 {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("【CGLIB代理】前置处理: " + method.getName());
Object result = proxy.invokeSuper(obj, args); // 调用父类(目标类)方法
System.out.println("【CGLIB代理】后置处理完成");
return result;
}
@SuppressWarnings("unchecked")
public <T> T createProxy(Class<T> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return (T) enhancer.create();
}
}
// 假设有一个没有实现接口的类
public class HouseService {
public void purchase(String location) {
System.out.println("【无接口类】购买房产: " + location);
}
}
// 测试
public class CglibTest {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
HouseService service = proxy.createProxy(HouseService.class);
service.purchase("上海市浦东新区");
}
}
4、FQA
1)代理模式分类
- JDK 代理:基于接口,轻量,Java 原生支持;
- CGLIB 代理:基于继承,可代理类,性能略高,需引入第三方库。
| 类型 | 特点 | 示例 |
|---|---|---|
| 静态代理 | 编译期确定,手动编写代理类 | BuyHouseProxy |
| JDK 动态代理 | 运行时生成代理类,仅支持接口 | Proxy.newProxyInstance() |
| CGLIB 动态代理 | 运行时生成子类,支持类(无需接口) | Enhancer.create() |
| 其他代理 | 远程代理、虚拟代理、保护代理等 | RMI、懒加载、权限控制 |
2)JDK 代理 vs CGLIB 代理
- 目标对象 实现了接口 → 使用 JDK 代理;
- 目标对象 未实现接口 → 使用 CGLIB 代理。
| 对比项 | JDK 动态代理 |
CGLIB 动态代理 |
|---|---|---|
| 实现方式 | 基于接口(Proxy + InvocationHandler) |
基于继承(生成子类) |
| 是否需要接口 | 必须 | 不需要 |
| 性能 | 略低(反射调用) | 略高(FastClass 机制) |
| 限制 | 不能代理类(只能代理接口) | 不能代理 final 类/方法 |
| 依赖 | Java 原生 |
需引入 cglib 库 |
| Spring 默认 | 有接口时用 JDK |
无接口时用 CGLIB |
3)模式优点
- 职责分离:真实对象专注核心逻辑,代理处理辅助功能;
- 增强灵活性:可在不修改目标类的情况下扩展行为;
- 控制访问:实现懒加载、权限校验、远程调用等;
- 符合开闭原则:新增代理无需改动原有代码。
4)注意事项
- 静态代理:每个目标类需手写代理,维护成本高;
- 动态代理:性能略低于直接调用(但可接受);
- CGLIB:不能代理
final类或方法; - 调试复杂度:动态生成的代理类增加排查难度。


