设计模式-1 装饰器模式

装饰器模式

简单的来说,就是为某一个类/对象增加一些额外的功能,类似于Spring的AOP,执行方法增强。

Topic

目标:学习装饰器模式
课题:

实现这样一个功能,在接收到post请求之后,为map添加一个timestamp字段,值为当前的时间戳(不依赖Spring的AOP)。

分析

为了实现这个装饰器,我们首先需要知道这里的map是怎么获取到的,这里直接打一个断点进入堆栈,对接口发送图中的请求。

在接口收到请求之后,我们很容易就可以定位到这个方法,进入这个方法中,发现有getMethodArgumentValues这个方法的调用。

点进这个方法,看到这个方法中关于args的赋值逻辑:交给resolver.resolveArgument()进行处理,那么这个真正的逻辑就藏在resolver中。

接着去看resolvers都是什么? (HandlerMethodArgumentResolverComposite)

同时如果不加配置的情况下,resolvers的size应该是27个(这里就不截图了)。因此可以确定,我们如果想实现目标,需要添加一个自定义的resolver或者是对某一个resolver进行增强,也就符合装饰器模式的定义,也就需要再看看Spring是什么时候添加这些resolver的,点进去这个HandlerMethodArgumentResolverComposite给这几个addResolver()打上断点,再看看调用堆栈。

很容易定位到下边这些个箭头的方法~,这个getDefaultArgumentResolvers是一坨巨大的石山(一个个new出来然后再add)。

接着再回来,这里的HandlerMethodArgumentResolverComposite也是一种装饰器模式的实现,即它本身是一个...resolver,里边又塞了一个resolver的list,在实际执行resolve方法的时候,他先获取支持当前param的resolver再丢给对应的resolver进行处理。

那么带有@RequestBody注解的param是通过哪一个resolver进行处理的,答案是RequestResponseBodyMethodProcessor,这时就比较简单了,我们需要查看一下这个resolver是怎么进行supportArgument的,再自定义一个resolver进行增强。

那么接下来我们需要做两件事情:

  1. 检查一下怎么supportArgument
  2. 依据supportArgument的结果,自行魔改一下。
  3. 看一下怎么把我们装饰之后的东西放到先前的resolvers中

可以看到,他是直接检查是否有@RequestBody注解,来判断是否supportArgument,那么controller中的map就是在这个类下的resolveArgument()中创建的,到这,我们就大体可以实现目标了。

简单写一下我们的装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.liuyan.wrapperspringboot.decorater;

import com.liuyan.wrapperspringboot.annotation.TimeStampRequestBody;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import java.util.Map;

/**
* 自定义的resolver,实际上是对RequestResponseBodyMethodProcessor进行装饰
*/
public class TimeStampMethodProcessor implements HandlerMethodArgumentResolver {

HandlerMethodArgumentResolver processor;

/**
* 判断一下含有我们自定义注解的参数
*
* @param parameter
* @return
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(TimeStampRequestBody.class);
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
//直接交给我们装饰的对象进行处理
Object result = processor.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
if (!(result instanceof Map)) {
return result;
}
((Map) result).put("timeStamp", System.currentTimeMillis());
return result;
}
}

那么目标1,2都已经实现了,接着我们要看看怎么把我们的decorater放到resolvers中。还记得我们我们之前说的一段石山代码吗,那里就是初始化这里resolvers代码的敌方,不过我们需要的是添加到自定义的resolver,截一部分图看看吧~。

点进去看看我们要怎么添加,就可以完成目标咯,但是图中的这个类只有他的set方法,那么还需要看看这个set是什么时候被调用的,给set方法打一下断点.

调用他的类名字叫WebMvcConfigurationSupport,大概率是配置类,再根据调用点点点。


这里就是添加argumentResolver的地方,还是一个经典的懒加载,那么再进入一下这里的方法调用,发现这是一个接口,那么找找实现类,其中只有一个DelegatingWebMvcConfiguration,仔细观察一下这个类的代码,又是一个经典的装饰器实现,那我们势必要再看看他装饰的类(WebMvcConfigurerComposite)中addArgumentResolver的实现了,可以找到一个addWebMvcConfigurers的方法被调用,就需要看看DelegatingWebMvcConfiguration是怎么调用这个方法的了。

这里就可以看到了,他收集了所有实现了WebMvcConfigurer接口的类,加入到我们期望的list当中,写一个configurer类,实现WebMvcConfigurer接口,在addArgumentResolvers方法中添加我们的decorater。

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class CustomWebConfigurer implements WebMvcConfigurer {
/**
* 加入我们自定义的解析器
* @param resolvers
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new TimeStampMethodProcessor());
}
}

重新启动一下验证有没有加入。

可以看到是已经加入了,但是现在还有一个问题,我们的自定义类中的processor是没有赋值了,那么该怎么获取到这个东西呢,因为我们原本包装的这个对象是通过new关键字直接加入到对应list当中的,我们需要拿到持有这个list的容器,通过遍历的方式来初始化我们的processor。RequestMappingHandlerAdapter这个类已经交给Spring容器进行管理了,那么事情就很简单。

不过我们没有把自己的类定义成Bean,所以还用不了@Autowired,可以直接通过ApplicationContext来获取,贴一小部分代码

1
2
3
4
5
6
7
8
9
10
11
12
	private void setupProcessor() {
if (processor != null) {
return;
}
RequestMappingHandlerAdapter adapter = this.applicationContext.getBean(RequestMappingHandlerAdapter.class);
for (HandlerMethodArgumentResolver resolver : adapter.getArgumentResolvers()) {
if (resolver instanceof RequestResponseBodyMethodProcessor) {
processor = resolver;
}
}
}
}

至此,我们的目标就完成了,结果如下图

代码链接: https://github.com/JisoLya/DesignPattern/tree/master/WrapperSpringBoot


设计模式-1 装饰器模式
http://example.com/2025/09/04/设计模式/
作者
Soya
发布于
2025年9月4日
许可协议