Java 设计模式・代理模式篇:从思想到代码实现
一、结构型模式
在面向对象的世界里,如何优雅地组织类与对象、构建更大的结构,是每一位开发者都会反复思考的问题。直接堆砌类和继承固然简单,但当业务复杂度上升、类间关系变得盘根错节时,这种方式就会让代码变得臃肿、难以维护。
结构型设计模式正是为了解决这一痛点而诞生的一套思想体系。它们关注如何将类或对象按某种布局组合成更大的结构,通过组合、代理、适配和装饰等手段,让代码更具灵活性、可复用性和可维护性。
在 Java 开发中,结构型模式主要包含以下 7 种经典实现:
- 代理模式 (Proxy):为其他对象提供一种代理以控制对这个对象的访问,实现延迟加载、权限控制等。
- 适配器模式 (Adapter):将一个类的接口转换成客户期望的另一个接口,让原本不兼容的类可以协同工作。Java 设计模式・适配器模式篇:从思想到代码实现-CSDN博客
- 装饰器模式 (Decorator):动态地给一个对象添加额外的职责,比生成子类更灵活地扩展功能。Java 设计模式・装饰器模式篇:从思想到代码实现-CSDN博客
- 桥接模式 (Bridge):将抽象部分与它的实现部分分离,使它们都可以独立地变化,避免类爆炸。Java 设计模式・桥接模式篇:从思想到代码实现-CSDN博客
- 外观模式 (Facade):为子系统中的一组接口提供一个统一的高层接口,使子系统更容易使用。Java 设计模式・外观模式篇:从思想到代码实现-CSDN博客
- 组合模式 (Composite):将对象组合成树形结构以表示 “部分 - 整体” 的层次关系,使客户端对单个对象和组合对象的使用具有一致性。Java 设计模式・组合模式篇:从思想到代码实现-CSDN博客
- 享元模式 (Flyweight):运用共享技术有效地支持大量细粒度的对象,减少内存占用。Java 设计模式・享元模式篇:从思想到代码实现-CSDN博客
二、代理模式
2.1 介绍
代理模式的本质是 “中间人”:
- 客户端不直接调用目标对象,而是通过代理对象间接访问。
- 代理对象可以在调用目标对象的前后插入额外逻辑,实现功能增强或访问控制,而无需修改目标对象的代码。
2.2 角色
-
抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
-
真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
-
代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
三、静态代理
注:本文代码类名为中文,方便理解
3.1 抽象主题
public interface 卖货 {
void sell();
}
3.2 真实主题
public class 网店商家 implements 卖货{
@Override
public void sell() {
System.out.println("网店商家卖货中...");
}
}
3.3 代理
public class 网购平台 implements 卖货{
网店商家 shop = new 网店商家();
@Override
public void sell() {
System.out.println("网购平台正在处理中...");
shop.sell();
}
}
3.4 客户端
public class 客户端 {
public static void main(String[] args) {
网购平台 platform = new 网购平台();
platform.sell();
}
}
客户端接触的是代理类,而不是直接接触真实主题,同时代理类还对原方法进行了增强
四、JDK动态代理
4.1 方法分析
Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。也就是说我们不需要手动再写一个代理类了
- newProxyInstance 第一个参数是代理类指定的类加载器,从被代理类中获取,第二个参数是代理类要实现的接口,从被代理类中获取,第三个参数是
InvocationHandler类型的对象,“方法调用处理器” - 所有对代理对象的方法调用,最终都会被转发到这个
InvocationHandler对象的invoke()方法中执行,第一个参数是生成的代理对象,第二个是被调用的方法,第三个是调用此方法时传入的参数
4.2 代码实现
接下来我们把代理类改造成代理工厂
public class 网购平台 {
网店商家 shop = new 网店商家();
public 卖货 getProxy(){
卖货 goods = (卖货) Proxy.newProxyInstance(
shop.getClass().getClassLoader(),
shop.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("网购平台正在代理...");
return method.invoke(shop, args);
}
}
);
return goods;
}
}
客户端
public class 客户端 {
public static void main(String[] args) {
网购平台 platform = new 网购平台();
卖货 goods = platform.getProxy();
goods.sell();
}
}
其他类不变
4.2 对比
动态代理通过 Proxy.newProxyInstance() 在运行时动态生成代理类,无需手动编写代理类代码,一个 InvocationHandler 可以代理多个实现了相同接口的目标类,扩展性极强
如果接口(卖货)新增了方法,代理逻辑无需任何修改,invoke() 方法会自动处理所有方法调用,而静态代理就要去实现新增的接口
| 对比维度 | 静态代理 | JDK 动态代理 |
|---|---|---|
| 实现方式 | 手动编写代理类,实现与目标类相同的接口 | 通过 Proxy.newProxyInstance() 在运行时动态生成代理类,无需手动编写 |
| 代理数量 | 一个代理类只能代理一个目标类 | 一个 InvocationHandler 可以代理多个实现了相同接口的目标类 |
| 接口变更影响 | 接口新增方法时,所有代理类必须实现新方法,否则编译报错 | 接口新增方法时,代理逻辑无需修改,invoke() 方法会自动处理所有方法调用 |
| 代码复用性 | 增强逻辑(如日志、事务)散落在各个代理类中,难以复用和统一维护 | 增强逻辑集中在 InvocationHandler 中,一处修改,所有代理生效 |
| 性能开销 | 编译期生成,无额外性能开销 | 运行时通过反射生成代理类并调用方法,存在一定性能开销 |
| 适用场景 | 目标类数量少、接口稳定的简单场景 | 需要对大量类进行统一增强(如 AOP、日志、事务)或在运行时确定代理对象的复杂系统 |
五、CGLIB代理
5.1 介绍
如果没有定义抽象主题接口,只定义了真实主题。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。
CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
5.2 代码实现
引入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
真实主题
public class 网店商家 {
public void sell() {
System.out.println("网店商家卖货中...");
}
}
代理工厂
public class 网购平台 implements MethodInterceptor {
网店商家 shop = new 网店商家();
public 网店商家 getProxy() {
//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
Enhancer enhancer =new Enhancer();
//设置父类的字节码对象
enhancer.setSuperclass(shop.getClass());
//设置回调函数
enhancer.setCallback(this);
//创建代理对象
网店商家 obj = (网店商家) enhancer.create();
return obj;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("网购平台正在代理...");
return methodProxy.invokeSuper(o,objects);
}
}
客户端
public class 客户端 {
public static void main(String[] args) {
网购平台 platform = new 网购平台();
网店商家 goods = platform.getProxy();
goods.sell();
}
}
5.3 对比
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。
在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。
| 对比维度 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 实现方式 | JDK 反射,运行时生成接口实现类 | ASM 字节码生成,运行时生成目标类的子类 |
| 对接口的要求 | 必须实现接口 | 无需实现接口 |
| 代理目标 | 只能代理实现了接口的类 | 可代理未实现接口的类 |
| 性能 | 反射调用,略低 | 直接调用子类方法,较高 |
| 扩展性 | 高,支持统一增强 | 高,支持无接口类代理 |
| 典型应用 | Spring AOP(当 Bean 实现接口时) | Spring AOP(当 Bean 未实现接口时) |
六、优缺点
6.1 优点
- 职责清晰:目标对象专注核心业务,代理对象处理日志、事务、权限等横切关注点。
- 高扩展性:无需修改目标对象代码,即可通过新增代理类或代理逻辑来扩展功能。
- 保护目标对象:客户端通过代理间接访问目标对象,有效隔离直接耦合,提升系统安全性。
- 透明性:代理对象与目标对象实现相同接口,客户端可无感知地使用代理替换目标对象。
6.2 缺点
- 增加复杂度:引入额外的代理类和对象,增加了系统的抽象层次和理解难度。
- 性能开销:动态代理在运行时生成类和通过反射调用,会带来一定的性能损耗。
- 调试困难:代理逻辑在运行时生成,出现问题时排查难度高于直接调用。
七、使用场景
-
远程(Remote)代理本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。
-
防火墙(Firewall)代理当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。
-
保护(Protect or Access)代理控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。
-
虚拟(Virtual)代理延迟创建开销很大的对象,先用一个轻量级的代理对象占位,直到真正需要时才实例化真实对象,从而优化系统启动速度和内存占用。
-
缓冲(Cache)代理为目标对象的操作结果提供临时缓存,在后续相同请求时直接返回缓存结果,避免重复计算或远程调用,提升响应速度。
-
智能引用(Smart Reference)代理在访问对象时执行附加操作,例如统计对象的引用次数、记录访问日志、自动释放资源等,比简单指针更智能。
八、Spring AOP
8.1 介绍
AOP 的本质是:在目标方法执行的前后,插入额外的增强逻辑。
- 客户端不再直接调用目标对象的方法,而是调用代理对象的同名方法。
- 代理对象在调用目标方法的前后,执行预先定义的增强逻辑(如
@Before、@After、@Around)。
8.2 实现
Spring AOP 会根据目标类是否实现了接口,自动选择不同的代理方式:
| 代理方式 | 适用场景 | 实现原理 |
|---|---|---|
| JDK 动态代理 | 目标类实现了接口 | 通过反射,在运行时生成实现了目标接口的代理类,所有方法调用都被转发到 InvocationHandler 的 invoke 方法中,在该方法内执行增强逻辑和目标方法。 |
| CGLIB 动态代理 | 目标类未实现接口 | 通过 ASM 字节码框架,在运行时生成目标类的子类作为代理,重写目标方法,在重写的方法中执行增强逻辑和父类(目标类)的方法。 |
8.3 优点
- 解耦:将横切关注点(日志、事务)与核心业务逻辑分离,使代码更清晰、易于维护。
- 增强:在不修改业务代码的前提下,为系统统一添加功能。
- 透明:客户端无需感知代理的存在,调用方式与直接调用目标对象完全一致。
8.4 扩展
详细内容可以看这篇博客
深入浅出 AOP:织入时机、JDK 动态代理与 CGLIB 原理及 Spring 选择策略-CSDN博客
九、其他相关设计模式
更多推荐




所有评论(0)