《设计模式系列》- 代理模式

article/2025/8/19 20:31:13

有情怀,有干货,微信搜索【三太子敖丙】关注这个有一点点东西的程序员。

本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点、资料以及我的系列文章。

设计模式已经跟大家分享很多了常见的模式了,感兴趣的小伙伴可以再回顾一下,巩固一下理解。

这次要跟大家分享的是设计模式中三大类创建型中的代理模式,代理模式在业务场景上我们可能不会经常用到,但是面试官却会经常问一个问题?

请你跟我讲讲Spring里面AOP的代理模式?jdk的代理模式和cglib的代理模式又啥区别?

清楚和不清楚的同学都可以接着向下看,一定会有收获。

言归正传,接下来开始一步一步分析一下代理模式。

定义以及目的

首先代理模式可以分为多种类型

  • 远程代理:就是将工作委托给远程对象(不同的服务器,或者不同的进程)来完成。常见的是用在web Service中。还有就是我们的RPC调用也可以理解为一种远程代理。
  • 保护代理:该模式主要进行安全/权限检查。(接触很少)
  • 缓存代理:这个很好理解,就是通过存储来加速调用,比如Sping中的@Cacheable方法,缓存特定的参数获取到的结果,当下次相同参数调用该方法,直接从缓存中返回数据。
  • 虚拟代理:这种代理主要是为方法增加功能,比如记录一些性能指标等,或进行延迟初始化

上面只是我们作为了解的概念,接下来再看看代理模式有哪些部分构成。

  • Subject(共同接口):客户端使用的现有接口
  • RealSubject(真实对象):真实对象的类
  • ProxySubject(代理对象):代理类

从图中可以看出其实整个接口还是很简单,就是一个真实对象以及代理对象。

目的:提供一个实际代理对象,以便更好的控制实际对象。 以上定义来自《设计模式之美》

代码举例实现

为了方便理解,还是举一个例子,不知道大家在读初中或者高中是否经历过传小纸条的过程,假如现在同学A 对同学C有一些话想聊(比如放学相约一起打游戏)但是因为现在是上课时间,又不能大声说,同学A和同学C之间坐了一个同学B,所以现在同学A只能是先找到同学B把纸条给它,让他转告同学C,但是去玩还是不是不去玩,那还是只能真正的同学C自己才能决定。

所以代理模式可以理解为 同学B是同学的C的代理,同学A要找同学C,只能找到同学B,通过同学B转达同学C,同时将同学的C的执行结果反馈给同学A。

说完了例子还是具体看看代码的实现吧

public interface Subject {// 共同的接口void doSomething();
}

定义一个共同的接口(即大家要做的事请:放学一起打游戏)

public class RealSubject implements Subject {// 真实对象@Overridepublic void doSomething() {System.out.println("放学去打游戏");}
}

构建一个真实对象,即例子中的同学C

public class ProxySubject implements Subject {private RealSubject realSubject;public ProxySubject(RealSubject realSubject) {this.realSubject = realSubject;}public ProxySubject() throws ClassNotFoundException, IllegalAccessException, InstantiationException {this.realSubject = (RealSubject) this.getClass().getClassLoader().loadClass("com.ao.bing.demo.proxyPattern.RealSubject").newInstance();}@Overridepublic void doSomething() {realSubject.doSomething();}public static void main(String[] args) {try {// 第一种方式new ProxySubject().doSomething();// 打印结果: 放学去打游戏} catch (Exception e) {// 异常情况,代理失败,// 传纸条的被老师抓了。或者同学C不在座位上了 等异常情况}// 第二种方式new ProxySubject(new RealSubject()).doSomething();// 打印结果: 放学去打游戏}
}

构建代理对象,即同学B,那么可以看到同学A并没有真实接触到同学C,通过同学B对同学C的代理就能知道同学C放学能不能跟他一起去打游戏

在Main方法里面,有两种方式来调用真实对象

  • 第一种:采用类加载器形式,去加载实列对象,这样我们就不同关心到底什么时候需要真实的实列化对象

  • 第二种:通过传值的形式,把实列化对象传过来。(理解为装饰器模式了)

    这里大家要区别一下,代理模式是提供完全相同的接口,而装饰器模式是为了增强接口。

静态代理、动态代理和cglib代理分析

静态代理

在上面的举的列子实现其实就是静态代理,大家可以看到整体也比较简单。但是它的缺点也很明显

静态代理需要为每一个对象都创建一个代理类,增加了维护成本以及开发成本,那么为了解决这个问题,动态代理就出来了,不要再固定为每一个需要代理的类而创建一个代理类

动态代理

动态代理合理的避免了静态代理的那种方式,不用事先为要代理的类而构建好代理类。而是在运行时通过反射机制创建。

在写动态代理事需要理解两个东西:Proxy 可以理解为就是调度器,InvocationHandler 增强服务接口可以理解为代理器。 所以我个人理解动态代理其实就是一种行为的监听。

具体的代码实现举一个例子:螳螂捕蝉,通过通过螳螂监听到蝉的动作。方便后面讲到多级代理模式。

public interface BaseService {void mainService();
}public class Cicada implements BaseService {@Overridepublic void mainService() {System.out.println("主要业务,以蝉为例,当蝉出现业务调用时,螳螂监听到");}
}

创建共同接口,以及真实对象蝉

public class PrayingMantis implements InvocationHandler {private BaseService baseService;// 这里采用的是构建传参数,可以用反射,举的第一个例子有样式代码public PrayingMantis(BaseService baseService) {this.baseService = baseService;}// 螳螂主要业务,也就是监听对象@Overridepublic Object invoke(Object listener, Method method, Object[] args) throws Throwable {method.invoke(baseService,args);secondaryMain();return null;}// 这里理解增强业务,即我们可以在实现InvocationHandler里面添加其他的业务,比如日志等等。private void secondaryMain(){System.out.println("螳螂捕蝉 - 次要业务");}
}

创建螳螂类,监听着蝉的类的动作

public class BeanFactory {public static BaseService newInstanc(Class classFile) {// 1. 创建蝉,真实类对象BaseService trueCicada = new Cicada();// 2.创建代理类 螳螂InvocationHandler prayingMantis = new PrayingMantis(trueCicada);// 3.向Jvm索要代理对象 其实就是监听的对象,Class classArray[] = {BaseService.class};BaseService baseService = (BaseService) Proxy.newProxyInstance(classFile.getClassLoader(), classArray, prayingMantis);return baseService;}// 测试Demopublic static void main(String[] args) {BaseService baseService  = newInstanc(Cicada.class);baseService.mainService();// 测试结果 :主要业务//           螳螂捕蝉 - 次要业务}
}

通过结果可以看出当蝉主要业务发生调用时,螳螂能监听到蝉的业务并且能处理其他业务逻辑,这也就是Spring里面AOP为什么能处理日志切面等。

代理的本质:

我认为其实就是一种行为的监听,对代理对象($proxy InvocationHandler)的一种监听行为。

代理模式组成:

  • 接口:声明需要被监听行为
  • 代理实现类(InvocationHandler):次要业务,次要业务和主要业务绑定执行
  • 代理对象(监听对象)

Cglib动态代理

cglib动态代理其实和jdk的动态代理是很相似的,都是要去实现代理器接口完成。

具体代码如下:

public class PrayingMantis implements MethodInterceptor {private Cicada cicada;// 代理对象public Cicada getInstance(Cicada cicada) {this.cicada = cicada;Enhancer enhancer = new Enhancer();enhancer.setSuperclass(this.cicada.getClass());enhancer.setCallback(this);return (Cicada) enhancer.create();}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {Object object = methodProxy.invokeSuper(o, objects);secondaryMain();return object;}private void secondaryMain() {System.out.println("螳螂捕蝉 - 次要业务");}public static void main(String[] args) {PrayingMantis prayingMantis = new PrayingMantis();Cicada instance = prayingMantis.getInstance(new Cicada());instance.mainService();// 结果:主要业务//      螳螂捕蝉 - 次要业务}

因为蝉类都是一样的所以我就不单独这里再贴出来。

细心的同学已经发现,Cglib 无需通过接口来实现,它是通过实现子类的方式来完成调用的。

Enhancer 对象把代理对象设置为被代理类的子类来实现动态代理的。因为是采用继承方式,所以代理类不能加final修饰,否则会报错。

final类:类不能被继承,内部的方法和变量都变成final类型

JDK和Cglib的区别:

jdk动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理

cglib动态代理是利用ASM开源包,对被代理对象类的class文件加载进来,通过修改其字节码生成子类来处理

ASM: 一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。 – 以上ASM解释来自简书

多级动态代理

看完上面的动态代理,不知道大家有没有想法,实现一个多级动态代理。

还是以螳螂捕蝉为例子,再加上一个黄雀在后,实现多级动态代理模式。

public class Cardinal implements InvocationHandler {// 监听代理代理对象private Object proxyOne;public Cardinal(Object proxyOne) {this.proxyOne = proxyOne;}// 螳螂主要业务,也就是监听对象@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws            Throwable {method.invoke(proxyOne, args);secondaryMain();return null;}private void secondaryMain() {System.out.println("黄雀吃螳螂 - 次要业务");}
}

创建一个黄雀代理对象,那作为他的真实对象就变成螳螂了,当螳螂对象发生调用时,黄雀就能坚挺到,同时作出对应业务逻辑

public class BeanFactory {public static BaseService newInstanc(Class classFile) {// 1. 创建蝉,真实类对象BaseService trueCicada = new Cicada();// 2.创建代理类 螳螂InvocationHandler prayingMantis = new PrayingMantis(trueCicada);// 3.向Jvm索要代理对象 其实就是坚挺的对象Class classArray[] = {BaseService.class};BaseService baseService = (BaseService) Proxy.newProxyInstance(classFile.getClassLoader(), classArray, prayingMantis);// 4.创建代理实现类 黄雀 二级代理InvocationHandler cardinal = new Cardinal(baseService);BaseService secondBaseService = (BaseService) Proxy.newProxyInstance(classFile.getClassLoader(), classArray, cardinal);// 假设要实现三级,四级代理,则在黄雀类上再加一层代理即可实现。// 省略其他的更多级代理对象return secondBaseService;}// 测试demopublic static void main(String[] args) {BaseService baseService  = BeanFactory.newInstanc(Cicada.class);baseService.mainService();// 结果:主要业务//      螳螂捕蝉 - 次要业务//      黄雀吃螳螂 - 次要业务}
}

根据这个代码基本就实现多级代理过程。螳螂监听着蝉类的动作,黄雀监听着螳螂类的动作。

同样的如果要实现三级代理,四级代理也就不是什么难事了,在每一层的上面再加一个代理对象就可以了。

动态代理本质还是可以理解为将“次要业务”与“主要业务”解耦合,让开发者能更加专注于主要业务,提升开发效率,以及维护成本。

总结

代理模式在业务代码上我个人认为是比较少见的,特别是动态代理基本上是没有见过。但是代理模式也是我们必须要理解的一种模式,因为学习好代理模式有助于我们去读一些源码,排查一些更深层次的问题,或者面对一些业务场景问题,也能有一个很大的提升,设计模式本身也就是为了解决问题而创建出来的。

理解完动态代理现在对我们来说AOP的实现原理也就不言而喻了。

详细的设计模式到这里就结束了,后面针对一些不常见设计模式我还是会给大家做一个总结吧。

我是敖丙,你知道的越多,你不知道的越多,我们下期见!!!


敖丙把自己的面试文章整理成了一本电子书,共 1630页!

干货满满,字字精髓。目录如下,还有我复习时总结的面试题以及简历模板,现在免费送给大家。

链接:https://pan.baidu.com/s/1ZQEKJBgtYle3v-1LimcSwg 密码:wjk6

我是敖丙,你知道的越多,你不知道的越多,感谢各位人才的:点赞收藏评论,我们下期见!


文章持续更新,可以微信搜一搜「 三太子敖丙 」第一时间阅读,回复【资料】有我准备的一线大厂面试资料和简历模板,本文 GitHub https://github.com/JavaFamily 已经收录,有大厂面试完整考点,欢迎Star。


http://chatgpt.dhexx.cn/article/gJY74E3K.shtml

相关文章

Nginx代理tomcat

为什么需要为tomcat配置nginx反向代理? 1.当服务器上同时拥有nginx与tomcat时,tomcat修改8080端口为80会冲突 2.tomcat不更改监听端口8080即可使用nginx的80端口 3.Nginx对于静态的请求速度上要优于Tomcat,Tomcat不擅长做高并发的静态文件请…

GPU虚拟化

GPU 虚拟化技术 须知: 文章内容大程度参考B站王利明老师对《GPU虚拟化技术分享》的主题演讲 视频链接: https://b23.tv/uQKBpcK 1 GPU 和软件架构 GPU可以用于图形渲染,GPU 作为加速图形绘制的芯片时,它主要面向的产品主要是会集中在 PC 和游戏两个市场…

代理模式(四):代理模式效果与适用场景

15.7 代理模式效果与适用场景 代理模式是常用的结构型设计模式之一,它为对象的间接访问提供了一个解决方案,可以对对象的访问进行控制。代理模式类型较多,其中远程代理、虚拟代理、保护代理等在软件开发中应用非常广泛。 15.7.1 模式优点 代理…

Nginx 反向代理、负载均衡、虚拟主机

文章目录 一、反向代理1、代理原理2、正/反向代理的区别(1)正向代理(2)反向代理 3、配置Nginx-Proxy(1)代理模块(2)代理配置(3) proxy 代理实例 二、负载均衡…

设计模式之代理模式

定义 代理模式又叫委托模式,是为某个对象提供一个代理对象,并且由代理对象控制对原对象的访问。代理模式通俗来讲就是我们生活中常见的中介。 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原真实对象的前提下&a…

java设计模式9:Proxy(3)虚拟代理

加载延迟可以提高软件的友好程度。 当一个真实的主题对象的加载需要耗费资源时,一个虚拟代理对象可以代替真实对象接受请求。一旦接到请求,代理对象马上打出一段“正在加载”的信息,并在适当的时候加载真实主题对象,也就是模块或…

设计模式:(代理模式)

1.定义 在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。 在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。 简单来说:代理模式为其他对象提…

Nginx 虚拟主机与反向代理

一、虚拟主机 虚拟主机使用的是特殊的软硬件技术,它把一台运行在因特网上的服务器主机分成一台台“虚拟”的主机,每台虚拟主机都可以是一个独立的网站,可以具有独立的域名,具有完整的Intemet服务器功能(WWW、FTP、Emai…

【设计模式】学习笔记16:代理模式之虚拟代理(实现CD封面加载器)

本文出自 http://blog.csdn.net/shuangde800 在上篇中,我们学习了代理模式,并用Java RMI实现了一个最简单的远程代理。 实际上代理模式并不仅仅应用与远程代理,还有很多其他的应用。 比如:虚拟代理。 代理模式可以以很多形式出现…

代理模式(三):远程代理,虚拟代理,缓冲代理

15.4 远程代理 远程代理(Remote Proxy)是一种常用的代理模式,它使得客户端程序可以访问在远程主机上的对象,远程主机可能具有更好的计算性能与处理速度,可以快速响应并处理客户端的请求。远程代理可以将网络的细节隐藏起来,使得客…

虚拟机 全局代理 主机代理_比较虚拟代理与真实代理的性能

云计算的关键基础是虚拟化。 面向云的设计人员,开发人员和管理员需要问自己的一个问题是:“虚拟化组件的性能水平如何与其“真实”物理对应物相提并论?” “如果存在负面差距,我该如何克服呢?” 本文介绍了在虚拟机&a…

设计模式——代理模式(虚拟代理)

代理模式的类型分为: (1)虚拟代理 (2)远程代理 (3)智能指引 (4)保护代理 这一篇主要讲虚拟代理,想要知道其他类型讲解的小伙伴可以去我其他博客翻一翻哦。 首先来理解一波虚拟代理,啥叫虚拟代理? 举个很常见也很通俗的例子,咱们平时抽奖的时候,是不是都想要抽个…

[转载]虚拟代理模式(Virtualnbsp;Proxy)

虚拟代理模式(Virtualnbsp;Proxy) 第25章 虚拟代理模式(Virtual Proxy) 描述: 虚拟代理模式 (Virtual Proxy)是一种节省内存的技术,它建议创建那些占用大量内存或处理复杂的对象时,把创建这类对象推迟到使用它的时候。在特定的应用 中&#x…

代理模式——虚拟代理(二)

代理模式定义 为另一个对象提供一个替身或占位符以控制对这个对象的访问。使用代理模式创建代表对象,让代表对象控制对某对象的访问,被代理的对象可是远程的对象、创建开销大的对象或需要安全控制的对象。 代理分三种: 1.远程代理&#xff…

SQL语法与数据库快速入门(1)

目录 数据库简介数据库分类常用数据库简介使用场景MySql 的安装与配置数据库客户端工具MySql 介绍SQL 简介DDL 数据库操作-创建DDL 数据库操作-查看DDL 数据库操作-修改DDL 数据库操作-删除DDL 数据库表操作简介DDL 数据库表操作-创建DDL 数据库表操作-查看DDL 数据库表操作-修…

SQL语法与数据库快速入门(2)

目录: 多表简介SQL 约束-外键约束多表关系简介多表查询多表查询-内连接查询多表查询-外连接查询子查询简介子查询实战数据库进阶redis 内存数据库mongodb nosql 数据库neo4j 图数据库 1.多表简介 多表及使用场景介绍: 多表顾名思义就是在数据库设计中…

【数据库】MYSQL轻松入门

文章目录 MYSQL入门一、MYSQL概述1. 数据库相关概念1.1 数据库,数据库管理系统与SQL1.2 数据库种类以及主流数据库管理系统排名1.2.1 数据库的种类1.2.2 数据库主流排名 1.3 MySQL数据库安装1.3.1 下载1.3.2 修改密码 1.4 数据模型 二、SQL2.1 通用语法与注释2.2 SQ…

Java连接mysql数据库的五分钟快速入门教程

总体流程:数据库->Java 总体步骤: 1.创建数据库并新建表 2.创建一个Java项目,在项目下新建文件夹lib,类型为Directory 3.将下载好mysql-connector-java-8.0.27.jar放到lib目录下 4.将lib下的依赖添加到Java项目中 5.编写代码连…

数据库快速入门教程--视频

数据库快速入门教程--视频 下载地址:http://v.51work6.com/courseInfoRedirect.do?actioncourseInfo&courseId240579本课程是这个课程体系的核心之一,为软件开发人员所需数据库知识的学习教材,而不是培训一个DBA(数据库管理员…

Docker 安装与操作 Mysql 数据库快速入门

目录 演示环境说明 Mysql 镜像检索 Mysql 镜像下载 Mysql 容器运行 错误运行方式 正确运行方式 端口映射 连接测试 高级命令指定编码运行 演示环境说明 1、《 Docker 镜像操作 常用命令》中说明了如何从 Docker Hub 上下载镜像,《 Docker 容器操作 常用命…