当前位置: 首页 > news >正文

设计模式-代理模式Proxy

代理模式Proxy

    • 代理模式 (Proxy)
      • 1) 静态代理
        • 1.a) 原理解析
        • 1.b) 使用场景
        • 1.c) 静态代理步骤总结
      • 2) 动态代理
        • 2.a) 基于 JDK 的动态代理实现步骤
        • 2.b) 基于 CGLIB 的动态代理实现步骤
        • 2.c) Spring中aop的使用步骤

代理模式 (Proxy)

代理设计模式(Proxy Design Pattern)是一种结构型设计模式,它为其他对象提供一个代理,以控制对这个对象的访问。代理模式可以用于实现懒加载、安全访问控制、日志记录等功能。

在设计模式中,代理模式可以分为静态代理和动态代理。静态代理是指代理类在编译时就已经确定,而动态代理是指代理类在运行时动态生成

1) 静态代理

1.a) 原理解析

在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。

1.b) 使用场景

1.缓存代理

缓存代理通常会在内部维护一个缓存数据结构,如 HashMap 或者 LinkedHashMap,用来存储已经处理过的请求及其结果。

假设有一个数据查询接口,它从数据库或其他数据源中检索数据。在没有缓存代理的情况下,每次查询都需要访问数据库,这可能会导致较高的资源消耗和延迟。通过引入缓存代理,我们可以将查询结果存储在内存中,从而避免重复查询数据库。

public interface DataQuery {String query(String queryKey);
}
public class DatabaseDataQuery implements DataQuery {@Overridepublic String query(String queryKey) {// 使用数据源从数据库查询数据很慢return "result";}
}

创建一个缓存代理类,它同样实现了 DataQuery 接口,并在内部使用HashMap 作为缓存:

public class DatabaseDataQueryProxy implements DataQuery {// 实现缓存,需要数据结构private Map<String, String> cache = new HashMap<>(256);// 你代理谁,就要持有谁private DatabaseDataQuery dataQuery;public DatabaseDataQueryProxy() {// 1.屏蔽被代理对象this.dataQuery = new DatabaseDataQuery();}@Overridepublic String query(String queryKey) {// 2.对被代理对象的方法做增强// 2.1.查询缓存,命中则返回String result = cache.get(queryKey);if (result != null) {System.out.println("命中缓存,走缓存");return result;}// 2.2.未命中,则查询数据库result = dataQuery.query(queryKey);// 2.2.1.如果有结果,需要将结果保存到缓存中,再返回if (result != null) {cache.put(queryKey, result);}System.out.println("未命中,走持久层");return result;}
}
// 测试代码
@Test
void test() {DataQuery dataQuery = new DatabaseDataQueryProxy();String value = dataQuery.query("key1");System.out.println(value);value = dataQuery.query("key1");System.out.println(value);value = dataQuery.query("key2");System.out.println(value);
}

2.安全代理

用于控制对真实主题对象的访问。通过安全代理,可以实现访问控制、权限验证等安全相关功能。

假设我们有一个敏感数据查询接口,只有具有特定权限的用户才能访问:

3.虚拟代理

在需要时延迟创建耗时或资源密集型对象。虚拟代理在初始访问时才创建实际对象,之后将直接使用该对象。这可以避免在实际对象尚未使用的情况下就创建它,从而节省资源。

以下是一个虚拟代理的应用示例:

假设我们有一个大型图片类,它从网络加载图像。由于图像可能非常大,我们希望在需要显示时才加载它。为了实现这一点,我们可以创建一个虚拟代理来代表大型图片类。

4.远程代理

用于访问位于不同地址空间的对象。远程代理可以为本地对象提供与远程对象相同的接口,使得客户端可以透明地访问远程对象。通常,远程代理需要处理网络通信、序列化和反序列化等细节。

1.c) 静态代理步骤总结

通过前四个案例,我们也大致了解了静态代理的使用方式,其大致流程如下:

  • 1.创建一个接口,定义 代理类和被代理类 实现共同的接口
  • 2.创建被代理类,实现这个接口,并且在其中定义实现方法
  • 3.创建代理类,也要实现这个接口,同时在其中定义一个被代理类的对象作为成员变量
  • 4.在代理类中实现接口中的方法,方法中调用 被代理类 中的对应方法
  • 5.通过创建代理对象,并调用其方法,方法增强

这样,被代理类的方法就会被代理类所覆盖,实现了对被代理类的增强或修改

2) 动态代理

静态代理需要手动编写代理类代理类与被代理类实现相同的接口或继承相同的父类,对被代理对象进行包装。在程序运行前,代理类的代码就已经生成,并在程序运行时调用。静态代理的优点是简单易懂,缺点是需要手动编写代理类,代码复杂度较高,且不易扩展。

动态代理是在程序运行时动态生成代理类,无需手动编写代理类,大大降低了代码的复杂度。动态代理一般使用 Java 提供的反射机制实现,可以对任意实现了接口的类进行代理。动态代理的优点是灵活性高,可以根据需要动态生成代理类,缺点是性能相对较低,由于使用反射机制,在运行时会产生额外的开销。

2.a) 基于 JDK 的动态代理实现步骤

使用缓存代理的例子

public interface DataQuery {String query(String queryKey);
}
public class DatabaseDataQuery implements DataQuery {@Overridepublic String query(String queryKey) {// 使用数据源从数据库查询数据很慢return "result";}
}

创建一个代理类,实现 InvocationHandler 接口,实现invoke()方法,并在其中定义一个被代理类的对象作为属性。

public class CacheInvocationHandler implements InvocationHandler {private Map<String, String> cache = new HashMap<>(256);private DatabaseDataQuery databaseDataQuery;public CacheInvocationHandler() {this.databaseDataQuery = new DatabaseDataQuery();}public CacheInvocationHandler(DatabaseDataQuery databaseDataQuery) {this.databaseDataQuery = databaseDataQuery;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 1.判断是哪一个方法 (只对query方法做缓存)String result;if ("query".equals(method.getName())) {// 2.查缓存// 2.1.命中直接返回result = cache.get(args[0].toString());if (result != null) {System.out.println("从缓存拿数据");return result;}// 2.2.未命中,查询数据库 (需要代理实例)result = (String) method.invoke(databaseDataQuery, args);// 3.查询到了,进行缓存cache.put(args[0].toString(), result);return result;}// 当其他的方法被调用,不希望被干预,直接调用原生的方法return method.invoke(databaseDataQuery, args);}
}

主要业务逻辑 (测试代码)

@Test
void testJdkDynamicProxy() {// jdk提供的代理实现,主要是使用Proxy类来实现// 参数1 classLoader:被代理类的类加载器ClassLoader classLoader = Thread.currentThread().getContextClassLoader();// 参数2 代理类需要实现的接口数组Class[] interfaces = new Class[]{DataQuery.class};// 参数3 InvocationHandlerInvocationHandler invocationHandler = new CacheInvocationHandler();DataQuery dataQuery = (DataQuery) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);// 调用query方法时,实际上是调用了invoke()方法String result = dataQuery.query("key1");System.out.println(result);result = dataQuery.query("key1");System.out.println(result);result = dataQuery.query("key2");System.out.println(result);System.out.println("-----------------");result = dataQuery.queryAll();System.out.println(result);
}

2.b) 基于 CGLIB 的动态代理实现步骤

基于 CGLIB 的动态代理需要使用 net.sf.cglib.proxy.Enhancer 类和 net.sf.cglib.proxy.MethodInterceptor 接口。

1.创建一个被代理类,定义需要被代理的方法 (以DatabaseDataQuery为例)

public class DatabaseDataQuery {public String query(String queryKey) {// 使用数据源从数据库查询数据很慢System.out.println("正在从数据库中查询数据");return "result";}public String queryAll() {System.out.println("正在从数据库中查询数据");return "query All result";}
}

2.创建一个方法拦截器类,实现 MethodInterceptor 接口,并在其中定义一个被代理类的对象作为属性。

  • intercept 方法中,我们可以对被代理对象的方法进行增强
public class CacheMethodInterceptor implements MethodInterceptor {private Map<String, String> cache = new HashMap<>(256);private DatabaseDataQuery databaseDataQuery;public CacheMethodInterceptor() {this.databaseDataQuery = new DatabaseDataQuery();}public CacheMethodInterceptor(DatabaseDataQuery databaseDataQuery) {this.databaseDataQuery = databaseDataQuery;}@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 1.判断是哪一个方法String result;if ("query".equals(method.getName())) {// 2.查询缓存,命中则直接返回result = cache.get(args[0].toString());if (result != null) {System.out.println("从缓存中提取数据");return result;}// 3.未命中,查询数据库result = (String) method.invoke(databaseDataQuery, args);// 4.缓存到缓存中cache.put(args[0].toString(), result);return result;}return method.invoke(databaseDataQuery, args);}
}

3.在使用代理类时,创建被代理类的对象和代理类的对象,并使用 Enhancer.create 方法生成代理对象。

@Test
void testCgkibDynamicProxy() {// cglib通过enhancerEnhancer enhancer = new Enhancer();// 1.设置父类enhancer.setSuperclass(DatabaseDataQuery.class);// 2.设置一个方法拦截器,用来拦截方法enhancer.setCallback(new CacheMethodInterceptor());// 3.创建代理类DatabaseDataQuery databaseDataQuery = (DatabaseDataQuery) enhancer.create();String value = databaseDataQuery.query("key1");System.out.println(value);value = databaseDataQuery.query("key1");System.out.println(value);value = databaseDataQuery.query("key2");System.out.println(value);
}

2.c) Spring中aop的使用步骤

在 Spring 中,AOP(面向切面编程)提供了一种有效的方式来对程序中的多个模块进行横切关注点的处理,例如日志、事务、缓存、安全等。使用 Spring AOP,可以在程序运行时动态地将代码织入到目标对象中,从而实现对目标对象的增强。

Spring AOP 的使用步骤如下:

1.引入 AOP 相关依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId><version>2.3.9.RELEASE</version>
</dependency>

2.在Main中开启自动代理@EnableAspectJAutoProxy

@SpringBootApplication
@EnableAspectJAutoProxy
public class Main {public static void main(String[] args) {SpringApplication.run(Main.class, args);}
}

3.定义接口和实现类,并将具体实现注入容器 (以DatabaseDataQuery为例)

// 接口
public interface DataQuery {String query(String queryKey);
}// 实现类
@Component
public class DatabaseDataQuery implements DataQuery {@Overridepublic String query(String queryKey) {// 使用数据源从数据库查询数据很慢System.out.println("正在从数据库中查询数据");return "result";}
}

4.定义切面类,对方法做增强

  • @Pointcut() 对某包下的某个类的某个方法做增强:.. 代表任意方法
  • @Around() 定义增强
@Component
@Aspect
public class CacheAspectj {private static Map<String,String> cache = new ConcurrentHashMap<>(256);@Pointcut("execution(* com.dcy.structural.proxy.dynamicProxy.aop.impl.DatabaseDataQuery.query(..))")public void pointcut() {}@Around("pointcut()")public String around(ProceedingJoinPoint joinPoint) {// 1.查询缓存Object[] args = joinPoint.getArgs();String key = args[0].toString();// 1.1.命中则返回String result = cache.get(key);if (result != null) {System.out.println("数据从缓存中提取");return result;}// 2.未命中,查询数据库,实际上是调用被代理bean的方法try {result = joinPoint.proceed().toString();// 如果查询有结果,进行缓存cache.put(key, result);} catch (Throwable e) {throw new RuntimeException(e);}return result;}
}

5.测试用例

@SpringBootTest
public class AopTest {@Resourceprivate DataQuery dataQuery;@Testvoid testSpringAop() {String result = dataQuery.query("key1");System.out.println(result);result = dataQuery.query("key1");System.out.println(result);result = dataQuery.query("key2");System.out.println(result);}}

相关文章:

设计模式-代理模式Proxy

代理模式Proxy 代理模式 (Proxy)1) 静态代理1.a) 原理解析1.b) 使用场景1.c) 静态代理步骤总结 2) 动态代理2.a) 基于 JDK 的动态代理实现步骤2.b) 基于 CGLIB 的动态代理实现步骤2.c) Spring中aop的使用步骤 代理模式 (Proxy) 代理设计模式&#xff08;Proxy Design Pattern&…...

如何使用CSS实现一个自适应等高布局?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 使用 Flexbox 布局⭐ 使用 Grid 布局⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发…...

Google colab部署VITS——零门槛快速克隆任意角色声音

目录 序言 查看GPU配置 复制代码库并安装运行环境 选择预训练模型 上传视频链接&#xff08;单个不应长于20分钟&#xff09; 自动处理所有上传的数据 训练质量相关&#xff1a;实验发现目前使用CJ模型勾选ADD_AUXILIARY&#xff0c;对于中/日均能训练出最好的效果&#x…...

14 | Spark SQL 的 DataFrame API 读取CSV 操作

sales.csv 内容 date,category,product,full_name,sales 2023-01-01,Electronics,Laptop,John Smith,1200.0 2023-01-02,Electronics,Smartphone,Jane Doe,800.0 2023-01-03,Books,Novel,Michael Johnson,15.0 2023-01-04,Electronics,Tablet,Emily Wilson,450.0 2023-01-05,B…...

redis面试题二

redis如何处理已过期的元素 常见的过期策略 定时删除&#xff1a;给每个键值设置一个定时删除的事件&#xff0c;比如有一个key值今天5点过期&#xff0c;那么设置一个事件5点钟去执行&#xff0c;把它数据给删除掉&#xff08;优点&#xff1a;可以及时利用内存及时清除无效数…...

虚拟现实(VR)和增强现实(AR)

虚拟现实&#xff08;Virtual Reality&#xff0c;VR&#xff09;和增强现实&#xff08;Augmented Reality&#xff0c;AR&#xff09;是两种前沿的计算机技术&#xff0c;它们正在改变人们与数字世界的互动方式。虚拟现实创造了一个计算机生成的全新虚拟环境&#xff0c;而增…...

如何使用ChatGPT提词器,看看这篇文章

ChatGPT提词器是一种强大的自然语言处理工具&#xff0c;可以帮助你提高创造性写作的效率和质量。本教程将向您介绍如何使用ChatGPT提词器&#xff0c;以获得有趣、吸引人的文章、故事或其他文本内容。 步骤1&#xff1a;访问ChatGPT提词器 首先&#xff0c;确保您已经访问了…...

vue3-vuex持久化实现

vue3-vuex持久化实现 一、背景描述二、实现思路1.定义数据结构2.存值3.取值4.清空 三、具体代码1.定义插件2.使用插件 四、最终效果 一、背景描述 有时候我们可能需要在vuex中存储一些静态数据&#xff0c;比如一些下拉选项的字典数据。这种数据基本很少会变化&#xff0c;所以…...

详解 SpringMVC 的 @RequestMapping 注解

文章目录 1、RequestMapping注解的功能2、RequestMapping注解的位置3、RequestMapping注解的value属性4、RequestMapping注解的method属性5、RequestMapping注解的params属性&#xff08;了解&#xff09;6、RequestMapping注解的headers属性&#xff08;了解&#xff09;7、Sp…...

类的静态成员变量 static member

C自学精简教程 目录(必读) 类的静态成员 static member 变量全局只有一份副本&#xff0c;不会随着类对象的创建而产生副本。 static 静态成员 在类的成员变量前面增加static关键字&#xff0c;表示这个成员变量是类的静态成员变量。 #include <iostream> using name…...

MVSNet (pytorch版) 搭建环境 运行dtu数据集重建 实操教程(图文并茂、超详细)

文章目录 1 准备工作1.1 下载源码1.2 测试集下载2 配置环境3 dtu数据集 重建演示3.1 重建效果查看4 补充解释4.1 bash 脚本文件超参数解释4.2 lists/dtu解释5 Meshlab查看三维点云时 ,使用技巧总结1 Meshlab查看三维点云时 ,换背景颜色2 Meshlab查看三维点云时,点云颜色很暗…...

Linux系统Ubuntu以非root用户身份操作Docker的方法

本文介绍在Linux操作系统Ubuntu版本中&#xff0c;通过配置&#xff0c;实现以非root用户身份&#xff0c;进行Docker各项操作的具体方法。 在文章Linux系统Ubuntu配置Docker详细流程&#xff08;https://blog.csdn.net/zhebushibiaoshifu/article/details/132612560&#xff0…...

m4s格式转换mp4

先安装 ffmpeg&#xff0c;具体从官网可以查到&#xff0c;https://ffmpeg.org&#xff0c;按流程走。 转换代码如下&#xff0c;可以任意选择格式导出 import subprocess import osdef merge_audio_video(input_audio_path, input_video_path, output_mp4_path):# 构建 FFmpe…...

SQL sever中库管理

目录 一、创建数据库 1.1库界面方式 1.2SQL命令方式 二、修改数据库 2.1库界面方式 2.2SQL命令方式 三、删除数据库 3.1库界面方式 3.2SQL命令方式 四、附加和分离数据库 4.1附加和分离数据库概述 4.2作用 4.3附加和分离数据库方法 4.4示例 一、创建数据库 1.1库…...

模板方法模式简介

概念&#xff1a; 模板方法模式是一种行为型设计模式&#xff0c;它定义了一个算法的骨架&#xff0c;将一些步骤延迟到子类中实现。该模式通过在抽象类中定义一个模板方法来控制算法的流程&#xff0c;并使用具体方法来实现其中的某些步骤。 特点&#xff1a; 定义了一个算…...

自动化运维工具-------Ansible(超详细)

一、Ansible相关 1、简介 Ansible是自动化运维工具&#xff0c;基于Python开发&#xff0c;分布式,无需客户端,轻量级&#xff0c;实现了批量系统配置、批量程序部署、批量运行命令等功能&#xff0c;ansible是基于模块工作的,本身没有批量部署的能力。真正具有批量部署的是a…...

计算机毕设 基于生成对抗网络的照片上色动态算法设计与实现 - 深度学习 opencv python

文章目录 1 前言1 课题背景2 GAN(生成对抗网络)2.1 简介2.2 基本原理 3 DeOldify 框架4 First Order Motion Model5 最后 1 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到毕业答辩的要…...

Citespace、vosviewer、R语言的文献计量学 、SCI

文献计量学是指用数学和统计学的方法&#xff0c;定量地分析一切知识载体的交叉科学。它是集数学、统计学、文献学为一体&#xff0c;注重量化的综合性知识体系。特别是&#xff0c;信息可视化技术手段和方法的运用&#xff0c;可直观的展示主题的研究发展历程、研究现状、研究…...

linux操作系统的权限的深入学习

1.Linux权限的概念 Linux下有两种用户&#xff1a;超级用户&#xff08;root&#xff09;、普通用户。 超级用户&#xff1a;可以再linux系统下做任何事情&#xff0c;不受限制 普通用户&#xff1a;在linux下做有限的事情。 超级用户的命令提示符是“#”&#xff0c;普通用户…...

LeetCode——三数之和(中等)

题目 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。 …...

SpringMVC使用

文章目录 一.MVC基础概念1.MVC定义2.SpringMVC和MVC的关系 二.SpringMVC的使用1.RequestMapping2.获取参数1.获取单个参数2.传递对象3.后端参数重命名&#xff08;后端参数映射&#xff09;4.获取URL中参数PathVariable5.上传文件RequestPart6.获取Cookie/Session/header 3.返回…...

【css】css奇数、偶数、指定数选择器:

文章目录 一、简单数字序号写法:nth-child(number)二、倍数写法:nth-child(an)三、倍数分组匹配:nth-child(anb) 与 :nth-child(an-b)四、反向倍数分组匹配:nth-child(-anb)五、奇偶匹配:nth-child(odd) 与 :nth-child(even) :nth-child(n) 选择器匹配属于其父元素的第 N 个子元…...

三维数据Ply格式介绍与读取

三维数据Ply格式介绍与读取 PLY文件格式是在1990年代中期由斯坦福大学的一组研究人员开发的。它已经发展成为最广泛使用的3D数据文件格式之一。该文件格式有ASCII版本和二进制版本。在需要文件大小和处理效率的情况下,首选二进制版本。ASCII版本使得调试变得非常容易。在这里…...

内存管理方式

内存管理 一、C/C内存分布1、内存空间的介绍2、示例题目3、示例题目图解 二、C语言动态内存管理方式1、代码2、介绍 三、C内存管理方式1、概念2、代码3、代码所代表的意义 四、new和delete操作自定义类型1、代码2、运行结果3、特点 五、operator new与operator delete函数1、概…...

文心一言接入Promptulate,开发复杂LLM应用程序

简介 最近在尝试将文心一言的LLM能力接入Promptulate&#xff0c;故写了一篇博客记录一下&#xff0c;Promptulate 是 Promptulate AI 旗下的大语言模型自动化与应用开发框架&#xff0c;旨在帮助开发者通过更小的成本构建行业级的大模型应用&#xff0c;其包含了LLM领域应用层…...

TDengine函数大全-聚合函数

以下内容来自 TDengine 官方文档 及 GitHub 内容 。 以下所有示例基于 TDengine 3.1.0.3 TDengine函数大全 1.数学函数 2.字符串函数 3.转换函数 4.时间和日期函数 5.聚合函数 6.选择函数 7.时序数据库特有函数 8.系统函数 聚合函数 TDengine函数大全APERCENTILEAVGCOUNTELAPS…...

DRM全解析 —— ADD_FB(2)

接前一篇文章&#xff1a;DRM全解析 —— ADD_FB&#xff08;1&#xff09; 本文参考以下博文&#xff1a; DRM驱动&#xff08;四&#xff09;之ADD_FB 特此致谢&#xff01; 上一回围绕libdrm与DRM在Linux内核中的接口&#xff1a; DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, d…...

windows下docker compose方式挂载数据卷volume遇到的问题

例子一&#xff0c;windows 下docker desk top部署TDengine td-compose.yml version: 3 services:tdengine1:image: tdengine/tdengine:latestcontainer_name: tdengine1hostname: tdengine1ports:- 6030:6030- 6041:6041- 6043-6049:6043-6049- 6043-6049:6043-6049/udpresta…...

TCP三次握手四次挥手总结

目录 一、两种传输模式&#xff1a; 二、数据方向&#xff1a; 三、端口的作用&#xff1a; 四、端口类型&#xff1a; 五、三次握手&#xff1a; 六、四次断开 常见面试题 TCP&#xff08;Transfer control protocol&#xff09;传输控制协议 一、两种传输模式&#x…...

【0901作业】QTday3 对话框、发布软件、事件处理机制,使用文件相关操作完成记事本的保存功能、处理键盘事件完成圆形的移动

目录 一、思维导图 二、作业 2.1 使用文件相关操作完成记事本的保存功能 2.2 处理键盘事件完成圆形的移动 一、思维导图 二、作业 2.1 使用文件相关操作完成记事本的保存功能 void Widget::on_saveBtn_clicked() {QString filename QFileDialog::getSaveFileName(this,&…...