设计模式第二章(装饰器模式)
设计模式第二章(装饰器模式)
装饰器模式(Decorator Pattern) 也称为包装模式(Wrapper Pattern) 是指在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式。装饰器模式的核心是功能扩展,使用装饰器模式可以透明且动态地扩展类的功能。
案例一
需求背景,我们现在需要删除一个集合中的元素,但是我们需要记录了那些元素是被删除的,我们想想是不是需要一个 list来记录那些元素是被删除的呢,还需要有 Set的功能,那么就需要一个代理对象。
代码部分
public class HistorySet<E> implements Set<E> {//记录删除的元素List<E> removeList = new ArrayList<>();// 代理对象private final Set<E> delegate;public HistorySet(Set<E> delegate) {this.delegate = delegate;}@Overridepublic int size() {return delegate.size();}@Overridepublic boolean isEmpty() {return delegate.isEmpty();}@Overridepublic boolean contains(Object o) {return delegate.contains(o);}@Overridepublic Iterator<E> iterator() {return delegate.iterator();}@Overridepublic Object[] toArray() {return delegate.toArray();}@Overridepublic <T> T[] toArray(T[] a) {return delegate.toArray(a);}@Overridepublic boolean add(E e) {return delegate.add(e);}@Overridepublic boolean remove(Object o) {if (delegate.remove(o)) {// 记录被删除的元素removeList.add((E)o);return true;}return false;}@Overridepublic boolean containsAll(Collection<?> c) {return delegate.containsAll(c);}@Overridepublic boolean addAll(Collection<? extends E> c) {return delegate.addAll(c);}@Overridepublic boolean retainAll(Collection<?> c) {return delegate.retainAll(c);}@Overridepublic boolean removeAll(Collection<?> c) {return delegate.removeAll(c);}@Overridepublic void clear() {delegate.clear();}@Overridepublic String toString() {return delegate.toString() + ", 删除的元素:"+removeList;}
}
测试部分
public static void main(String[] args) {Set<String> historySet1 = new HistorySet<>(new HashSet<>());Set<String> historySet = new HistorySet<>(historySet1);historySet.add("1");historySet.add("2");historySet.add("3");historySet.add("4");historySet.add("5");historySet.remove("2");historySet.remove("2");historySet.remove("3");historySet.remove("5");historySet.remove("5");System.out.println(historySet);}
思考部分
装饰器的本质就是增强,自己的内部有一个需要被代理的对象,实际上调用的都是被代理对象的方法,我们不需要更新额外的更改,需要实现被代理对象的接口。那么就拥有和代理对象一样的功能了。
案例一
我们通过字节流读取一个读取一个pdf,这个文件大小为2.6m的大小,我们看传统的字节流读取需要花费多少时间。
代码部分
public static void main(String[] args) {File file = new File("E:\\技术文档\\springboot\\SpringBoot-1.pdf");long s = Instant.now().toEpochMilli();try (FileInputStream fileInputStream = new FileInputStream(file)){while (true) {int length = fileInputStream.read();if (length == -1) {break;}}System.out.println("耗时:"+(Instant.now().toEpochMilli() - s) + " 毫秒!");} catch (IOException e) {throw new RuntimeException(e);}}
思考部分
我们看到读取一个字节的文件花费了2.5秒的时间,那么这个耗时的动作在哪里呢。我们打开了read()
看到这个范围是 -1到255 个字节之间,那么也就是说每次只能读一个 255的字节的数据,频繁的读取增加了耗时
优化代码
我们利用装饰器模式,自己的内部有一个FileInputStream 我们利用现有的功能进行增强,只需要将buffer 扩大是不是就可以实现呢
public class BufferInputStream extends InputStream {private byte[] buffer = new byte[8024];// 读取的位置private int position = -1;//读取的容量private int capacity = -1;private final FileInputStream fileInputStream;public BufferInputStream(FileInputStream fileInputStream) {this.fileInputStream = fileInputStream;}@Overridepublic int read() throws IOException {if (buffCanRead()) {// buff 可以读取return readFromBuffer();}//刷新 bufferrefreshBuffer();if (!buffCanRead()) {return -1;}return readFromBuffer();}private int readFromBuffer() {return buffer[position++] & 0xFF;}private void refreshBuffer() throws IOException {capacity = this.fileInputStream.read(buffer);position = 0;}private boolean buffCanRead() {if (capacity == -1) {return false;}if (position == capacity) {return false;}return true;}
测试部分
public static void main(String[] args) {File file = new File("E:\\技术文档\\springboot\\SpringBoot-1.pdf");long s = Instant.now().toEpochMilli();try (BufferInputStream fileInputStream = new BufferInputStream(new FileInputStream(file))){while (true) {int length = fileInputStream.read();if (length == -1) {break;}}System.out.println("耗时:"+(Instant.now().toEpochMilli() - s) + " 毫秒!");} catch (IOException e) {throw new RuntimeException(e);}}
实战部分
我们平时使用@ResponseBody 注解后,自动会解析为对象,那么我们能否在对象里面增加一个自己的属性呢。
前言
我们创建一个springboot的工程,在接口上定义自己的注解。最终达到增加一个时间戳的值放进去,那么我们一步一步的debug 进去
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.fashion</groupId><artifactId>decorator_pattern-boot</artifactId><version>0.0.1-SNAPSHOT</version><name>decorator_pattern-boot</name><description>decorator_pattern-boot</description><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><spring-boot.version>3.4.1</spring-boot.version><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement></project>
controller部分
@PostMappingpublic Map<String,Object> origin1(@RequestBody Map<String,Object> json) {return json;}POST http://localhost:8080/api
Content-Type: application/json{"name": "张三","age": 18
}
思考部分
- 我们标记@RequestBody 注解后,是如何将参数组装起来的呢
- @RequestBody 在何处被使用了呢
- 找到 HandlerMethodArgumentResolverComposite 类
@RequestBody 参数解析源码部分
- com.fashion.decorator_patternboot.controller.DecoratorController#origin- org.springframework.web.method.support.InvocableHandlerMethod#doInvoke- org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest- org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues- org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#getArgumentResolver
至此我们找到了 HandlerMethodArgumentResolverComposite 所属类,我们看到这个类的结构,是不是就一个装饰器模式的使用,肚子里面有一堆解析器
HandlerMethodArgumentResolver
该类为解析器的顶层入口,里面只有两个方法,一个是否支持,另外一个解释如何解析数据。
RequestResponseBodyMethodProcessor
我们看到该类继承了 extends AbstractMessageConverterMethodProcessor,里面实际上是实现了HandlerMethodArgumentResolver,那么我们可以得到 supportsParameter以及 resolveArgument 方法。
-
@Overridepublic boolean supportsParameter(MethodParameter parameter) {return parameter.hasParameterAnnotation(RequestBody.class);}
-
@Override @Nullable public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {parameter = parameter.nestedIfOptional();Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());if (binderFactory != null) {String name = Conventions.getVariableNameForParameter(parameter);ResolvableType type = ResolvableType.forMethodParameter(parameter);WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name, type);if (arg != null) {validateIfApplicable(binder, parameter);if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());}}if (mavContainer != null) {mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());}}return adaptArgumentIfNecessary(arg, parameter); }
增加自定义注解
TimestampBody 注解类,该类用来做一个标记。
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TimestampBody {
}
自定义解析器
public class TimeStampResponseBodyMethodProcessor implements HandlerMethodArgumentResolver {private RequestResponseBodyMethodProcessor processor;@Overridepublic boolean supportsParameter(MethodParameter parameter) {return parameter.hasParameterAnnotation(TimestampBody.class);}@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {Object retObj = processor.resolveArgument(parameter, mavContainer, webRequest, binderFactory);if (retObj instanceof Map) {((Map) retObj).put("timestamp",System.currentTimeMillis()+"");}return retObj;}}
如何将我们自己的解析器放到解析器组合中
相当于我们需要把 TimeStampResponseBodyMethodProcessor 得类放到 HandlerMethodArgumentResolverComposite 的 argumentResolvers 中。
debug 断点
- org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#addResolvers(java.util.List<? extends org.springframework.web.method.support.HandlerMethodArgumentResolver>)
- org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#afterPropertiesSet
- org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getDefaultArgumentResolvers
getCustomArgumentResolvers
那么我们断点一下,看这个方法在什么时候进行调用的呢?
- org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getCustomArgumentResolvers
- org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#setCustomArgumentResolvers
- org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#requestMappingHandlerAdapter
- org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getArgumentResolvers
- org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration#addArgumentResolvers
至此我们来到了DelegatingWebMvcConfiguration 的类,这个类的肚子里面还有一个 WebMvcConfigurerComposite mvc 的组合
我们看到是通过spring注入的方式注入进去的,那么我们只需要自己实现一个 WebMvcConfigurer的接口,那么是不是就可以进去了 。
自定义process 实现
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {@AutowiredApplicationContext applicationContext;@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {resolvers.add(new TimeStampResponseBodyMethodProcessor(applicationContext));}
}
public class TimeStampResponseBodyMethodProcessor implements HandlerMethodArgumentResolver {private final ApplicationContext applicationContext;private RequestResponseBodyMethodProcessor processor;public TimeStampResponseBodyMethodProcessor(ApplicationContext applicationContext) {this.applicationContext = applicationContext;}@Overridepublic boolean supportsParameter(MethodParameter parameter) {return parameter.hasParameterAnnotation(TimestampBody.class);}@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {setupProcessor();Object retObj = processor.resolveArgument(parameter, mavContainer, webRequest, binderFactory);if (retObj instanceof Map) {((Map) retObj).put("timestamp",System.currentTimeMillis()+"");}return retObj;}private void setupProcessor() {if (this.processor != null) {return;}List<HandlerMethodArgumentResolver> argumentResolvers = this.applicationContext.getBean(RequestMappingHandlerAdapter.class).getArgumentResolvers();for (HandlerMethodArgumentResolver resolver : argumentResolvers) {if (resolver instanceof RequestResponseBodyMethodProcessor) {this.processor = (RequestResponseBodyMethodProcessor) resolver;return;}}}
}
查看HandlerMethodArgumentResolverComposite 是否有我们这个对象
测试结果
@PostMappingpublic Map<String,Object> origin(@TimestampBody Map<String,Object> json) {return json;}
总结部分
特别感谢bi站的u主,学java的生生,我断点不熟练,导致来回的看调用栈。视频也是看了好几遍,动手尝试,现在有一些自己的理解