上一节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
设置过来的。
然后从这些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. 接口实现
可以看到的实现的类非常多。我们测试的接口:
@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
支持:
@RequestParam
标注的参数的处理,- 没有标注
@RequestParam
的简单类型,如int, long,String
等; 结合MultipartResolver
,也支持@RequestParam MultipartFile file
- 结合
Servlet 3.0 multipart requests
,也支持javax.servlet.http.Part
HandlerMethodArgumentResolver
的实现类非常多。我们一定使用过@RequestParam
,PathVariable
,@Value
,@RequestHeader
,@CookieValue
,SessionAttribute
,RequestAttribute
等等。而这些的解析起都是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;
}
}
}
利用模版设计模式,该抽象类中定义了解析参数的主要逻辑,子类只需要实现对应的抽象模版方法即可。
这里面规定了如下流程:
- 基于
MethodParameter
构建NameValueInfo
,主要是要解析方法参数上标注的注解 - 解析
${}
占位符以及SpEL
得到解析后的name
-
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest)
,解析参数值(具体由子类去实现) - 处理没有解析出参数值的一些情况
- 通过
WebDataBinder
将arg
转换成Methodparameter.getParameterType()
类型。
所以子类的话,需要做的就是:
实现
public boolean supportsParameter(MethodParameter parameter)
实现
protected abstract NamedValueInfo createNamedValueInfo(MethodParameter parameter);
实现
protected abstract Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception
其他非必须的,比如
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
,主要是利用默认值来解析@Value
里value
值。
// 支持@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());
}
}
剩下一些就不去一一记录了,原理差不多,也比简单。
三. 总结
总结一下到这主要分析得到了些什么:
- 代码定位到了参数解析这一步,并debug看到了默认的
resolvers
,可以看到其中默认的顺序; - 了解
HandlerMethodArgumentResolver
的作用以及它的实现结构图; - 分析了
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
了,我们下一篇接着分析。