装饰器模式

参考 装饰者模式
【设计模式实战】装饰器模式

1. HistorySet的例子

HistorySet 可以在实现的Set的基础上,在remove时保留删除的元素。通过将方法委托给现有的Set,在remove时先保留被删除元素后委托给注入的set进行remove

public class HistorySet<E>  implements Set<E> {private final List<E> removeList=new ArrayList<>();private final Set<E> delegate;public HistorySet(Set<E> set){this.delegate=set;}@Overridepublic int size() {return this.delegate.size();}@Overridepublic boolean isEmpty() {return this.delegate.isEmpty();}@Overridepublic boolean contains(Object o) {return this.delegate.contains(o);}@Overridepublic Iterator<E> iterator() {return  this.delegate.iterator();}@Overridepublic Object[] toArray() {return this.delegate.toArray();}@Overridepublic <T> T[] toArray(T[] a) {return this.delegate.toArray(a);}@Overridepublic boolean add(E s) {return this.delegate.add(s);}@Overridepublic boolean remove(Object o) {boolean result=false;if( this.delegate.contains(o)){this.removeList.add((E) o);this.delegate.remove(o);}return result;}@Overridepublic boolean containsAll(Collection<?> c) {return this.delegate.containsAll(c);}@Overridepublic boolean addAll(Collection<? extends E> c) {return this.delegate.addAll(c);}@Overridepublic boolean retainAll(Collection<?> c) {return this.delegate.retainAll(c);}@Overridepublic boolean removeAll(Collection<?> c) {return this.delegate.removeAll(c);}@Overridepublic void clear() {this.delegate.clear();}@Overridepublic String toString() {return "HistorySet{" +"removeList=" + removeList +", delegate=" + delegate +'}';}public static void main(String[] args) {HistorySet<String> historySet = new HistorySet<>(new HashSet<>());historySet.add("age");historySet.add("sex");historySet.add("interest");historySet.remove("sex");for (String s : historySet.removeList) {System.out.println(s);}System.out.println(historySet.toString());Collection<Object> objects = Collections.synchronizedCollection(new ArrayList<>());//不是原子操作if(objects.isEmpty()){objects.add(1);}}
}

2. BufferInputStream

2.1 原始的读取文件的代码

由于采用fileInputStream.read()一个一个字节读入,每次读入都要重新启动IO操作,费时费力。

public class MyFile {public static void main(String[] args) {File file = new File(MyFile.class.getClassLoader().getResource("test.pdf").getPath());long l = Instant.now().toEpochMilli();try (InputStream fileInputStream=new FileInputStream(file)){while (true){int read = fileInputStream.read();if(read== -1){break;}}System.out.println("用时:"+(Instant.now().toEpochMilli()-l)+"毫秒");} catch (IOException e) {throw new RuntimeException(e);}}
}

2.2 增强后的代码

因此,可以采用装饰器模式,在原有的FileInputStream的read()方法上,为 FileInputStream 添加了缓冲功能,显著提升了读取效率

public class BufferInputStream extends InputStream {private final byte[] buffer = new byte[8192]; // 缓冲区private int position;       // 当前读取位置private int count;          // 缓冲区有效数据长度private final FileInputStream fileInputStream;public BufferInputStream(FileInputStream fileInputStream) {this.fileInputStream = fileInputStream;}@Overridepublic int read() throws IOException {// 如果缓冲区已读完,尝试填充if (position >= count) {fillBuffer();if (count == -1) {return -1; // 流已结束}}// 返回缓冲区的下一个字节(转换为 0~255)return buffer[position++] & 0xFF;}/*** 从底层 FileInputStream 填充缓冲区*/private void fillBuffer() throws IOException {count = fileInputStream.read(buffer); // 读取数据到缓冲区position = 0; // 重置读取位置}@Overridepublic void close() throws IOException {fileInputStream.close(); // 关闭底层流super.close();}public static void main(String[] args) {String filePath = BufferInputStream.class.getClassLoader().getResource("a.txt").getPath(); // 替换为你的文件路径try (// 1. 创建底层文件流FileInputStream fis = new FileInputStream(filePath);// 2. 包装为缓冲流BufferInputStream bis = new BufferInputStream(fis)) {int byteData;// 3. 逐字节读取文件内容while ((byteData = bis.read()) != -1) {// 处理字节数据(例如打印字符)System.out.print((char) byteData);}} catch (IOException e) {e.printStackTrace();}}
}

3. ReadCounterInputStream

通过装饰器模式,记录inputStream当前调用过多少次read(方法

public class ReadCounterInputStream extends InputStream {private final InputStream target;  // 被装饰的输入流private int readCount = 0;         // 记录 read() 调用次数public ReadCounterInputStream(InputStream target) {this.target = target;}// 获取当前 read() 方法调用次数public int getReadCount() {return readCount;}// 重写所有 read 方法,确保完整计数@Overridepublic int read() throws IOException {readCount++;return target.read();}@Overridepublic int read(byte[] b) throws IOException {readCount++;return target.read(b);}@Overridepublic int read(byte[] b, int off, int len) throws IOException {readCount++;return target.read(b, off, len);}@Overridepublic void close() throws IOException {target.close();}// 可选:重写其他需要委托的方法(如 available()、skip() 等)@Overridepublic int available() throws IOException {return target.available();}public static void main(String[] args) throws IOException {// 原始输入流(这里以字节数组流为例)InputStream rawStream = new ByteArrayInputStream("Hello World".getBytes());// 包装为计数装饰器ReadCounterInputStream counterStream = new ReadCounterInputStream(rawStream);// 读取操作counterStream.read();                   // 单字节读取counterStream.read(new byte[10], 0, 3); // 带偏移量的读取System.out.println("Total read() calls: " + counterStream.getReadCount()); // 输出 3counterStream.close();}
}

4. 自定义注解,Map 自动加入事件戳

4.1 map是在什么时候生成的

关键是要弄清楚map是在什么时候生成的。

@RestController
@RequestMapping("/")
public class WebController {@PostMappingpublic Map<String, String> test(@RequestResponseBody  Map<String,String> map){return map;}
}

return map语句打断点进行调试。发现调用this.resolvers.resolveArgument后生成了Map对象.

在这里插入图片描述

查看this.resolvers的具体类型:发现this.resolvers HandlerMethodArgumentResolverComposite类对象,
在这里插入图片描述

其中,HandlerMethodArgumentResolverComposite实现了HandlerMethodArgumentResolver接口,并且有类成员List<HandlerMethodArgumentResolver>。毫无疑问,这里利用了装饰者模式,在增强代码的职责后,将底层的代码委托给这些List中类成员实现。(因此我们可以有这样一个印象,Composite中装入许多同类性的对象,必要时将方法委托给它们实现,而这些委托对象又层层委托给底下的委托对象实现,因此到看到某一层的委托就可以了)

在这里插入图片描述
进入 HandlerMethodArgumentResolver类的resolveArgument()方法体中
在这里插入图片描述

发现该方法首先先择合适的解析器resolver,然后委托其实现resolver.resolveArgument()后生成了args[i]。这个方法的核心就是如何先择合适的解析器resolver
因此接下来我们主要分为两步骤,查看getArgumentResolve()和断点调试解析到的具体的resolver类型查看其是如何实现的。

4.11 查看getArgumentResolve()方法体

在这里插入图片描述
发现主要是利用resolver.supportsParameter(parameter)选择合适的解析器。

4.12 查看resolver的具体类型

return result 打上断点,可以看见result解析器类型为RequestResponseBodyMethodProcessor。查看其源码:
在这里插入图片描述
由于 HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);使用HandlerMethodArgumentResolver 接收,因此只需查看HandlerMethodArgumentResolver接口中的方法。
在这里插入图片描述
在这里插入图片描述

会发现这里用到了RequestBody.class。点击进入该注解:
在这里插入图片描述

原来我们使用的@RequestBody注解在这里使用到了,因此,我们可以定义一个HandlerMethodArgumentResolve类, 在遇到自定义注解时,supportsParam()方法返回true,就可以调用自定义的解析器的resolve方法,只要我们在resolve()生成实例时加入时间戳就可以了。

因此我们可以自定义一个HandlerMethodArgumentResolver类,当遇到参数上有@TimestampResponseBody返回true,即可选中该解析对象,在调用解析对象resolve后在map中加入时间戳就可以了。最后只要可以将其放入HandlerMethodArgumentResolverCompositeList<HandlerMethodArgumentResolver>中就可以了。

模仿RequestResponseBodyMethodProcessor类,写出自定义的类。为了能够简化代码,可以利用装饰器模式定义自定义解析器

public class TimestampBodyMethodProcessor implements HandlerMethodArgumentResolver {private  RequestResponseBodyMethodProcessor processor;@Overridepublic boolean supportsParameter(MethodParameter parameter) {return  parameter.hasParameterAnnotation(TimestampResponseBody.class);}@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {Object o = processor.resolveArgument(parameter, mavContainer, webRequest, binderFactory);if(!(o instanceof Map<?,?>)){return o;}((Map) o).put("timestamp","11233333");return o;}
}

模仿@RequestBody 自定义注解@TimestampResponseBody
在这里插入图片描述

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TimestampResponseBody {
}

4.2 在HandlerMethodArgumentResolverComposite中的List中放入自定义解析器

发现原始的argumentResolvers为空,调用addResolver()方法后才加入resolver。在addResolver()打上断点,看是哪个函数调用的。
在这里插入图片描述
发现在resolvers = this.getDefaultArgumentResolvers();生成resolvers
在这里插入图片描述

进入resolvers = this.getDefaultArgumentResolvers();
在这里插入图片描述
在这里插入图片描述

发现在这里预留了加入在resolvers中加入自定义的resolvers的方法即this.getCustomArgumentResolvers()

进入this.getCustomArgumentResolvers()方法
在这里插入图片描述

发现this.customArgumentResolvers通过setCustomArgumentResolvers()方法设置的。因此接下来给该方法打上断点,查看该方法是如何被调用的。
在这里插入图片描述

发现resolversthis.getArgumentResolvers()产生的,进入该方法中
在这里插入图片描述

发现实际加入的自定义resolvers是由addArgumentResolvers()产生的。因此继续查看实现的方法。
在这里插入图片描述
发现是调用this.configurers提供的方法,查看this.configurers的具体类型。
在这里插入图片描述
发现DelegatingWebMvcConfiguration利用了装饰器模式,并且仔细查看发现该类中的configures是通过外部注入的方式(可以作为突破口,自定义这个WebMvcConfigurers并注入容器中)。然后查看configurers的addArgumentResolvers()方法
在这里插入图片描述

发现addArgumentResolvers()是依次调用this.delegates中每个delegate.addArgumentResolvers()
因此我们可以自定义类实现WebMvcConfigurationSupport接口中的
this.configurers.addArgumentResolvers(argumentResolvers)方法,并让该类注入到spring的容器,然后利用装饰器模式,在委托对象加入argumentResolvers之前拦截argumentResolvers,在其中加入自定义的解析器。

@Component
public class MyWebMvcConfigure implements WebMvcConfigurer {@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {resolvers.add(new TimestampBodyMethodProcessor());}
}

4.3 获取RequestResponseBodyMethodProcessor

在自定义 HandlerMethodArgumentResolver类中,发现processor没有办法获得。在查看解析器那一部分源码中,我们发现这些解析器都是有系统new出来的,没有交给容器管理,因此,必须采用其它方法获得。在浏览源码中,发现这些new出来的解析器都在 HandlerMethodArgumentResolverComposite的肚子里,因此可以首先获取容器,然后取出该对象,从肚子中获取就可以了。

public class TimestampBodyMethodProcessor implements HandlerMethodArgumentResolver {private  RequestResponseBodyMethodProcessor processor;private ApplicationContext applicationContext;public TimestampBodyMethodProcessor(ApplicationContext applicationContext){this.applicationContext=applicationContext;}@Overridepublic boolean supportsParameter(MethodParameter parameter) {return  parameter.hasParameterAnnotation(TimestampResponseBody.class);}@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {setupProcessor();Object o = processor.resolveArgument(parameter, mavContainer, webRequest, binderFactory);if(!(o instanceof Map<?,?>)){return o;}((Map) o).put("timestamp","11233333");return o;}private void setupProcessor() {if(this.processor ==null){RequestMappingHandlerAdapter bean = this.applicationContext.getBean(RequestMappingHandlerAdapter.class);List<HandlerMethodArgumentResolver> argumentResolvers = bean.getArgumentResolvers();for(HandlerMethodArgumentResolver argumentResolver:argumentResolvers){if(argumentResolver instanceof  RequestResponseBodyMethodProcessor){this.processor= (RequestResponseBodyMethodProcessor) argumentResolver;return;}}}}
}
@Component
public class MyWebMvcConfigure implements WebMvcConfigurer {@Resourceprivate ApplicationContext applicationContext;@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {resolvers.add(new TimestampBodyMethodProcessor(applicationContext));}
}

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

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

相关文章

软件定义汽车时代的功能安全和信息安全

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 简单&#xff0c;单纯&#xff0c;喜欢独处&#xff0c;独来独往&#xff0c;不易合同频过着接地气的生活…

【Golang】GC探秘/写屏障是什么?

之前写了 一篇【Golang】内存管理 &#xff0c;有了很多的阅读量&#xff0c;那么我就接着分享一下Golang的GC相关的学习。 由于Golang的GC机制一直在持续迭代&#xff0c;本文叙述的主要是Go1.9版本及以后的GC机制&#xff0c;该版本中Golang引入了 混合写屏障大幅度地优化了S…

docker 运行 芋道微服务

jar包打包命令 mvn clean install package -Dmaven.test.skiptrue创建文件夹 docker-ai 文件夹下放入需要jar包的文件夹及 docker-compose.yml 文件 docker-compose.yml 内容&#xff1a;我这里的是ai服务&#xff0c;所以将原先的文件内容做了变更&#xff0c;你们需要用到什…

【苍穹外卖】学习

软件开发整体介绍 作为一名软件开发工程师,我们需要了解在软件开发过程中的开发流程&#xff0c; 以及软件开发过程中涉及到的岗位角色&#xff0c;角色的分工、职责&#xff0c; 并了解软件开发中涉及到的三种软件环境。那么这一小节&#xff0c;我们将从 软件开发流程、角色…

网工项目理论1.7 设备选型

本专栏持续更新&#xff0c;整一个专栏为一个大型复杂网络工程项目。阅读本文章之前务必先看《本专栏必读》。 一.交换机选型要点 制式:盒式交换机/框式交换机。功能:二层交换机/三层交换机。端口密度:每交换机可以提供的端口数量。端口速率:百兆/千兆/万兆。交换容量:交换矩阵…

前端面试技巧与实践

在当今快速发展的互联网行业中&#xff0c;前端开发已经成为了一个至关重要的角色。随着技术的不断进步和用户需求的日益复杂&#xff0c;前端工程师的职责不再仅仅是实现页面的布局和交互&#xff0c;而是需要具备全方位的技术能力和工程思维。根据2023年Stack Overflow的开发…

项目2 数据可视化--- 第十五章 生成数据

数据分析是使用代码来探索数据内的规律和关联。 数据可视化是通过可视化表示来 探索和呈现数据集内的规律。 好的数据可视化&#xff0c;可以发现数据集中未知的规律和意义。 一个流行的工具是Matplotlib&#xff0c;他是一个数据绘图库&#xff1b; 还有Plotly包&#xff…

前端常见面试题-2025

vue4.0 Vue.js 4.0 是在 2021 年 9 月发布。Vue.js 4.0 是 Vue.js 的一个重要版本&#xff0c;引入了许多新特性和改进&#xff0c;旨在提升开发者的体验和性能。以下是一些关键的更新和新特性&#xff1a; Composition API 重构&#xff1a;Vue 3 引入了 Composition API 作为…

python学opencv|读取图像(六十八)使用cv2.Canny()函数实现图像边缘检测

【1】引言 前序学习进程中&#xff0c;在对图像进行边缘识别的基础上&#xff0c;先后进行了边缘轮廓绘制&#xff0c;矩形标注、圆形标注和凸包标注。相关文章包括且不限于&#xff1a; python学opencv|读取图像&#xff08;六十四&#xff09;使用cv2.findContours()函数cv…

C语言基础16:二维数组、字符数组

二维数组 定义 二维数组本质上是一个行列式的组合&#xff0c;也就是说二维数组由行和列两部分组成。属于多维数组&#xff0c;二维数组数据是通过行列进行解读。 二维数组可被视为一个特殊的一维数组&#xff0c;相当于二维数组又是一个一维数组&#xff0c;只不过它的元素…

小爱音箱连接电脑外放之后,浏览器网页视频暂停播放后,音箱整体没声音问题解决

背景 22年买的小爱音箱增强版play&#xff0c;小爱音箱连接电脑外放之后&#xff0c;浏览器网页视频暂停播放后&#xff0c;音箱整体没声音&#xff08;一边打着游戏&#xff0c;一边听歌&#xff0c;一边放视频&#xff0c;视频一暂停&#xff0c;什么声音都没了&#xff0c;…

【做一个微信小程序】校园地图页面实现

前言 上一个教程我们实现了小程序的一些的功能&#xff0c;有背景渐变色&#xff0c;发布功能有的呢&#xff0c;已支持图片上传功能&#xff0c;表情和投票功能开发中&#xff08;请期待&#xff09;。下面是一个更高级的微信小程序实现&#xff0c;包含以下功能&#xff1a;…

使用Navicat for MySQL工具连接本地虚拟机上的MySQL

昨天在虚拟机上装了MySQL数据库&#xff0c;今天打算用Navicat for MySQL工具连下&#xff0c;结果连接不上。 使用本地Navicat for MySQL工具连接虚拟机上的MySQL数据库&#xff1a; 1.Navicat连接mysql 解决方案 1、首先使用xshell工具连上虚拟机服务器&#xff0c;输入命令&…

算法笔记 02 —— 入门模拟

本系列为胡凡编著的算法笔记当中代码部分的精简版整理&#xff0c;笔者也在同时准备Leetcode刷题和实习面试&#xff0c;希望为有一定编码和数据结构基础的同学提供一份系统型的参考&#xff0c;以方便遗忘时的算法查阅、期末复习总览以及C学习参照。 目录 01 简单模拟 Ⅰ害…

unity学习38:导入角色和动画,实测用脚本控制trigger和动作状态的转换

目录 1 资源准备&#xff1a;先从unity的 Asset store下载一些free的资源 2 在project/Asset里找到角色模型和动画 2.1 在prefab里找到角色资源 2.2 找到动画资源&#xff0c;一般在Animation下的模型文件fbx下层 2.3 准备工作 2.4 拖拽模型文件里的动作到Animator 2.5 …

Weboffice在线Word权限控制:限制编辑,只读、修订、禁止复制等

在现代企业办公中&#xff0c;文档编辑是一项常见且重要的任务。尤其是在线办公环境中&#xff0c;员工需要在网页中打开和编辑文档&#xff0c;但如何确保这些文档只能进行预览而无法被编辑或复制&#xff0c;成为许多企业面临的一个痛点。尤其是在处理涉密文档时&#xff0c;…

Endnote使用笔记——持续更新

&#xff08;1&#xff09;如果样式库里没有想要的期刊格式&#xff0c;可以到这个网址进行下载&#xff0c;并放在本地安装Endnote的文件下边的styles文件里&#xff1a; https://endnote.com/downloads/styles/ &#xff08;2&#xff09;EndNote导入参考文献时&#xff0c;关…

try learning-git-branching

文章目录 mergerebase分离 HEAD相对引用利用父节点branch -f 撤销变更cherry-pick交互式 rebase只取一个提交记录提交的技巧rebase 在上一次提交上amendcherry-pick 在上一次提交上 amend tag多分支 rebase两个parent节点纠缠不清的分支偏离的提交历史锁定的Main推送主分支合并…

Unity使用反射进行Protobuf(CS/SC)协议,json格式

protobuf生成的协议,有挺多协议的.利用反射生成dto进行伪协议的响应 和 发送请求 应用场景: 请求(CS)_后端先写完了(有proto接口了),前端还没搞完时(暂还没接入proto),后端可使用此请求,可自测 响应(SC)_可自行构建一个响应(有些特殊数据后端下发不了的),对数据进行测试 // 请…

Linux探秘坊-------8.进程详解

1.概念详解 1.运行&&阻塞&&挂起 内容基础&#xff1a;方框中的就是调度队列&#xff0c;是一个 双向队列&#xff0c;每一个元素是PCB其对应的代码数据 1.运行 只要进程 在调度队列中&#xff0c;进程的状态就是运行&#xff08;running&#xff09;. 2.阻塞…