
🍐⚱️:(代理模式举例)爱因斯坦和他司机的故事——司机负责演讲,有人提问的时候就叫爱因斯坦起来回答问题 [1]。
小故事:爱因斯坦和他的司机
爱因斯坦提出相对论后,震惊世界,于是被很多大学邀请去做报告,爱因斯坦因此而被弄得疲惫不堪。
有一天,司机对他说:“你太累了,今天我帮你作报告吧?”
爱因斯坦问:“你能行吗?”
司机说:“我闭着眼睛都能背出来。”
那天司机上台,果然讲得滴水不漏。
但刚想下台时,一位博士站了起来,然后提了一个非常深奥刁钻的问题。
司机不知怎么作答,幸好脑瓜转得快:“你这问题太简单了,我司机都能回答。”
爱因斯坦站起来,几句话就解决了问题。
博士惊呆了:“没想到他的司机也远胜于我。”
但在回去的路上,司机对爱因斯坦说:“我知道的只是概念,你懂得的才是知识。”
代理模式可以在不改变原始类(被代理类)代码的情况下,通过引入代理类来给原始类附加功能。
登场角色:
Subject(主体):Subject 角色定义了使 Proxy 角色和 RealSubject 角色之间具有一致性的接口。由于存在 Subject 角色,所以 Client 角色不必在意它所使用的究竟是 Proxy 角色还是 RealSubject 角色。Proxy(代理人):Proxy 角色会尽量处理来自 Client 角色的请求。只有当自己不能处理时,它才会将工作交给 RealSubject 角色。Proxy 角色只有在必要时才会生成 RealSubject 角色。Proxy 角色实现了在 Subject 角色中定义的接口(API)。在示例程序中,由 PrinterProxy 类扮演此角色。RealSubject(实际的主体、被代理类):“本人”RealSubject 角色会在「代理人 Proxy」角色无法胜任工作时出场。它与 Proxy 角色一样,也实现了在 Subject 角色中定义的接口(API)。RealSubject 并不知道 Proxy 的存在。Client(请求者):使用 Proxy 模式的角色。在 GoF 书中,Client 角色并不包含在代理模式中。
代码示例:
1 | interface Subject { |
使用方式:
1 | public static void main(String[] args) { |
代理模式在访问实际对象时引入一定程度的间接性,利用这种间接性我们可以附加多种用途。拓展思路:
Proxy 类和 RealSubject 类,而是直接在 RealSubject 类中加入惰性求值功能(即只有必要时才生成实例的功能)。不过划分 Proxy 和 RealSubject 角色可以使它们成为独立的组件,在修改的时候也不会互相之间产生影响(分而治之)。Main 中 Client 直接使用 Subject 接口,Main 不必在意调用的究竟是哪个类。在这种情况下,可以说 Proxy 类是具有「透明性」的。打印机案例

Printer 打印机 print() 函数调用了 heavyJob 函数。PrinterProxy 不实现真正的 print 函数。 realize 函数用于实现真正的打印机,这个函数只在调用了代理类的 print 函数时调用。不论 setPrinterName 方法和 getPrinterName 方法被调用多少次,都不会生成 Printer 类的实例。只有当真正需要本人时,才会生成 Printer 类的实例。Client 应当使用抽象 Subject 才对| 优点 | 缺点 |
|---|---|
| 可以在客户端毫无察觉的情况下控制服务对象。 | 代码可能会变得复杂,因为需要新建许多类。 |
| 如果客户端对服务对象的生命周期没有特殊要求,可以对生命周期进行管理。 | 服务响应可能会延迟。 |
| 即使服务对象还未准备好或不存在,代理也可以正常工作。 | |
| 开闭原则。可以在不对服务或客户端做出修改的情况下创建新代理。 |
相关的设计模式:
Proxy 角色与 RealSubject 角色的接口(API)是相同的(透明性)。在上面的例子中,代理类 Proxy 和被代理类 RealSubject 都继承同一个接口。如果被代理类没有定义接口,且无法修改源码(如第三方类库的类),我们就不能用上面的方法实现代理模式。
解决方案:继承。让代理类继承原始类,然后扩展附加功能。

代理类 Proxy 通过 super.request1() 的方式委托被代理类。
由程序员创建或特定工具自动生成代理模式的源代码,即在编译前就已经将接口,被代理类,代理类等确定下来的代理方式,叫做静态代理。
如果通过静态代理的方式实现代理模式,对于每个代理类 Proxy,我们都需要将被代理类的所有方法重新实现一遍,且代理类 Proxy 每个方法都有相似的代码逻辑。如果被代理类很多,那么代理类也会很多,增加代码的维护成本。
动态代理:不事先为每个原始类编写代理类,而是在运行的时候,根据我们在 Java 代码中的「指示」,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
| 代理方式 | 静态代理 | 动态代理 |
|---|---|---|
| JVM | 编译时生成代理类的 class 文件 | 运行时动态生成类字节码并加载到 JVM |
| 灵活性 | 🔴接口一旦新增加方法,目标对象和代理对象都要进行修改 | 🟢不需要针对每个目标类都创建一个代理类 |
Java 标准库提供了动态代理机制,可以在运行期动态创建某个 interface 的实例,不用编写实现类。动态代理依赖 Java 站内文章反射 语法。
Java 常用的动态代理实现方式有:
| 动态代理 | JDK | CGLIB |
|---|---|---|
| 代理对象 | 实现了接口的类或者直接代理接口 | 未实现任何接口的类 |
| 效率 | 🟢 | 🟡 |
| 特点 | 简单易用 | 引入第三方库 |
在 Spring 中的 站内文章AOP 模块中,如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
假设,代理类 Proxy 在调用被代理类前都需要执行前置方法和后置方法。在静态代理方式,我们需要将 Proxy 的 request1、request2、request3 都改动代码。
1 | public class Proxy implements Subject{ |
在 Java 的 java.lang.reflect 包下提供了一个 Proxy 类和一个 InvocationHandler 接口,通过这个类和这个接口可以生成 JDK 动态代理类和动态代理对象。
JDK 动态代理类使用步骤:
InvocationHandler 并重写 invoke 方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;下面是示例代码:
1 | class MyInvocationHandler<T> implements InvocationHandler { |
主函数使用动态代理:
1 | public class ProxyTest { |
当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现 InvocationHandler 接口类的 invoke 方法来调用。
此处动态代理的优势体现为:可以方便对代理类的函数进行统一处理,不用修改代理类中的方法。因为代理类中的方法,是通过 InvocationHandler 中的 invoke 方法调用的,所以我们只要在 invoke 方法中统一处理,就可以对所有被代理的方法进行相同的操作了。
在上面的 main 代码调用中,我们创建实现了 Subject 接口的代理类还能通过 站内文章引入工厂 进一步优化。
1 | class SubjectProxyFactory { |
main 代码的调用简化为:
1 | Subject subject = new RealSubject(); |
通读 JDK 动态代理实现源码,我们可以简单理解为 JVM 帮我们自动编写了一个下面的类。这个类时动态生成的。
1 |
|
上面 JVM 动态生成的代码中 implements Subject 注定了 JDK 动态代理方式只能代理接口,而不能代理类。
CGLIB(Code Generation Library)是一个基于 ASM 的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。
在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。
CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法,private 方法也无法代理。
1 |
|

业务系统中存在一些附加功能,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们可以将将这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发。在 Spring 中,这部分的工作可以交给 AOP 完成。
详看文章:站内文章简易 RPC 调用框架的实现
假设我们要开发一个接口请求的缓存功能,对于某些接口请求,如果入参相同,在设定的过期时间内,直接返回缓存结果,而不用重新进行逻辑处理。比如,针对获取用户个人信息的需求,我们可以开发两个接口,一个支持缓存,一个支持实时查询。对于需要实时数据的需求,我们让其调用实时查询接口,对于不需要实时数据的需求,我们让其调用支持缓存的接口。
参考实现方式:在应用启动的时候,我们从配置文件中加载需要支持缓存的接口,以及相应的缓存策略(比如过期时间)等。当请求到来的时候,我们在 AOP 切面中拦截请求,如果请求中带有支持缓存的字段(比如 http://...?..&cached=true),我们便从缓存(内存缓存或者 Redis 缓存等)中获取数据直接返回。
1 | interface Subject{ |
1 | class RealSubject{ |

🍐⚱️:(代理模式举例)爱因斯坦和他司机的故事——司机负责演讲,有人提问的时候就叫爱因斯坦起来回答问题 [1]。
小故事:爱因斯坦和他的司机
爱因斯坦提出相对论后,震惊世界,于是被很多大学邀请去做报告,爱因斯坦因此而被弄得疲惫不堪。
有一天,司机对他说:“你太累了,今天我帮你作报告吧?”
爱因斯坦问:“你能行吗?”
司机说:“我闭着眼睛都能背出来。”
那天司机上台,果然讲得滴水不漏。
但刚想下台时,一位博士站了起来,然后提了一个非常深奥刁钻的问题。
司机不知怎么作答,幸好脑瓜转得快:“你这问题太简单了,我司机都能回答。”
爱因斯坦站起来,几句话就解决了问题。
博士惊呆了:“没想到他的司机也远胜于我。”
但在回去的路上,司机对爱因斯坦说:“我知道的只是概念,你懂得的才是知识。”
代理模式可以在不改变原始类(被代理类)代码的情况下,通过引入代理类来给原始类附加功能。
登场角色:
Subject(主体):Subject 角色定义了使 Proxy 角色和 RealSubject 角色之间具有一致性的接口。由于存在 Subject 角色,所以 Client 角色不必在意它所使用的究竟是 Proxy 角色还是 RealSubject 角色。Proxy(代理人):Proxy 角色会尽量处理来自 Client 角色的请求。只有当自己不能处理时,它才会将工作交给 RealSubject 角色。Proxy 角色只有在必要时才会生成 RealSubject 角色。Proxy 角色实现了在 Subject 角色中定义的接口(API)。在示例程序中,由 PrinterProxy 类扮演此角色。RealSubject(实际的主体、被代理类):“本人”RealSubject 角色会在「代理人 Proxy」角色无法胜任工作时出场。它与 Proxy 角色一样,也实现了在 Subject 角色中定义的接口(API)。RealSubject 并不知道 Proxy 的存在。Client(请求者):使用 Proxy 模式的角色。在 GoF 书中,Client 角色并不包含在代理模式中。
代码示例:
1 | interface Subject { |
使用方式:
1 | public static void main(String[] args) { |
代理模式在访问实际对象时引入一定程度的间接性,利用这种间接性我们可以附加多种用途。拓展思路:
Proxy 类和 RealSubject 类,而是直接在 RealSubject 类中加入惰性求值功能(即只有必要时才生成实例的功能)。不过划分 Proxy 和 RealSubject 角色可以使它们成为独立的组件,在修改的时候也不会互相之间产生影响(分而治之)。Main 中 Client 直接使用 Subject 接口,Main 不必在意调用的究竟是哪个类。在这种情况下,可以说 Proxy 类是具有「透明性」的。打印机案例

Printer 打印机 print() 函数调用了 heavyJob 函数。PrinterProxy 不实现真正的 print 函数。 realize 函数用于实现真正的打印机,这个函数只在调用了代理类的 print 函数时调用。不论 setPrinterName 方法和 getPrinterName 方法被调用多少次,都不会生成 Printer 类的实例。只有当真正需要本人时,才会生成 Printer 类的实例。Client 应当使用抽象 Subject 才对| 优点 | 缺点 |
|---|---|
| 可以在客户端毫无察觉的情况下控制服务对象。 | 代码可能会变得复杂,因为需要新建许多类。 |
| 如果客户端对服务对象的生命周期没有特殊要求,可以对生命周期进行管理。 | 服务响应可能会延迟。 |
| 即使服务对象还未准备好或不存在,代理也可以正常工作。 | |
| 开闭原则。可以在不对服务或客户端做出修改的情况下创建新代理。 |
相关的设计模式:
Proxy 角色与 RealSubject 角色的接口(API)是相同的(透明性)。在上面的例子中,代理类 Proxy 和被代理类 RealSubject 都继承同一个接口。如果被代理类没有定义接口,且无法修改源码(如第三方类库的类),我们就不能用上面的方法实现代理模式。
解决方案:继承。让代理类继承原始类,然后扩展附加功能。

代理类 Proxy 通过 super.request1() 的方式委托被代理类。
由程序员创建或特定工具自动生成代理模式的源代码,即在编译前就已经将接口,被代理类,代理类等确定下来的代理方式,叫做静态代理。
如果通过静态代理的方式实现代理模式,对于每个代理类 Proxy,我们都需要将被代理类的所有方法重新实现一遍,且代理类 Proxy 每个方法都有相似的代码逻辑。如果被代理类很多,那么代理类也会很多,增加代码的维护成本。
动态代理:不事先为每个原始类编写代理类,而是在运行的时候,根据我们在 Java 代码中的「指示」,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
| 代理方式 | 静态代理 | 动态代理 |
|---|---|---|
| JVM | 编译时生成代理类的 class 文件 | 运行时动态生成类字节码并加载到 JVM |
| 灵活性 | 🔴接口一旦新增加方法,目标对象和代理对象都要进行修改 | 🟢不需要针对每个目标类都创建一个代理类 |
Java 标准库提供了动态代理机制,可以在运行期动态创建某个 interface 的实例,不用编写实现类。动态代理依赖 Java 站内文章反射 语法。
Java 常用的动态代理实现方式有:
| 动态代理 | JDK | CGLIB |
|---|---|---|
| 代理对象 | 实现了接口的类或者直接代理接口 | 未实现任何接口的类 |
| 效率 | 🟢 | 🟡 |
| 特点 | 简单易用 | 引入第三方库 |
在 Spring 中的 站内文章AOP 模块中,如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
假设,代理类 Proxy 在调用被代理类前都需要执行前置方法和后置方法。在静态代理方式,我们需要将 Proxy 的 request1、request2、request3 都改动代码。
1 | public class Proxy implements Subject{ |
在 Java 的 java.lang.reflect 包下提供了一个 Proxy 类和一个 InvocationHandler 接口,通过这个类和这个接口可以生成 JDK 动态代理类和动态代理对象。
JDK 动态代理类使用步骤:
InvocationHandler 并重写 invoke 方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;下面是示例代码:
1 | class MyInvocationHandler<T> implements InvocationHandler { |
主函数使用动态代理:
1 | public class ProxyTest { |
当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现 InvocationHandler 接口类的 invoke 方法来调用。
此处动态代理的优势体现为:可以方便对代理类的函数进行统一处理,不用修改代理类中的方法。因为代理类中的方法,是通过 InvocationHandler 中的 invoke 方法调用的,所以我们只要在 invoke 方法中统一处理,就可以对所有被代理的方法进行相同的操作了。
在上面的 main 代码调用中,我们创建实现了 Subject 接口的代理类还能通过 站内文章引入工厂 进一步优化。
1 | class SubjectProxyFactory { |
main 代码的调用简化为:
1 | Subject subject = new RealSubject(); |
通读 JDK 动态代理实现源码,我们可以简单理解为 JVM 帮我们自动编写了一个下面的类。这个类时动态生成的。
1 |
|
上面 JVM 动态生成的代码中 implements Subject 注定了 JDK 动态代理方式只能代理接口,而不能代理类。
CGLIB(Code Generation Library)是一个基于 ASM 的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。
在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。
CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法,private 方法也无法代理。
1 |
|

业务系统中存在一些附加功能,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们可以将将这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发。在 Spring 中,这部分的工作可以交给 AOP 完成。
详看文章:站内文章简易 RPC 调用框架的实现
假设我们要开发一个接口请求的缓存功能,对于某些接口请求,如果入参相同,在设定的过期时间内,直接返回缓存结果,而不用重新进行逻辑处理。比如,针对获取用户个人信息的需求,我们可以开发两个接口,一个支持缓存,一个支持实时查询。对于需要实时数据的需求,我们让其调用实时查询接口,对于不需要实时数据的需求,我们让其调用支持缓存的接口。
参考实现方式:在应用启动的时候,我们从配置文件中加载需要支持缓存的接口,以及相应的缓存策略(比如过期时间)等。当请求到来的时候,我们在 AOP 切面中拦截请求,如果请求中带有支持缓存的字段(比如 http://...?..&cached=true),我们便从缓存(内存缓存或者 Redis 缓存等)中获取数据直接返回。
1 | interface Subject{ |
1 | class RealSubject{ |