本系列文章的上一篇 : Spring MVC : 控制器方法处理请求的过程分析 - 1. 概述
作为RequestMappingHandlerAdapter
调用控制器方法处理请求的第一步,应该就是从请求中获取参数了。我们从RequestMappingHandlerAdapter
调用目标控制器方法的入口#invokeHandlerMethod
开始像剥洋葱一样逐层深入 :
RequestMappingHandlerAdapter#invokeHandlerMethod
==> ServletInvocableHandlerMethod#invokeAndHandle
==> InvocableHandlerMethod#invokeForRequest
走到InvocableHandlerMethod#invokeForRequest
时,我们可以看到它的代码实现如下 :
@Nullable
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);
}
在这个方法签名中,参数request
表示当前请求,mavContainer
是调用者RequestMappingHandlerAdapter
准备的用于记录请求处理过程中各种信息的持有器对象,而providedArgs
是外部提供的无需解析既可直接应用的参数(缺省情况下,这个参数为空)。这些参数,结合调用者设置到当前ServletInvocableHandlerMethod
对象上的各种属性共同构成了分析请求参数和执行目标控制器方法的上下文。
该方法的逻辑实现也不难理解,可以简单地理解成两步 :
- 获取控制器方法参数值
getMethodArgumentValues
到对象数组args
; - 使用
args
调用目标控制器方法,返回值通过Object
来接收,实际类型是控制器方法的返回值类型,由开发人员指定;
本文的关注要点,就是这里的第一个小步骤getMethodArgumentValues
是如何工作的。该方法代码如下 :
protected Object[] getMethodArgumentValues(NativeWebRequest request,
@Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// getMethodParameters 返回的是控制器方法的参数列表,
// 如果该参数列表为空,直接返回 EMPTY_ARGS : new Object[0]
if (ObjectUtils.isEmpty(getMethodParameters())) {
return EMPTY_ARGS;
}
// 控制器方法参数列表不为空的情况
MethodParameter[] parameters = getMethodParameters();
// 构建对象数组 args,让其元素个数跟控制器方法参数个数相同,因为这个对象数组
// 是用来保存相应参数的参数值的
// 这个对象数组 args 会被下面的代码逻辑填充,然后被作为实参调用目标控制器方法
Object[] args = new Object[parameters.length];
// 从左至右逐个遍历每个控制器方法参数,从请求上下文中获取相应的参数值放到args相应的位置
for (int i = 0; i < parameters.length; i++) {
// 一次 for 循环完成一个控制器方法参数的参数值解析逻辑
// 获取控制器方法第 i 个参数
MethodParameter parameter = parameters[i];
// 设置方法参数名称发现器,缺省会是 DefaultParameterNameDiscoverer,
// 方法参数名称发现器用于发现该参数的名称,该名称用于从请求上下文中分析该参数的参数值
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
// 首先尝试 从 providedArgs 中获取该参数的参数值,
// Spring MVC 默认情况下 providedArgs 总是为空,所以这里关于应用 providedArgs 的
// 逻辑默认总是空转,不产生实际效果
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
// 如果通过 providedArgs 应用参数值成功则表示该参数参数值解析成功,跳过
// 随后逻辑继续下一个参数的参数值解析逻辑
continue;
}
// this.resolvers 是调用者 RequestMappingHandlerAdapter 设置给当前对象的
// 多个参数解析器的组合对象 HandlerMethodArgumentResolverComposite,
// 检测当前参数是否被 this.resolvers 支持,如果不支持则抛出异常
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter,
"No suitable resolver"));
}
// 当前参数经过检测被 this.resolvers 支持,现在尝试使用 this.resolvers 从请求上下文中分析其值,
// 注意,this.resolvers 检测某个参数是否被支持的逻辑通常仅仅考虑参数的某些特征 : 比如数据类型
// 等等,所以一个参数被支持,并不代表它的值能被正确解析,因为数据的内容或者格式有可能在解析时会
// 遇到问题。
// 下面的 try-catch 就是处理这种参数值解析过程中遇到的异常的。
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 error = ex.getMessage();
if (error != null
&& !error.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, error));
}
}
throw ex;
}
}
// 现在针对每个控制器方法的参数值已经被解析到对象数组中 args 了, 返回该对象数组,
// 下一步就是使用这些实参对象调用目标控制器方法了。
return args;
}
在上面的getMethodArgumentValues
代码中,我们基本上逐行做了注释分析说明,从该方法的逻辑可以看出,获取控制器方法实参的基本逻辑可以总结如下 :
- 控制器方法每个参数的参数值从请求上下文和
providedArgs
中获取; - 获取每个控制器方法参数值首先要获取该参数的名称,这一动作是通过参数名称发现器
this.parameterNameDiscoverer
完成的,缺省情况下,这是一个DefaultParameterNameDiscoverer
对象; - 如果
providedArgs
中存在跟控制器方法某个参数同名的参数值,则该控制器方法参数会采用该参数值; 不过需要注意在RequestMappingHandlerAdapter
中,所提供的providedArgs
总是为空; - 如果没能从
providedArgs
应用某个方法参数值,则会尝试使用参数解析器this.resolvers
从请求上下文中解析该参数的值; - 针对每个控制器方法参数解析得到的参数值保存在数组
args
中作为函数方法返回; - 如果控制器方法没有参数,则返回的
args
是一个长度为0的数组对象,而不是null
;
到此为止,获取请求参数的过程我们就分析完了。不过在该过程中,有一个组件this.resolvers
的很重要,它是从请求中分析参数值的关键组件,概括来讲,它会从请求中解析参数值,进行空值/null
值/缺省值处理,检查参数必要性,以及进行类型转换,但是为了避免影响理解主要过程,本文不对它展开分析(关于该组件的分析会另外行文)。