装饰器模式
参考 装饰者模式
【设计模式实战】装饰器模式
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;
}
@Override
public int size() {
return this.delegate.size();
}
@Override
public boolean isEmpty() {
return this.delegate.isEmpty();
}
@Override
public boolean contains(Object o) {
return this.delegate.contains(o);
}
@Override
public Iterator<E> iterator() {
return this.delegate.iterator();
}
@Override
public Object[] toArray() {
return this.delegate.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return this.delegate.toArray(a);
}
@Override
public boolean add(E s) {
return this.delegate.add(s);
}
@Override
public boolean remove(Object o) {
boolean result=false;
if( this.delegate.contains(o)){
this.removeList.add((E) o);
this.delegate.remove(o);
}
return result;
}
@Override
public boolean containsAll(Collection<?> c) {
return this.delegate.containsAll(c);
}
@Override
public boolean addAll(Collection<? extends E> c) {
return this.delegate.addAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
return this.delegate.retainAll(c);
}
@Override
public boolean removeAll(Collection<?> c) {
return this.delegate.removeAll(c);
}
@Override
public void clear() {
this.delegate.clear();
}
@Override
public 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;
}
@Override
public 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; // 重置读取位置
}
@Override
public 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 方法,确保完整计数
@Override
public int read() throws IOException {
readCount++;
return target.read();
}
@Override
public int read(byte[] b) throws IOException {
readCount++;
return target.read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
readCount++;
return target.read(b, off, len);
}
@Override
public void close() throws IOException {
target.close();
}
// 可选:重写其他需要委托的方法(如 available()、skip() 等)
@Override
public 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()); // 输出 3
counterStream.close();
}
}
4. 自定义注解,Map 自动加入事件戳
4.1 map是在什么时候生成的
关键是要弄清楚map是在什么时候生成的。
@RestController
@RequestMapping("/")
public class WebController {
@PostMapping
public 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中加入时间戳就可以了。最后只要可以将其放入HandlerMethodArgumentResolverComposite
的List<HandlerMethodArgumentResolver>
中就可以了。
模仿RequestResponseBodyMethodProcessor
类,写出自定义的类。为了能够简化代码,可以利用装饰器模式定义自定义解析器
public class TimestampBodyMethodProcessor implements HandlerMethodArgumentResolver {
private RequestResponseBodyMethodProcessor processor;
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(TimestampResponseBody.class);
}
@Override
public 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()
方法设置的。因此接下来给该方法打上断点,查看该方法是如何被调用的。
发现resolvers
是this.getArgumentResolvers()
产生的,进入该方法中
发现实际加入的自定义resolvers
是由addArgumentResolvers()
产生的。因此继续查看实现的方法。
发现该类也利用了装饰器模式,是调用this.configures的方法进行添加的,并且仔细查看发现该类中的configures
是通过外部注入的方式。然后查看该类的addArgumentResolvers()
方法,发现
发现addArgumentResolvers()
是依次调用this.delegates
中每个delegate.addArgumentResolvers()
,并且使用该对象时时通过外部注入的方式为this.delegates
赋值,
因此我们可以自定义类实现WebMvcConfigurationSupport
接口中的
this.configurers.addArgumentResolvers(argumentResolvers)
方法,并让该类注入到spring的容器,然后利用装饰器模式,在委托对象加入argumentResolvers
之前拦截argumentResolvers
,在其中加入自定义的解析器。
@Component
public class MyWebMvcConfigure implements WebMvcConfigurer {
@Override
public 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;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(TimestampResponseBody.class);
}
@Override
public 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 {
@Resource
private ApplicationContext applicationContext;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new TimestampBodyMethodProcessor(applicationContext));
}
}