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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值