实现自定义参数解析器在SpringBoot
- 2020-04-25
- ArchGeass
实现自定义参数解析器在SpringBoot
SpringBoot的@RequestBody注入的实现流程
获取http请求中post的body部分,转换为String,再反序列化为一个对应的对象.
方法参数解析器的组合:InvocableHandlerMethod#private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
->HandlerMethodArgumentResolverComposite#private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<>();
对于每一种参数都有对应的参数解析器,通过调用HandlerMethodArgumentResolverComposite#private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter)
来获取需要的参数解析器,获取到之后进行解析.
实现自定义注解加入SpringBoot的解析器之中
比如实现一个自定义的@MyRequestBody
,使得在原有的解析并反序列化过程中,自动加入一个时间戳的参数.
1 | (ElementType.PARAMETER) |
Spring之所以能够处理@RequestBody
这个注解,是因为在启动过程中,执行查找参数解析器时
HandlerMethodArgumentResolverComposite#private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter)
获取到了@RequestBody
对应的参数解析器,即RequestResponseBodyMethodProcessor#public boolean supportsParameter(MethodParameter parameter);
所以只需要按照同样的方式在HandlerMethodArgumentResolverComposite
中加入自己的参数解析器即可
先简单实现自定义参数解析器
1 | public class MyHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver{ |
spring是在这里实现参数解析器的获取的:
RequestMappingHandlerAdapter#private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers();
通过这个方法的这个判断可以加入自定义的参数解析器
1 | // Custom arguments |
且是在执行这个方法的时候实现的自定义解析器加入:DelegatingWebMvcConfiguration#protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers)
即实现了org.springframework.web.servlet.config.annotation.WebMvcConfigurer
接口
so
只要我们这样实现一个自己的Configuration即可被加入到DelegatingWebMvcConfiguration
之中
实现:
1 |
|
以上这部分代码实现的是如何将自定义参数解析器注入,接下来实现自定义参数解析器
实现的思路是扩展原有的RequestResponseBodyMethodProcessor
,为了不修改原有代码,采用装饰器模式实现:
1 | public class RequestResponseBodyMethodProcessorDecorator implements HandlerMethodArgumentResolver{ |
难点来了,如何将自定义参数解析器加入MyConfiguration
不能直接使用@Autowired
注入RequestResponseBodyMethodProcessor
,
因为它并不是以一个Bean的方式被List<HandlerMethodArgumentResolver>
注入的:resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
可行思路:获取到RequestMappingHandlerAdapter#private HandlerMethodArgumentResolverComposite argumentResolvers;
后,将自己的参数解析器加入进去即可.
ps.可能由于时机等原因,RequestMappingHandlerAdapter
不能使用@Autowired
的字段注入方式获取
思路:采用延迟初始化的方式,在使用时再去获取.
MyConfiguration增加:
1
2
3
4
public MyController.RequestResponseBodyMethodProcessorDecorator myProcessor() {
return new MyController.RequestResponseBodyMethodProcessorDecorator();
}RequestResponseBodyMethodProcessorDecorator修改为:
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
32public class RequestResponseBodyMethodProcessorDecorator implements HandlerMethodArgumentResolver, ApplicationContextAware {
private RequestResponseBodyMethodProcessor delegate;
private ApplicationContext applicationContext;
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(MyRequestBody.class);
}
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
//延迟获取delegate
if (delegate == null) {
delegate = (RequestResponseBodyMethodProcessor) applicationContext.getBean(RequestMappingHandlerAdapter.class)
.getArgumentResolvers().stream()
.filter(p -> p instanceof RequestResponseBodyMethodProcessor)
.findFirst().get();
}
Object resolvedArgument = delegate.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
if (resolvedArgument instanceof Map) {
((Map) resolvedArgument).put("timestamp", System.currentTimeMillis());
}
return resolvedArgument;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}自定义MyConfiguration注入自定义参数解析器实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyConfiguration implements WebMvcConfigurer {
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(myProcessor());
}
public RequestResponseBodyMethodProcessorDecorator myProcessor() {
return new RequestResponseBodyMethodProcessorDecorator();
}
}
到此,成功的实现了自定义注解的功能,成功在public void test(@MyRequestBody Map<String, Object> param)
参数中加入了时间戳
实现spring-boot-starter
SpringBoot实现自动注入外部Bean的原理是:
扫描正确目录下的spring.factories这样的文件,
读取文件中的配置org.springframework.boot.autoconfigure.EnableAutoConfiguration
,
注入其中所有的Bean.
同理在项目的resources/META-INF/spring.factories
中配置org.springframework.boot.autoconfigure.EnableAutoConfiguration=MyConfiguration
,
即可注入自定义的Bean,实现SpringBoot-starter