自定义handlermapping_method.invoke方法参数

(3) 2024-08-31 11:23

Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说
自定义handlermapping_method.invoke方法参数,希望能够帮助你!!!。

前言

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

@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的方式来优雅解决这问题,他的代码形如下

@Data public 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; } } 
@Configuration public 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的执行顺序

具体做法如下

@Configuration public 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之前。调整后,我们测试一下

自定义handlermapping_method.invoke方法参数_https://bianchenghao6.com/blog__第1张


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

总结

本文主要讲解自定义HandlerMethodArgumentResolver不生效原因与解法,我们可以思考一个问题修改或者填充请求参数,除了利用HandlerMethodArgumentResolver之外,还有没有其他实现方式?下篇文章揭晓答案

今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

上一篇

已是最后文章

下一篇

已是最新文章

发表回复