设计模式-代理模式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);}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/75769.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

golang面试题:json包变量不加tag会怎么样?

问题 json包里使用的时候&#xff0c;结构体里的变量不加tag能不能正常转成json里的字段&#xff1f; 怎么答 如果变量首字母小写&#xff0c;则为private。无论如何不能转&#xff0c;因为取不到反射信息。如果变量首字母大写&#xff0c;则为public。 不加tag&#xff0c…

【Spring Cloud系统】- 轻量级高可用工具Keepalive详解

【Spring Cloud系统】- 轻量级高可用工具Keepalive详解 文章目录 【Spring Cloud系统】- 轻量级高可用工具Keepalive详解一、概述二、Keepalive分类2.1 TCP的keepalive2.2 HTTP的keep-alive2.3 TCP的 KeepAlive 和 HTTP的 Keep-Alive区别 三、nginx的keepalive配置3.1 nginx保持…

在Docker中运行PostgreSQL数据库

1.下载Docker 2.设置DockerHub账号 3.运行Docker并下载Image 4.启动PostgreSQL Image 5.连接到数据库运行SQL docker run --name some-postgres -p 5432:5432 -e POSTGRES_PASSWORDmysecretpassword -d postgres 开放端口从Docker容器到主操作系统&#xff0c;这将允许我们…

【GAN小白入门】Semi-Supervised GAN 理论与实战

🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊🚀 文章来源:K同学的学习圈子论文原文:Semi-Supervised Learning with Generative Adversarial Networks.pdf在学习GAN的时候你有没有想过这样一个问题呢,如果我们生成的图像是带有标签的,例如数…

详解 sudo usermod -aG docker majn

这个命令涉及到几个 Linux 系统管理的基础概念&#xff0c;包括 sudo、usermod 和用户组管理。我们可以逐一地解析它们&#xff1a; sudo: sudo&#xff08;superuser do&#xff09;允许一个已经被授权的用户以超级用户或其他用户的身份执行一个命令。当使用 sudo 前缀一个命令…

Python总结上传图片到服务器并保存的两种方式

一、前言 图片保存到服务器的两种方法&#xff1a; 1、根据图片的 URL 将其保存到服务器的固定位置 2、根据 request.FILES.get("file") 方式从请求中获取上传的图片文件&#xff0c;并将其保存到服务器的固定位置 二、方法 1、图片的 URL 要根据图片的 URL 将…

连接云-边-端,构建火山引擎边缘云网技术体系

近日&#xff0c;火山引擎边缘云网络产品研发负责人韩伟在LiveVideoStack Con 2023上海站围绕边缘云海量分布式节点和上百T的网络规模&#xff0c;结合边缘云快速发展期间遇到的各种问题和挑战&#xff0c;分享了火山引擎边缘云网的全球基础设施&#xff0c;融合开放的云网技术…

数据结构——七大排序[源码+动图+性能测试]

本章代码gitee仓库&#xff1a;排序 文章目录 &#x1f383;0. 思维导图&#x1f9e8;1. 插入排序✨1.1 直接插入排序✨1.2 希尔排序 &#x1f38a;2. 选择排序&#x1f38b;2.1 直接选择排序&#x1f38b;2.2 堆排序 &#x1f38f;3. 交换排序&#x1f390;3.1 冒泡排序&#…

【MyBatis】三、ResultMap与多表查询的处理

ResultMap与多表查询的处理 当字段名与实类名不一致时 使用别名进行处理 字段名&#xff1a;emp_name 实体类名&#xff1a;empName 映射文件中写法&#xff1a; <select id"getAllEmp" resultType"Emp">select eid, emp_name empName, age, se…

210. 课程表 II(leetcode210,ArrayList类型的数组创建,拓扑排序)-------------------Java实现

210. 课程表 II&#xff08;leetcode210&#xff0c;ArrayList类型的数组创建&#xff0c;拓扑排序&#xff09;-------------------Java实现 题目表述 现在你总共有 numCourses 门课需要选&#xff0c;记为 0 到 numCourses - 1。给你一个数组 prerequisites &#xff0c;其…

【React】React获取URL参数,根据URL参数隐藏页面元素

React获取URL参数&#xff0c;根据URL参数隐藏页面元素 AI推荐方法 如果您想使用React获取URL参数并相应地隐藏页面元素&#xff0c;可以按照以下步骤进行操作&#xff1a; 导入React和React DOM&#xff1a; import React from react; import ReactDOM from react-dom;创建…

react 中 antd 的 样式和 tailwind 样式冲突

问题原因&#xff1a;在使用 tailwindcss 时&#xff0c;会导入大量的 tailwindcss 默认属性&#xff0c;而默认样式中 button, [typebutton] 包含了 background-color: transparent; 从而导致 antd Button 按钮背景色变成透明。解决办法&#xff1a;禁止 tailwindcss 的默认属…

单例模式(Singleton Pattern)

单例模式 1、掌握单例模式的应用场景。1-1、饿汉式单例1-2、懒汉式单例1-2-1、 测试类:1-2-2、 main1-2-3、 控制台结果1-2-4、 改进 加锁 `synchronized `1-2-5、 控制台输出1-2-6、 再改进,使用双检锁,懒汉式双重检查锁定(Lazy initialization with double-checked locki…

Qt应用开发(基础篇)——工具按钮类 QToolButton

一、前言 QToolButton类继承于QAbstractButton&#xff0c;该部件为命令或选项提供了一个快速访问按钮&#xff0c;通常用于QToolBar中。 按钮基类 QAbstractButton QToolButton是一个特殊的按钮&#xff0c;一般显示文本&#xff0c;只显示图标&#xff0c;结合toolBar使用。它…

【图文并茂】c++介绍之队列

1.1队列的定义 队列&#xff08;queue&#xff09;简称队&#xff0c;它也是一种操作受限的线性表&#xff0c;其限制为仅允许在表的一端进行插入操作&#xff0c;而在表的另一端进行删除操作 一些基础概念&#xff1a; 队尾&#xff08;rear&#xff09; &#xff1a;进行插…

django项目: ModuleNotFoundError: No module named ‘import_export‘

django项目&#xff1a; ModuleNotFoundError: No module named ‘import_export’ 解决方法&#xff1a; pip install django-import_export

MFC新建内部消息

提示&#xff1a;记录一下MFC新建内部消息的成功过程 文章目录 前言一、第一阶段二、第二阶段三、第三阶段总结 前言 先说一下基本情况&#xff0c;因为要在mapview上增加一个显示加载时间的功能。然后发现是要等加载完再显示时间&#xff0c;显示在主窗口。所以就是在子线程中…

DELL precision上安装nvidia A4000驱动 cuda cudnn

一、安装驱动 参考这篇文章进行安装Ubuntu安装Nvidia显卡驱动_Kevin__47的博客-CSDN博客 【出现问题】 禁用nouveau后出现黑屏&#xff0c;有几行代码&#xff0c;断线一直在闪 【解决方法】 1、参考这篇文章Ubuntu20.04安装nvidia显卡驱动并解决重启后黑屏问题_ubuntu安装…

Java常用的设计模式

单例模式&#xff08;Singleton Pattern&#xff09;: 确保一个类只有一个实例&#xff0c;并提供一个全局访问点。示例&#xff1a;应用程序中的配置管理器。 工厂模式&#xff08;Factory Pattern&#xff09;: 用于创建对象的模式&#xff0c;封装对象的创建过程。示例&…

开开心心带你学习MySQL数据库之节尾篇

Java的JDBC编程 各种数据库,MySQL, Oracle, SQL Server在开发的时候,就会提供一组编程接口(API) API ~~ Application Programming Interface ~~ 应用程序编程接口 计算机领域里面的一个非常常见的概念, 给你个软件,你能对他干啥(从代码层次上的) 基于它提供的这些功能,就可以写…