共计 8354 个字符,预计需要花费 21 分钟才能阅读完成。
前言
本文素材的起源自业务部门技术负责人一次代码走查引发的故事,技术负责人在某次走查成员的代码时,发现他们的业务管制层大量充斥着如下的代码
@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 之前。调整后,咱们测试一下
此时会发现曾经有值填充进去了
总结
本文次要解说自定义 HandlerMethodArgumentResolver 不失效起因与解法,咱们能够思考一个问题批改或者填充申请参数,除了利用 HandlerMethodArgumentResolver 之外,还有没有其余实现形式?下篇文章揭晓答案