WebArgumentResolver的resolveArgument回调为什么只执行了一次
WebArgumentResolver的resolveArgument回调为什么只执行了一次
一、问题场景
今天coding debug的时候遇到一个问题,就是通过下列代码完成的账号Account参数自动注入,第一次请求,会触发resolveArgument方法,后面再请求,怎么都无法再进入resolveArgument回调了。代码大致如下:
配置:
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
//。。。
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new ServletWebArgumentResolverAdapter(new WebAccountArgResolver()));
}
//。。
}
解析header中的userId,然后处理:
@Component
public class WebAccountArgResolver implements WebArgumentResolver {
@Override
public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest nativeWebRequest) {
if (null != methodParameter.getParameterType() && methodParameter.getParameterType().equals(Account.class)) {
HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
String userId = request.getHeader("userId");
//dosomething
log.info("不存在当前账号的登录信息,userId={}", userId);
//** 后来分析发现,如果是Account参数,无法处理的话,这里应该加上 return null;这样下次也会走这个resolver判断。
}
return UNRESOLVED;
}
}
后来通过分析spring源码找到了问题的关键,对于Account参数无法处理的时候,也要返回null,而不是UNRESOLVED,返回UNRESOLVED spring会认为这个方法不适用这个 WebArgumentResolver,下次也不会找到这个customResolver这里来。
下面进入源码分析:
二、关键源码分析
源码地址:git@github.com:spring-projects/spring-framework.git
源码分支:5.3.x
commitId:a18842b72b779664bc9ae40714a86a08449958d9
1、org.springframework.web.method.support.InvocableHandlerMethod
/**
* Get the method argument values for the current request, checking the provided
* argument values and falling back to the configured argument resolvers.
* <p>The resulting array will be passed into {@link #doInvoke}.
* @since 5.1.2
*/
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
上面是RequestMapping方法处理参数时的调用,会调用下面类HandlerMethodArgumentResolverComposite的 resolveArgument方法来对所有的参数进行处理。
2、org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
/**
* Iterate over registered
* {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}
* and invoke the one that supports it.
* @throws IllegalArgumentException if no suitable argument resolver is found
*/
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
/**
* Find a registered {@link HandlerMethodArgumentResolver} that supports
* the given method parameter.
*/
@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;
}
这里的关键在于 argumentResolverCache 缓存对象,所有的Resolvers按照顺序,判断是否 supportsParameter ,如果支持的话,会把这个MethodParameter 和 HandlerMethodArgumentResolver 的处理关系缓存起来,这次是A处理了,下次也交给A处理。
而我们做配置时,自定义的参数处理器是通过org.springframework.web.method.annotation.AbstractWebArgumentResolverAdapter包装的,该类的 supportsParameter实现是:
public boolean supportsParameter(MethodParameter parameter) {
try {
NativeWebRequest webRequest = this.getWebRequest();
Object result = this.adaptee.resolveArgument(parameter, webRequest);
return result == WebArgumentResolver.UNRESOLVED ? false : ClassUtils.isAssignableValue(parameter.getParameterType(), result);
} catch (Exception var4) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Error in checking support for parameter [" + parameter + "]: " + var4.getMessage());
}
return false;
}
}
如果返回WebArgumentResolver.UNRESOLVED对象,代表这个Resolver不能处理这个参数类型,如果非 UNRESOLVED ,代表可以处理。
3、org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getDefaultArgumentResolvers
/**
* Return the list of argument resolvers to use including built-in resolvers
* and custom resolvers provided via {@link #setCustomArgumentResolvers}.
*/
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);
// 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());
if (KotlinDetector.isKotlinPresent()) {
resolvers.add(new ContinuationHandlerMethodArgumentResolver());
}
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new PrincipalMethodArgumentResolver());
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
关键代码:resolvers.addAll(getCustomArgumentResolvers());
我们自定义的resolvers都在这里添加进来,后续的注释//Catch-all。 就是我们自定义的Resolvers都处理不了的参数,都会被后面的resolvers捕获。
如果我们的Resolvers supportsParameter()某个参数类型返回的是false,那么后续该类型的入参都不会交给我们自定义的 Resolvers 处理。
三、问题总结
如果想通过自定义WebAccountArgResolver回调,来给请求方法注入参数,默认supportsParameter方法实现,对于想要处理的参数类型,一定不要返回 UNRESOLVED对象,可以返回null或者其它自定义对象。
否则,在这次spring的启动周期下,所有的该方法请求,如果第一次返回UNRESOLVED对象,后续该方法都不会触发这个 WebAccountArgResolver。
更优雅的方式,配置时重写supportsParameter方法,指定需要解析哪种class类型的参数,也是可以的。
验证:e5eb2d1d-1312-4b0f-839e-eb7c2eb31332