实现自定义参数解析器在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
2
3
4
5
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestBody {
}

Spring之所以能够处理@RequestBody这个注解,是因为在启动过程中,执行查找参数解析器时

HandlerMethodArgumentResolverComposite#private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter)
获取到了@RequestBody对应的参数解析器,即
RequestResponseBodyMethodProcessor#public boolean supportsParameter(MethodParameter parameter);
所以只需要按照同样的方式在HandlerMethodArgumentResolverComposite中加入自己的参数解析器即可

先简单实现自定义参数解析器

1
2
3
4
5
6
7
8
9
10
11
12
public class MyHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver{
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(MyRequestBody.class);
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return null;
}
}

spring是在这里实现参数解析器的获取的:

RequestMappingHandlerAdapter#private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers();
通过这个方法的这个判断可以加入自定义的参数解析器

1
2
3
4
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}

且是在执行这个方法的时候实现的自定义解析器加入:
DelegatingWebMvcConfiguration#protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers)
即实现了org.springframework.web.servlet.config.annotation.WebMvcConfigurer接口
so
只要我们这样实现一个自己的Configuration即可被加入到DelegatingWebMvcConfiguration之中
实现:

1
2
3
4
5
@Configuration
public class MyConfiguration implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {}
}

以上这部分代码实现的是如何将自定义参数解析器注入,接下来实现自定义参数解析器

实现的思路是扩展原有的RequestResponseBodyMethodProcessor,为了不修改原有代码,采用装饰器模式实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class RequestResponseBodyMethodProcessorDecorator implements HandlerMethodArgumentResolver{

private RequestResponseBodyMethodProcessor delegate;

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(MyRequestBody.class);
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Object resolvedArgument = delegate.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
//实现对于Map类型的参数注入时间戳的功能
if (parameter instanceof Map) {
((Map) parameter).put("timestamp", System.currentTimeMillis());
}
return resolvedArgument;
}

}

难点来了,如何将自定义参数解析器加入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
    @Bean
    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
    32
    public class RequestResponseBodyMethodProcessorDecorator implements HandlerMethodArgumentResolver, ApplicationContextAware {

    private RequestResponseBodyMethodProcessor delegate;

    private ApplicationContext applicationContext;

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(MyRequestBody.class);
    }

    @Override
    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;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
    }
    }
  • 自定义MyConfiguration注入自定义参数解析器实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Configuration
    public class MyConfiguration implements WebMvcConfigurer {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    resolvers.add(myProcessor());
    }

    @Bean
    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