当前位置: 首页 > 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;答案中不可以包含重复的三元组。 …...

k8s从入门到放弃之Ingress七层负载

k8s从入门到放弃之Ingress七层负载 在Kubernetes&#xff08;简称K8s&#xff09;中&#xff0c;Ingress是一个API对象&#xff0c;它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress&#xff0c;你可…...

Java如何权衡是使用无序的数组还是有序的数组

在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力

引言&#xff1a; 在人工智能快速发展的浪潮中&#xff0c;快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型&#xff08;LLM&#xff09;。该模型代表着该领域的重大突破&#xff0c;通过独特方式融合思考与非思考…...

如何在看板中有效管理突发紧急任务

在看板中有效管理突发紧急任务需要&#xff1a;设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP&#xff08;Work-in-Progress&#xff09;弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中&#xff0c;设立专门的紧急任务通道尤为重要&#xff0c;这能…...

(转)什么是DockerCompose?它有什么作用?

一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用&#xff0c;而无需手动一个个创建和运行容器。 Compose文件是一个文本文件&#xff0c;通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

使用 SymPy 进行向量和矩阵的高级操作

在科学计算和工程领域&#xff0c;向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能&#xff0c;能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作&#xff0c;并通过具体…...

Reasoning over Uncertain Text by Generative Large Language Models

https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...

Java编程之桥接模式

定义 桥接模式&#xff08;Bridge Pattern&#xff09;属于结构型设计模式&#xff0c;它的核心意图是将抽象部分与实现部分分离&#xff0c;使它们可以独立地变化。这种模式通过组合关系来替代继承关系&#xff0c;从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...