SpringMvc源码分析5.1——HandlerMethodArgumentResolver


上一节Post not found: SpringMvc源码分析4.2——HandlerAdapter SpringMvc源码分析4.2——HandlerAdapter,我们留下了几个问题。这里就先来分析一下遗留的第一个问题——handler参数的解析过程。

一. 追踪源码

上一节,我们定位到了ServletInvocableHandlerMethod#invokeAndHandle:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {
   // 重点,从请求上下文中解析出参数值,然后调用方法
   Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
   // 设置响应状态
   setResponseStatus(webRequest);
   if (returnValue == null) {
      if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
         disableContentCachingIfNecessary(webRequest);
         mavContainer.setRequestHandled(true);
         return;
      }
   }
   else if (StringUtils.hasText(getResponseStatusReason())) {
      mavContainer.setRequestHandled(true);
      return;
   }
   mavContainer.setRequestHandled(false);
   Assert.state(this.returnValueHandlers != null, "No return value handlers");
   try {
      // 重点关注,调用匹配的HandlerMethodReturnValueHandler
      this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
   }
   catch (Exception ex) {
      if (logger.isTraceEnabled()) {
         logger.trace(formatErrorForReturnValue(returnValue), ex);
      }
      throw ex;
   }
}

接下来我们就看一下invokeForRequest(webRequest, mavContainer, providedArgs)

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

  	// 解析参数
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
			logger.trace("Arguments: " + Arrays.toString(args));
		}
		return doInvoke(args);
	}

该方法主要就是解析参数,然后调用。

主要看下Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs):

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);
      // 从提供的providedArgs获取参数值
			args[i] = findProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
      
			if (!this.resolvers.supportsParameter(parameter)) {
				throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
			}
			try {
        // 利用resolvers解析参数,重点关注
				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;
	}

参考上面代码上的注释。主要我们关注this.resolvers.resolveArgument(..).而resolvers是从RequestMappingHandlerAdapter设置过来的。

resolvers

然后从这些argumentResolvers找到匹配的HandlerMethodArgumentResolver,然后执行它的resolveArgument(..)获得参数值。:

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);
	}

到这我们可以看到参数的解析会依赖HandlerMethodArgumentResolver。所以接下来我们就主要看一下HandlerMethodArgumentResolver

二. HandlerMethodArgumentResolver

该接口是一个从请求上下文解析方法参数的策略接口

1. 接口规范

public interface HandlerMethodArgumentResolver {

	/**
	 * Whether the given {@linkplain MethodParameter method parameter} is
	 * supported by this resolver.
	 * @param parameter the method parameter to check
	 * @return {@code true} if this resolver supports the supplied parameter;
	 * {@code false} otherwise
	 */
  // 是否支持给某个方法参数的解析
	boolean supportsParameter(MethodParameter parameter);

	/**
	 * Resolves a method parameter into an argument value from a given request.
	 * A {@link ModelAndViewContainer} provides access to the model for the
	 * request. A {@link WebDataBinderFactory} provides a way to create
	 * a {@link WebDataBinder} instance when needed for data binding and
	 * type conversion purposes.
	 * @param parameter the method parameter to resolve. This parameter must
	 * have previously been passed to {@link #supportsParameter} which must
	 * have returned {@code true}.
	 * @param mavContainer the ModelAndViewContainer for the current request
	 * @param webRequest the current request
	 * @param binderFactory a factory for creating {@link WebDataBinder} instances
	 * @return the resolved argument value, or {@code null} if not resolvable
	 * @throws Exception in case of errors with the preparation of argument values
	 */
	@Nullable
  // 从给定的请求解析参数值
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

2. 接口实现

HandlerMethodArgumentResolver

可以看到的实现的类非常多。我们测试的接口:

@RestController
public class IndexController {
    @GetMapping("/hello")
    public String hello(String name) {
        return "hello: " + name;
    }
}

匹配到的则是RequestParamMethodArgumentResolver.

那我们来看一下RequestParamMethodArgumentResolver#supportsParameter:

public boolean supportsParameter(MethodParameter parameter) {
   if (parameter.hasParameterAnnotation(RequestParam.class)) {
      if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
         RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
         return (requestParam != null && StringUtils.hasText(requestParam.name()));
      }
      else {
         return true;
      }
   }
   else {
      if (parameter.hasParameterAnnotation(RequestPart.class)) {
         return false;
      }
      parameter = parameter.nestedIfOptional();
      if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
         return true;
      }
      else if (this.useDefaultResolution) {
         return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
      }
      else {
         return false;
      }
   }
}

可以看到RequestParamMethodArgumentResolver支持:

  1. @RequestParam标注的参数的处理,
  2. 没有标注@RequestParam的简单类型,如int, long,String等;
  3. 结合MultipartResolver,也支持@RequestParam MultipartFile file
  4. 结合Servlet 3.0 multipart requests,也支持javax.servlet.http.Part

HandlerMethodArgumentResolver的实现类非常多。我们一定使用过@RequestParam,PathVariable,@Value,@RequestHeader,@CookieValue,SessionAttribute,RequestAttribute等等。而这些的解析起都是AbstractNamedValueMethodArgumentResolver的子类。我们先来看一下类的实现继承关系图,然后再分析源码:

AbstractNamedValueMethodArgumentResolver

3. AbstractNamedValueMethodArgumentResolver

这是一个从命名值中解析方法参数的基类。比如从请求参数,请求头路径变量中获取。每种方式都有一个名称,是否必须的标识以及一个默认值。

子类需要做的事情:

  • 获取方法参数的命名值信息
  • 将名称解析为参数值
  • 在需要参数值时处理缺少的参数值
  • (可选)处理解析值

需要注意的一点就是默认String值支持${...}EL表达式,但是这歌功能的支持需要ConfigurableBeanFactory的支持。

看一下代码:

public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {

   @Nullable
   private final ConfigurableBeanFactory configurableBeanFactory;

   @Nullable
   private final BeanExpressionContext expressionContext;

   private final Map<MethodParameter, NamedValueInfo> namedValueInfoCache = new ConcurrentHashMap<>(256);

   @Override
   @Nullable
   public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
         NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

     	// 这一步获取参数名称相关信息
      NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
      //对于Optional的处理
      MethodParameter nestedParameter = parameter.nestedIfOptional();

      // 解析${...},EL表达式,获取解析后的nama值
      Object resolvedName = resolveStringValue(namedValueInfo.name);
      if (resolvedName == null) {
         throw new IllegalArgumentException(
               "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
      }

     // 获取参数值,模版方法,具体的实现交给子类
      Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
      if (arg == null) {
         if (namedValueInfo.defaultValue != null) {
           // 设置了默认值
            arg = resolveStringValue(namedValueInfo.defaultValue);
         }
         else if (namedValueInfo.required && !nestedParameter.isOptional()) {
            // 处理没有值的情况,默认抛出异常,子类可以Override
            handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
         }
         arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
      }
      else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
         arg = resolveStringValue(namedValueInfo.defaultValue);
      }

      // 数据绑定
      if (binderFactory != null) {
         WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
         try {
            // 是否需要进行参数类型的转换
            arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
         }
         catch (ConversionNotSupportedException ex) {
            throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
                  namedValueInfo.name, parameter, ex.getCause());
         }
         catch (TypeMismatchException ex) {
            throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
                  namedValueInfo.name, parameter, ex.getCause());

         }
      }

     
      // protected的方法,交给子类去复写(并不是必须的)
      handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

      return arg;
   }

   /**
    * Obtain the named value for the given method parameter.
    */
   private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
      NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
      if (namedValueInfo == null) {
         // 抽象方法,交给子类实现
         namedValueInfo = createNamedValueInfo(parameter);
         namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
         this.namedValueInfoCache.put(parameter, namedValueInfo);
      }
      return namedValueInfo;
   }


   /**
    * Represents the information about a named value, including name, whether it's required and a default value.
    */
   protected static class NamedValueInfo {

      private final String name;

      private final boolean required;

      @Nullable
      private final String defaultValue;

      public NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {
         this.name = name;
         this.required = required;
         this.defaultValue = defaultValue;
      }
   }

}

利用模版设计模式,该抽象类中定义了解析参数的主要逻辑,子类只需要实现对应的抽象模版方法即可。
这里面规定了如下流程:

  1. 基于MethodParameter构建NameValueInfo,主要是要解析方法参数上标注的注解
  2. 解析${}占位符以及SpEL得到解析后的name
  3. Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest),解析参数值(具体由子类去实现)
  4. 处理没有解析出参数值的一些情况
  5. 通过WebDataBinderarg转换成Methodparameter.getParameterType()类型。

所以子类的话,需要做的就是:

  1. 实现public boolean supportsParameter(MethodParameter parameter)

  2. 实现protected abstract NamedValueInfo createNamedValueInfo(MethodParameter parameter);

  3. 实现protected abstract Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception

  4. 其他非必须的,比如handleMissingValue(..),handleResolvedValue(..)

那么我们来看一下一些具体的子类实现:

1. RequestParamMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {
		if (parameter.hasParameterAnnotation(RequestParam.class)) {
			if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
				RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
				return (requestParam != null && StringUtils.hasText(requestParam.name()));
			}
			else {
				return true;
			}
		}
		else {
			if (parameter.hasParameterAnnotation(RequestPart.class)) {
				return false;
			}
			parameter = parameter.nestedIfOptional();
			if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
				return true;
			}
			else if (this.useDefaultResolution) {
				return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
			}
			else {
				return false;
			}
		}
	}

支持的方法参数的判断,比如@RequestParam,Optional,简单类型的解析。

protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
		RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
		return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
	}

其实就是获取@RequestParam注解里面的信息

protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
		HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);

		if (servletRequest != null) {
			Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
			if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
				return mpArg;
			}
		}

		Object arg = null;
		MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
		if (multipartRequest != null) {
			List<MultipartFile> files = multipartRequest.getFiles(name);
			if (!files.isEmpty()) {
				arg = (files.size() == 1 ? files.get(0) : files);
			}
		}
		if (arg == null) {
			String[] paramValues = request.getParameterValues(name);
			if (paramValues != null) {
				arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
			}
		}
		return arg;
	}

利用request.getParameterValues(name)获取值,也支持Multipart请求(如:文件上传)的解析。

这里举一个${..}el表达式的示例吧:

在配置文件application.yml里面添加配置:

app:
  arg0: name

然后添加一个接口:

@GetMapping("test")
    @ResponseBody
    public String test(@RequestParam("${app.arg0}") String a,
                @RequestParam(value = "age", required = false, defaultValue = "#{12 * 2}") String b) {
        log.info("a: {}, b: {}", a, b);
        return "ok";
    }

我们就是要演示通过占位符获取name的值,和得到默认的age,请求:

curl http://localhost:8080/namedValue/test10?name=jack

打印结果:

2020-02-26 21:51:32.396  INFO 84285 --- [nio-8080-exec-1] c.j.s.c.ArgumentsResolveController       : a: jack, b: 24
2. PathVariableMethodArgumentResolver
@Override
	public boolean supportsParameter(MethodParameter parameter) {
		if (!parameter.hasParameterAnnotation(PathVariable.class)) {
			return false;
		}
		if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
			PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
			return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
		}
		return true;
	}

支持@PathVariable的解析

3. ExpressionValueMethodArgumentResolver

这个比较简单,支持@Value的解析,resolveName(..)直接返回null,主要是利用默认值来解析@Valuevalue值。

// 支持@Value参数
public boolean supportsParameter(MethodParameter parameter) {
  return parameter.hasParameterAnnotation(Value.class);
}

// 直接返回null
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
  // No name to resolve
  return null;
}

@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
  Value ann = parameter.getParameterAnnotation(Value.class);
  Assert.state(ann != null, "No Value annotation");
  return new ExpressionValueNamedValueInfo(ann);
}


private static final class ExpressionValueNamedValueInfo extends NamedValueInfo {
	// 将注解的value值赋值给defaultValue,利用defaultValue的解析得到参数值
  private ExpressionValueNamedValueInfo(Value annotation) {
    super("@Value", false, annotation.value());
  }
}

剩下一些就不去一一记录了,原理差不多,也比简单。

三. 总结

总结一下到这主要分析得到了些什么:

  1. 代码定位到了参数解析这一步,并debug看到了默认的resolvers,可以看到其中默认的顺序;
  2. 了解HandlerMethodArgumentResolver的作用以及它的实现结构图;
  3. 分析了AbstractNamedValueMethodArgumentResolver的作用,了解了它一些子类的的实现。

在controller方法里到底支持那些参数类型呢?直接拿官方文档看一下:

Controller method argument Description
WebRequest, NativeWebRequest Generic access to request parameters and request and session attributes, without direct use of the Servlet API.
javax.servlet.ServletRequest, javax.servlet.ServletResponse Choose any specific request or response type — for example, ServletRequest, HttpServletRequest, or Spring’s MultipartRequest, MultipartHttpServletRequest.
javax.servlet.http.HttpSession Enforces the presence of a session. As a consequence, such an argument is never null. Note that session access is not thread-safe. Consider setting the RequestMappingHandlerAdapter instance’s synchronizeOnSession flag to true if multiple requests are allowed to concurrently access a session.
javax.servlet.http.PushBuilder Servlet 4.0 push builder API for programmatic HTTP/2 resource pushes. Note that, per the Servlet specification, the injected PushBuilder instance can be null if the client does not support that HTTP/2 feature.
java.security.Principal Currently authenticated user — possibly a specific Principal implementation class if known.
HttpMethod The HTTP method of the request.
java.util.Locale The current request locale, determined by the most specific LocaleResolver available (in effect, the configured LocaleResolver or LocaleContextResolver).
java.util.TimeZone + java.time.ZoneId The time zone associated with the current request, as determined by a LocaleContextResolver.
java.io.InputStream, java.io.Reader For access to the raw request body as exposed by the Servlet API.
java.io.OutputStream, java.io.Writer For access to the raw response body as exposed by the Servlet API.
@PathVariable For access to URI template variables. See URI patterns.
@MatrixVariable For access to name-value pairs in URI path segments. See Matrix Variables.
@RequestParam For access to the Servlet request parameters, including multipart files. Parameter values are converted to the declared method argument type. See @RequestParam as well as Multipart.Note that use of @RequestParam is optional for simple parameter values. See “Any other argument”, at the end of this table.
@RequestHeader For access to request headers. Header values are converted to the declared method argument type. See @RequestHeader.
@CookieValue For access to cookies. Cookies values are converted to the declared method argument type. See @CookieValue.
@RequestBody For access to the HTTP request body. Body content is converted to the declared method argument type by using HttpMessageConverter implementations. See @RequestBody.
HttpEntity For access to request headers and body. The body is converted with an HttpMessageConverter. See HttpEntity.
@RequestPart For access to a part in a multipart/form-data request, converting the part’s body with an HttpMessageConverter. See Multipart.
java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap For access to the model that is used in HTML controllers and exposed to templates as part of view rendering.
RedirectAttributes Specify attributes to use in case of a redirect (that is, to be appended to the query string) and flash attributes to be stored temporarily until the request after redirect. See Redirect Attributes and Flash Attributes.
@ModelAttribute For access to an existing attribute in the model (instantiated if not present) with data binding and validation applied. See @ModelAttribute as well as Model and DataBinder.Note that use of @ModelAttribute is optional (for example, to set its attributes). See “Any other argument” at the end of this table.
Errors, BindingResult For access to errors from validation and data binding for a command object (that is, a @ModelAttribute argument) or errors from the validation of a @RequestBody or @RequestPart arguments. You must declare an Errors, or BindingResult argument immediately after the validated method argument.
SessionStatus + class-level @SessionAttributes For marking form processing complete, which triggers cleanup of session attributes declared through a class-level @SessionAttributes annotation. See @SessionAttributes for more details.
UriComponentsBuilder For preparing a URL relative to the current request’s host, port, scheme, context path, and the literal part of the servlet mapping. See URI Links.
@SessionAttribute For access to any session attribute, in contrast to model attributes stored in the session as a result of a class-level @SessionAttributes declaration. See @SessionAttribute for more details.
@RequestAttribute For access to request attributes. See @RequestAttribute for more details.
Any other argument If a method argument is not matched to any of the earlier values in this table and it is a simple type (as determined by BeanUtils#isSimpleProperty, it is a resolved as a @RequestParam. Otherwise, it is resolved as a @ModelAttribute.

对于每种支持的类型,我们都可以找到具体的HandlerMethodArgumentResolver,大家可以debug一探究竟。在工作中我使用的比较多的还有@RequestBody,那么也很有必要去分析一下对应的源码。这就涉及到RequestResponseBodyMethodProcessor了,我们下一篇接着分析。


文章作者: shiv
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 shiv !
评论
  目录