设计模式之代理模式

article/2025/8/19 20:25:57

定义

代理模式又叫委托模式,是为某个对象提供一个代理对象,并且由代理对象控制对原对象的访问。代理模式通俗来讲就是我们生活中常见的中介。
我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原真实对象的前提下,提供额外的功能操作,扩展真实对象的功能。比如说在真实对象的某个方法执行前后你可以增加一些自定义的操作。

类型

结构型

UML图

在这里插入图片描述

角色

抽象角色(Subject):通过接口或抽象类声明真实角色实现的业务方法。
代理角色(Proxy):实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
真实角色(RealSubject):实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

示例

静态代理

静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。

静态代理实现步骤:

  1. 定义一个接口及其实现类;
  2. 创建一个代理类同样实现这个接口
  3. 将真实目标对象注入进代理类,然后在代理类的对应方法调用真实目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对真实目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。是不是感觉和spring AOP 面向切面编程很像。

我们每次在Controller层中实现向数据库insert数据,一般都是经过Service层、Dao层,再到数据库,这里我们将Service层进行代理。

Order类,实体类。

public class Order {private Object orderInfo;private Integer userId;public Object getOrderInfo() {return orderInfo;}public void setOrderInfo(Object orderInfo) {this.orderInfo = orderInfo;}public Integer getUserId() {return userId;}public void setUserId(Integer userId) {this.userId = userId;}
}

Dao层接口

public interface IOrderDao {int insert(Order order);
}

Dao层实现类

public class OrderDaoImpl implements IOrderDao {public int insert(Order order) {System.out.println("Dao层添加Order成功");return 1;}
}

Service接口

public interface IOrderService {int saveOrder(Order order);
}

Service实现类

public class OrderServiceImpl implements IOrderService {private IOrderDao iOrderDao;public int saveOrder(Order order) {// 使用Spring注解会自己注入,这里就自己直接newiOrderDao = new OrderDaoImpl();System.out.println("Service层调用Dao层添加Order");return iOrderDao.insert(order);}
}

OrderServiceStaticProxy类(Service层的静态代理类)

public class OrderServiceStaticProxy {private IOrderService iOrderService;public int saveOrder(Order order){//方法增强,比如分库、校验、安全等处理(前置方法)beforeMethod(order);iOrderService = new OrderServiceImpl();int result = iOrderService.saveOrder(order);//方法增强,比如释放资源等处理(后置方法)afterMethod();return result;}private void beforeMethod(Order order){int userId = order.getUserId();//分库int dbRouter = userId % 2;System.out.println("静态代理分配到【db"+dbRouter+"】处理数据");//todo 设置dataSource;DataSourceContextHolder.setDBType("db"+String.valueOf(dbRouter));System.out.println("开始执行代理任务");System.out.println("静态代理 before code");}private void afterMethod(){System.out.println("静态代理 after code");System.out.println("执行完毕");}}

Test(应用层)

public class Test {public static void main(String[] args) {Order order = new Order();order.setUserId(2);OrderServiceStaticProxy orderServiceStaticProxy = new OrderServiceStaticProxy();orderServiceStaticProxy.saveOrder(order);}
}

输出结果
在这里插入图片描述

动态代理

相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,通过动态代理类我们可以完成全部的代理功能。

静态代理是在代码编译后就已经确定被代理的对象了。
动态代理是在代码运行时,通过反射机制在运行时动态生成类字节码并加载到JVM中的,也就是说,在运行过程中才确立要代理的对象。这样能够代理各种类型的对象。

Spring AOP、RPC 框架的实现都依赖了动态代理,动态代理在框架中几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。

对于Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理、CGLIB 动态代理等等

JDK动态代理简单介绍

JDK动态代理,java.lang.reflect.InvocationHandler接口java.lang.reflect.Proxy类是核心。

Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
  • loader :类加载器,用于加载代理对象。
  • interfaces : 被代理类实现的一些接口;
  • h : 实现了 InvocationHandler 接口的对象;

要实现动态代理的话,还必须需要实现InvocationHandler 来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。

public interface InvocationHandler {/*** 当你使用代理对象调用方法的时候实际会调用到这个方法*//*** proxy :动态生成的代理类* method : 与代理类对象调用的方法相对应* args : 当前 method 方法的参数*/public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
}

总结起来就是:我们通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。 你可以在 invoke() 方法中自定义处理逻辑,比如在代理方法执行前后做什么其他的事情

JDK 动态代理类使用步骤

  1. 定义一个接口及其实现类;
  2. 实现 InvocationHandler 接口 并重写invoke方法,在 invoke
    方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
  3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[]
    interfaces,InvocationHandler h) 方法创建代理对象

示例

OrderServiceDynamicProxy类(Service层的JDK动态代理类)。

public class OrderServiceDynamicProxy implements InvocationHandler {// 代理类中的真实对象private Object target;public OrderServiceDynamicProxy(Object target) {this.target = target;}public Object bind(){Class cls = target.getClass();//主要通过Proxy.newProxyInstance()方法获取某个类的代理对象return Proxy.newProxyInstance(cls.getClassLoader(), //目标类的类加载cls.getInterfaces(),  // 被代理类实现的一些接口;this); // 实现了 InvocationHandler 接口的代理对象}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object argObject = args[0];// 执行代理业务方法之前,我们可以添加前置操作beforeMethod(argObject);Object object = method.invoke(target,args);//同理,可以添加后置操作afterMethod();return object;}private void beforeMethod(Object obj){int userId = 0;if(obj instanceof Order){Order order = (Order)obj;userId = order.getUserId();}int dbRouter = userId % 2;System.out.println("动态代理分配到【db"+dbRouter+"】处理数据");//todo 设置dataSource;DataSourceContextHolder.setDBType("db"+String.valueOf(dbRouter));System.out.println("开始执行代理任务");System.out.println("动态代理 before code");}private void afterMethod(){System.out.println("动态代理 after code");System.out.println("执行完毕");}}

invoke() 方法: 当我们的动态代理对象调用原生方法的时候,最终实际上调用到的是 invoke() 方法,然后 invoke() 方法代替我们去调用了被代理对象的原生方法。

Test

public class Test {public static void main(String[] args) {Order order = new Order();order.setUserId(1);IOrderService orderServiceDynamicProxy = (IOrderService) new OrderServiceDynamicProxy(new OrderServiceImpl()).bind();orderServiceDynamicProxy.saveOrder(order);}
}

输出
在这里插入图片描述

CGLib动态代理

CGLib简单介绍

JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,可以使用 CGLib动态代理

CGLIB (opens new window)(Code Generation Library)是一个基于ASM (opens new window)的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。

很多知名的开源框架都使用到了CGLIB (opens new window), 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。

在 CGLIB 动态代理机制中 MethodInterceptor 接口Enhancer 类是核心。
需要我们自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法。

public interface MethodInterceptor extends Callback {// 拦截被代理类中的方法Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}

obj :被代理的对象(需要增强的对象)
method :被拦截的方法(需要增强的方法)
args :方法入参
proxy :用于调用原始方法

我们可以通过 Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法。

CGLIB 动态代理类使用步骤

  1. 定义一个类;
  2. 实现 MethodInterceptor 接口并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK动态代理中的 invoke 方法类似;
  3. 通过 Enhancer 类的 create()创建代理类;

不同于 JDK 动态代理不需要额外的依赖。CGLIB (opens new window)(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要添加相关依赖。我这里添加的依赖是

    <dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.3.10</version></dependency></dependencies>

示例

OrderServiceCGLibDynamicProxy类(Service层的CGLib动态代理类)。

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;public class OrderServiceCGLibDynamicProxy implements MethodInterceptor {public OrderServiceImpl bind(){Enhancer enhancer = new Enhancer();//生成代理对象enhancer.setSuperclass(OrderServiceImpl.class);//设置对谁进行代理enhancer.setCallback(this);//代理要做什么,设置拦截器OrderServiceImpl orderService = (OrderServiceImpl) enhancer.create();//创建代理对象return orderService;}/*** @param o           代理对象(增强的对象)* @param method      被拦截的方法(需要增强的方法)* @param objects     方法入参* @param methodProxy 用于调用原始方法*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {beforeMethod(objects[0]);Object object = methodProxy.invokeSuper(o,objects);afterMethod();return object;}private void beforeMethod(Object object){if(object instanceof Order){Order order = (Order)object;int userId = order.getUserId();int dbRouter = userId % 2;System.out.println("CGLib动态代理分配到 【db"+dbRouter+"】处理数据");//TODO 分库操作}//TODO 其他类型处理System.out.println("开始执行代理任务");System.out.println("CGLib动态代理 before code");}private void afterMethod(){System.out.println("CGLib动态代理 after code");System.out.println("执行完毕");}
}

Test

public class Test {public static void main(String[] args) {Order order = new Order();order.setUserId(1);OrderServiceCGLibDynamicProxy orderServiceCGLibDynamicProxy =new OrderServiceCGLibDynamicProxy();IOrderService iOrderService = orderServiceCGLibDynamicProxy.bind();iOrderService.saveOrder(order);}
}

输出
在这里插入图片描述

JDK 动态代理和 CGLIB 动态代理对比

  • JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
  • 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显

代理模式总结

优点

  • 实现了访问者与访问对象之间的解耦。
  • 代理模式在应用层与对象之间起到中介作用,保护了对对象的访问。
  • 职责清晰(单一职责):真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,代理模式可以在代理过程中增加逻辑,如Spring框架的AOP。

缺点

  • 增加代理会使程序请求处理变慢。
  • 类的数量变多,系统更加复杂。

应用场景

  1. 远程代理(Remote Proxy)
    为一个位于不同的地址空间的对象提供一个本地的代理对象。这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)
  2. 虚拟代理(Virtual Proxy)
    根据需要创建开销很大的对象。如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
  3. 保护代理(Protection Proxy)
    控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。
  4. 智能指引(Smart Reference)
    取代了简单的指针,它在访问对象时执行一些附加操作。
  5. Copy-on-Write代理
    它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆
  6. 引用计数(reference counting)指针对象。
    当一个复杂对象的多份副本须存在时,代理模式可以结合享元模式以减少存储器用量。典型作法是创建一个复杂对象及多个代理者,每个代理者会引用到原本的复杂对象。而作用在代理者的运算会转送到原本对象。一旦所有的代理者都不存在时,复杂对象会被移除。

References:

  • https://blog.csdn.net/weixin_34007906/article/details/92172234
  • https://baike.baidu.com/item/%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/8374046?fr=aladdin
  • https://javaguide.cn/java/basis/%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F%E8%AF%A6%E8%A7%A3/#
  • https://blog.csdn.net/qq_37960603/article/details/104101825
  • https://coding.imooc.com/class/270.html?utm_term=%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F&utm_campaign=SEM&utm_medium=33&_channel_track_key=V4q7e28K&utm_source=szjineng5&bd_vid=11577451146916505204
  • https://www.cnblogs.com/jy107600/p/8657217.html
  • https://www.jianshu.com/p/0021bc657203

(写博客主要是对自己学习的归纳整理,资料大部分来源于书籍和网络资料,整理不易,但是难免有不足之处,如有错误,请大家评论区批评指正。同时感谢广大博主和广大作者辛苦整理出来的资源。)


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

相关文章

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

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

设计模式:(代理模式)

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

Nginx 虚拟主机与反向代理

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

【数据库】MYSQL轻松入门

文章目录 MYSQL入门一、MYSQL概述1. 数据库相关概念1.1 数据库&#xff0c;数据库管理系统与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数据库的五分钟快速入门教程

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

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

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

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

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

数据库(MySQL)基础快速入门!

前言&#xff1a;这篇送给那些想要学会数据库&#xff0c;但又不想花太多时间的同学们。看了这篇数据库知识&#xff0c;轻轻松松搞定它&#xff01;用不同的工具操作数据库命令可能会有小小的不同&#xff0c;我用的是系统自带的cmd。&#xff08;凡是数据库名称&#xff0c;数…

MySQL数据库1——数据库快速入门

MySQL数据库——数据库基础 &#xff08;一&#xff09;为什么使用数据库 数据库用来存储海量数据数据库利于数据查询和管理数据库相对于文件安全性更高数据库相对于文件利于控制 &#xff08;二&#xff09;MySQL的基本使用 安装好MySQL数据库以后&#xff0c;连接服务器。…

mysql快捷创建数据库_mysql数据库快速入门(1)

1、数据库操作 1.1、连接mysql服务器 mysql -u root( 用户名 ) -p 1.2、退出mysql命令提示窗 exit 1.3、查看版本 SELECT VERSION(); 1.4、列出数据库列表 SHOW DATABASES; 1.5、创建数据库 CREATE DATABASE IF NOT EXISTS [database] DEFAULT CHARSET UTF8; 1.6、选择要操作的…

SQLite3数据库的快速入门菜鸟教程

SQLite3是一个轻量级嵌入式数据库&#xff0c;其主要有以下的特点&#xff1a; 1、零配置无需安装和管理配置&#xff1b; 2、储存在单一磁盘文件中&#xff1b; 3、数据库文件可在不同字节序的机器自由共享&#xff1b; 4、比目前流行的大多数数据库对数据的操作要快&…

MySQL数据库快速入门

MySQL基础 1、数据库相关概念 以前我们做系统&#xff0c;数据持久化的存储采用的是文件存储。存储到文件中可以达到系统关闭数据不会丢失的效果&#xff0c;当然文件存储也有它的弊端。假设在文件中存储以下的数据&#xff1a; 姓名 年龄 性别 住址 张三 23 男 北京西三旗…