前言

本文素材的起源自业务部门技术负责人一次代码走查引发的故事,技术负责人在某次走查成员的代码时,发现他们的业务管制层大量充斥着如下的代码

@PostMapping("add")    public User add(@RequestBody User user, HttpServletRequest request){        String tenantId = request.getHeader("x-tenantid");        String appId = request.getHeader("x-appid");        user.setAppId(appId);        user.setTenantId(tenantId);        return user;    }

他们的tenantId和appId是作为元数据放在申请头,而业务model又须要tenantId和appId,于是他们团队的成员就写出了形如上的代码,尽管这样的代码是能满足业务要求,然而大面积如上的写法,都是重复性的代码,很不优雅。前面这个技术负责人项通过自定义HandlerMethodArgumentResolver的形式来优雅解决这问题,他的代码形如下

@Datapublic class MetaInfo {    private String tenantId;        private String appId;    }
public class MetaInfoHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {    private RequestResponseBodyMethodProcessor handlerMethodArgumentResolver;    public MetaInfoHandlerMethodArgumentResolver(RequestResponseBodyMethodProcessor handlerMethodArgumentResolver) {        this.handlerMethodArgumentResolver = handlerMethodArgumentResolver;    }    @Override    public boolean supportsParameter(MethodParameter parameter) {        return parameter.hasParameterAnnotation(RequestBody.class) && MetaInfo.class.isAssignableFrom(parameter.getParameterType());    }    @Override    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {        MetaInfo metaInfo = (MetaInfo) handlerMethodArgumentResolver.resolveArgument(parameter,mavContainer,webRequest,binderFactory);        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);        metaInfo.setAppId(request.getHeader("x-appid"));        metaInfo.setTenantId(request.getHeader("x-tenantid"));        return metaInfo;    }}
@Configurationpublic class WebConfig implements WebMvcConfigurer {    @Autowired    private MetaInfoHandlerMethodArgumentResolver metaInfoHandlerMethodArgumentResolver;    @Bean    @ConditionalOnMissingBean    public MetaInfoHandlerMethodArgumentResolver metaInfoHandlerMethodArgumentResolver(List<HttpMessageConverter<?>> httpMessageConverters){        RequestResponseBodyMethodProcessor handlerMethodArgumentResolver = new RequestResponseBodyMethodProcessor(httpMessageConverters);        return new MetaInfoHandlerMethodArgumentResolver(handlerMethodArgumentResolver);    }    @Override    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {        resolvers.add(metaInfoHandlerMethodArgumentResolver);    }}

当他写下如下代码时,按他的想法应该是没问题才对,然而事实上这个HandlerMethodArgumentResolver却无奈失效,他排查了很久,没啥脉络,于是就找我探讨了一下。本文就来聊一下该自定义HandlerMethodArgumentResolver不失效起因

为何自定义的HandlerMethodArgumentResolver不失效

看过springmvc的源码或者背过springmvc相干八股文的敌人,可能会晓得springmvc执行HandlerMethodArgumentResolver,次要是通过HandlerMethodArgumentResolverComposite这个聚合器来进行执行。而HandlerMethodArgumentResolverComposite这个聚合器是如何获取要执行的HandlerMethodArgumentResolver呢?咱们能够间接查看源码

  @Nullable    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {        HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);        if (result == null) {            Iterator var3 = this.argumentResolvers.iterator();            while(var3.hasNext()) {                HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();                if (resolver.supportsParameter(parameter)) {                    result = resolver;                    this.argumentResolverCache.put(parameter, resolver);                    break;                }            }        }        return result;    }

看到这个源码,我想老司机应该会有点脉络,HandlerMethodArgumentResolverComposite外部是会保护一个key为MethodParameter,值为HandlerMethodArgumentResolver的本地缓存,因而要取HandlerMethodArgumentResolver,就会通过MethodParameter来取。

接着咱们在来思考一个问题,源码中的this.argumentResolvers的是什么时候放进去的,咱们持续跟踪源码会发现,他是通过

  public HandlerMethodArgumentResolverComposite addResolvers(@Nullable List<? extends HandlerMethodArgumentResolver> resolvers) {        if (resolvers != null) {            this.argumentResolvers.addAll(resolvers);        }        return this;    }

这个办法进行增加。而addResolvers又是什么时候被调用的,咱们持续跟踪源码,会发现addResolvers,他是会RequestMappingHandlerAdapter的afterPropertiesSet办法中的被调用

@Override    public void afterPropertiesSet() {                if (this.argumentResolvers == null) {            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);        }        if (this.initBinderArgumentResolvers == null) {            List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();            this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);        }    

从这个代码片段,咱们能够看到HandlerMethodArgumentResolverComposite初始会增加一些默认的HandlerMethodArgumentResolver

    List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);

而getDefaultArgumentResolvers这办法点开

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();        // Annotation-based argument resolution        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));        resolvers.add(new RequestParamMapMethodArgumentResolver());        resolvers.add(new PathVariableMethodArgumentResolver());        resolvers.add(new PathVariableMapMethodArgumentResolver());        resolvers.add(new MatrixVariableMethodArgumentResolver());        resolvers.add(new MatrixVariableMapMethodArgumentResolver());        resolvers.add(new ServletModelAttributeMethodProcessor(false));        resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));        resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));        resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));        resolvers.add(new RequestHeaderMapMethodArgumentResolver());        resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));        resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));        resolvers.add(new SessionAttributeMethodArgumentResolver());        resolvers.add(new RequestAttributeMethodArgumentResolver());        // Type-based argument resolution        resolvers.add(new ServletRequestMethodArgumentResolver());        resolvers.add(new ServletResponseMethodArgumentResolver());        resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));        resolvers.add(new RedirectAttributesMethodArgumentResolver());        resolvers.add(new ModelMethodProcessor());        resolvers.add(new MapMethodProcessor());        resolvers.add(new ErrorsMethodArgumentResolver());        resolvers.add(new SessionStatusMethodArgumentResolver());        resolvers.add(new UriComponentsBuilderMethodArgumentResolver());        // Custom arguments        if (getCustomArgumentResolvers() != null) {            resolvers.addAll(getCustomArgumentResolvers());        }        // Catch-all        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));        resolvers.add(new ServletModelAttributeMethodProcessor(true));        return resolvers;    }

就会发现一堆默认的HandlerMethodArgumentResolver,有教训的老司机看到这里,应该就晓得为啥自定义HandlerMethodArgumentResolver会生效了吧。

自定义HandlerMethodArgumentResolver会生效的起因是当咱们办法中有引入@RequestBody时,他的用到的HandlerMethodArgumentResolver是RequestResponseBodyMethodProcessor,而咱们自定义的
HandlerMethodArgumentResolver是通过setCustomArgumentResolvers塞进去,而从源码咱们能够看出,咱们自定义的HandlerMethodArgumentResolver是放在默认的HandlerMethodArgumentResolver之后

当咱们办法中同时存在@RequestBody和自定义HandlerMethodArgumentResolver,因为他们的Method雷同,即MethodParameter一样,因而argumentResolverCache的key是一样的,从一开始的源码咱们就能够得悉,当key曾经找到值时,它就间接返回了,因而当它找到@RequestBody的HandlerMethodArgumentResolver,它就不会再找自定义的HandlerMethodArgumentResolver,这就会导致咱们自定义的HandlerMethodArgumentResolver不失效

HandlerMethodArgumentResolver不失效的解法

1、办法一:间接去掉办法中的@RequestBody

去掉办法中的@RequestBody,此时办法就不存在解析@RequestBody的HandlerMethodArgumentResolver,因而就只剩咱们自定义的HandlerMethodArgumentResolver必然会执行

2、办法二:进步咱们自定义HandlerMethodArgumentResolver的执行程序

具体做法如下

@Configurationpublic class HandlerMethodArgumentResolverAutoConfiguration implements InitializingBean{    @Autowired    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;        @Override    public void afterPropertiesSet() throws Exception {        List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();        List<HandlerMethodArgumentResolver> customArgumentResolvers = new ArrayList<>();        for (HandlerMethodArgumentResolver argumentResolver : argumentResolvers) {            if(argumentResolver instanceof RequestResponseBodyMethodProcessor){                 customArgumentResolvers.add(new MetaInfoHandlerMethodArgumentResolver(argumentResolver));            }            customArgumentResolvers.add(argumentResolver);        }        requestMappingHandlerAdapter.setArgumentResolvers(customArgumentResolvers);    }}

将自定义的HandlerMethodArgumentResolver放在解析@RequestBody的HandlerMethodArgumentResolver之前。调整后,咱们测试一下


此时会发现曾经有值填充进去了

总结

本文次要解说自定义HandlerMethodArgumentResolver不失效起因与解法,咱们能够思考一个问题批改或者填充申请参数,除了利用HandlerMethodArgumentResolver之外,还有没有其余实现形式?下篇文章揭晓答案