起因:
对代码进行重构,把解析token的方法从Controller迁移到自定义的参数解析器中,实现代码复用。
问题:
按照正常流程搭建自定义参数解析器,发现最终请求未进入该自定义参数解析器。
Controller
1 2 3 4 5 6 7 8 9 10
| @PostMapping @NeedDistinct public Result<Void> livenessDetect(@RequestHeader Map<String, String> headers, @RequestBody LivenessParam livenessParam) { if (uid == null){ throw new UserNotExistException(); } }
|
UserInfoArgumentResolver自定义参数解析器
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Component public class UserInfoArgumentResolver extends BaseArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasMethodAnnotation(NeedDistinct.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { return null; }
|
配置类实现WebMvcConfigurer
1 2 3 4 5 6 7 8 9 10 11 12
| @Configuration @EnableAsync public class WebMvcConfig implements WebMvcConfigurer { @Autowired private UserInfoArgumentResolver userInfoArgumentResolver; @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(userInfoArgumentResolver); } }
|
原因:因为在获得参数解析器的过程中,Controller的入参存在@RequestHeader注解的Map参数,mvc优先解析到其他的参数解析器,从而跳过了自定义参数解析器的判断与执行。
解决思路:
通过idea的栈信息,往上层找到调用参数解析器中 supportsParameter 与 resolveArgument的方法,然后定位到HandlerMethodArgumentResolverComposite中的getArgumentResolver方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Nullable private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); if (result == null) { for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) { if (resolver.supportsParameter(parameter)) { result = resolver; this.argumentResolverCache.put(parameter, result); break; } } } return result; }
|
此时对该方法进行debug,发现在遍历argumentResolvers的过程中,优先加载到一个RequestHeaderMethodArgumentResolver的类执行成功,并且放到缓存中。下一次每次发送该Controller的请求,会直接从缓存中获得。
从上面的方法可以看出,mvc在获得参数解析器时, 会先从缓存中获得参数解析器,若不存在,则遍历argumentResolvers,若有一个解析器的supportsParameter结果为true,则对该解析器进行缓存,并退出遍历。
意味着只要有一个其他的参数解析器比我们自定义的UserInfoArgumentResolver优先加载成功,则不会执行我们自定义的解析器的逻辑。
此时再查看argumentResolvers中的解析器。

发现在argumentResolvers集合中,RequestHeaderMethodArgumentResolver类的index比我们自定义解析器的index小,从而优先加载到RequestHeaderMethodArgumentResolver,跳过了我们的自定义解析器。
然后查看查看RequestHeaderMethodArgumentResolver中的逻辑。
1 2 3 4 5 6 7 8 9 10 11 12
| @Override public boolean supportsParameter(MethodParameter parameter) { return (parameter.hasParameterAnnotation(RequestHeader.class) && !Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())); }
@Override protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) { RequestHeader ann = parameter.getParameterAnnotation(RequestHeader.class); Assert.state(ann != null, "No RequestHeader annotation"); return new RequestHeaderNamedValueInfo(ann); }
|
发现当传入参数包含注解@RequestHeader,并且参数为Map时,该参数解析器则返回为true。
所以,看到这里就可以得知,我们只需要把原来Controller中的headers去掉,RequestHeaderMethodArgumentResolver则会返回false,argumentResolvers会继续遍历,直到遍历到我们自定义的UserInfoArgumentResolver为止。或者我们可以在Controller中添加一个自定义参数,即解决走不到自定义解析器的问题。
1 2 3 4 5 6 7 8
| @PostMapping @NeedDistinct public Result<Void> livenessDetect(@RequestBody LivenessParam livenessParam, @RequestBody LivenessParam livenessParam, String needDistinct) { if (uid == null){ throw new UserNotExistException(); } }
|
转载自:
https://dongguabai.blog.csdn.net/article/details/135724410?spm=1001.2014.3001.5502