SpringMVC中HandlerMethod的请求参数解析过程
SpringMVC中处理请求的方法叫做HandlerMethod,HandlerMethod可以通过多种方式声明它的参数来源,同时对应着不同的解析过程。那么,今天我们就来详细的研究一下SpringMVC请求参数的解析过程。
直接定位到参数解析的方法,下面列出了HandlerMethodInvoker的resolveHandlerArguments方法。这个方法封装了HandlerMethod参数的解析全过程,所以它很长,但是非常清晰,下面我们逐步解析。
private Object[] resolveHandlerArguments(Method handlerMethod, Object handler,
NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {
Class[] paramTypes = handlerMethod.getParameterTypes();
Object[] args = new Object[paramTypes.length];
for (int i = 0; i < args.length; i++) {
MethodParameter methodParam = new MethodParameter(handlerMethod, i);
methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);
GenericTypeResolver.resolveParameterType(methodParam, handler.getClass());
String paramName = null;
String headerName = null;
boolean requestBodyFound = false;
String cookieName = null;
String pathVarName = null;
String attrName = null;
boolean required = false;
String defaultValue = null;
boolean validate = false;
Object[] validationHints = null;
int annotationsFound = 0;
Annotation[] paramAnns = methodParam.getParameterAnnotations();
for (Annotation paramAnn : paramAnns) {
if (RequestParam.class.isInstance(paramAnn)) {
RequestParam requestParam = (RequestParam) paramAnn;
paramName = requestParam.value();
required = requestParam.required();
defaultValue = parseDefaultValueAttribute(requestParam.defaultValue());
annotationsFound++;
}
else if (RequestHeader.class.isInstance(paramAnn)) {
RequestHeader requestHeader = (RequestHeader) paramAnn;
headerName = requestHeader.value();
required = requestHeader.required();
defaultValue = parseDefaultValueAttribute(requestHeader.defaultValue());
annotationsFound++;
}
else if (RequestBody.class.isInstance(paramAnn)) {
requestBodyFound = true;
annotationsFound++;
}
else if (CookieValue.class.isInstance(paramAnn)) {
CookieValue cookieValue = (CookieValue) paramAnn;
cookieName = cookieValue.value();
required = cookieValue.required();
defaultValue = parseDefaultValueAttribute(cookieValue.defaultValue());
annotationsFound++;
}
else if (PathVariable.class.isInstance(paramAnn)) {
PathVariable pathVar = (PathVariable) paramAnn;
pathVarName = pathVar.value();
annotationsFound++;
}
else if (ModelAttribute.class.isInstance(paramAnn)) {
ModelAttribute attr = (ModelAttribute) paramAnn;
attrName = attr.value();
annotationsFound++;
}
else if (Value.class.isInstance(paramAnn)) {
defaultValue = ((Value) paramAnn).value();
}
else if (paramAnn.annotationType().getSimpleName().startsWith("Valid")) {
validate = true;
Object value = AnnotationUtils.getValue(paramAnn);
validationHints = (value instanceof Object[] ? (Object[]) value : new Object[] {value});
}
}
if (annotationsFound > 1) {
throw new IllegalStateException("Handler parameter annotations are exclusive choices - " +
"do not specify more than one such annotation on the same parameter: " + handlerMethod);
}
if (annotationsFound == 0) {
Object argValue = resolveCommonArgument(methodParam, webRequest);
if (argValue != WebArgumentResolver.UNRESOLVED) {
args[i] = argValue;
}
else if (defaultValue != null) {
args[i] = resolveDefaultValue(defaultValue);
}
else {
Class<?> paramType = methodParam.getParameterType();
if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) {
if (!paramType.isAssignableFrom(implicitModel.getClass())) {
throw new IllegalStateException("Argument [" + paramType.getSimpleName() + "] is of type " +
"Model or Map but is not assignable from the actual model. You may need to switch " +
"newer MVC infrastructure classes to use this argument.");
}
args[i] = implicitModel;
}
else if (SessionStatus.class.isAssignableFrom(paramType)) {
args[i] = this.sessionStatus;
}
else if (HttpEntity.class.isAssignableFrom(paramType)) {
args[i] = resolveHttpEntityRequest(methodParam, webRequest);
}
else if (Errors.class.isAssignableFrom(paramType)) {
throw new IllegalStateException("Errors/BindingResult argument declared " +
"without preceding model attribute. Check your handler method signature!");
}
else if (BeanUtils.isSimpleProperty(paramType)) {
paramName = "";
}
else {
attrName = "";
}
}
}
if (paramName != null) {
args[i] = resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler);
}
else if (headerName != null) {
args[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler);
}
else if (requestBodyFound) {
args[i] = resolveRequestBody(methodParam, webRequest, handler);
}
else if (cookieName != null) {
args[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler);
}
else if (pathVarName != null) {
args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler);
}
else if (attrName != null) {
WebDataBinder binder =
resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);
boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));
if (binder.getTarget() != null) {
doBind(binder, webRequest, validate, validationHints, !assignBindingResult);
}
args[i] = binder.getTarget();
if (assignBindingResult) {
args[i + 1] = binder.getBindingResult();
i++;
}
implicitModel.putAll(binder.getBindingResult().getModel());
}
}
return args;
}
这个方法的调用关系如下图所示,
看了上面的图片,可以更清晰的了解到这个方法是在请求分发时,找到了我们最常用的AnnotationMethodHandlerAdapter,最终请求到了HandlerMethodInvoker的invokeHandlerMethod方法,而在调用HandlerMethod之前需要解析出方法需要的所有参数。
一,有标注的参数的解析
Spring解析参数的第一步就是尝试根据参数使用的标注解析出参数的值。下面分别说明每种标注的参数解析过程
1,RequestParam
RequestParam是最常见的参数来源标注,它的意思是这个参数为浏览器发送过来的请求参数。对于RequestParam的解析最普遍的过程就是根据参数的名字,在Request中查找相应的Parameter。它的结果有可能是一个字符串或者是字符串数组。如果没有查到则尝试使用默认值,同时若即没有默认值还指定了required属性为true则会抛出异常。有两个特殊情况要优于从Request中直接查找,分别是Map类型的参数和MultiPart类型的Request。如果参数的类型是Map类型并且没有指定参数的名字,则认为这个Map需要所有来自浏览器的请求参数,它的解析过程就是将Request中的所有Parameter放到这个Map中。如果当前的请求是一个多部分请求,并且尝试去寻找同名的文件。
2,RequestHeader和CookieValue
RequestHeader、CookieValue形式的参数解析与RequestParam非常类似。它们都可以指定默认值,也可以指定是否必须。默认值和是否必须使用起来也是相同的方式,没有按header或cookieName找到对应的值,则尝试使用默认值。最终若还是没有解析出对应值,并且通过required指定了此参数必须则抛出异常,否则返回null(默认情况下)。
3,PathVariable
PathVariable标识的参数说明这个参数来自于RequestMapping中声明的URI中的模板变量。RequestMapping注解在 URI 模板变量中支持正则表达式, 语法 :{变量名:正则表达式},如@RequestMapping("index/{id:\\d+}")。
4,RequestBody
顾名思义RequestBody注解说明参数来自于请求体中,具体的转换过程则是在HandlerAdapter 配置的HttpMessageConverters中。一般用到这个这个注解,都需要跟客户端协商好一组协议以约定传输的内容。
5,ModelAttribute
这个注解用起来稍微有些麻烦,需要提前设置变量到ModelAttribute中,然后这里直接根据名字获取之前设置的变量。具体的解析规则会涉及到很多其他部分的知识,希望以后可以专门的研究一下吧。
上面的这些标注只能出现其中一个,如果使用了一个以上的标注则会抛出异常。如果HandlerMethod的参数通过注解的方式以及解析出了相应的值,则会先保存起来待调用方法是使用,否则还将进行进一步的解析过程。剩下的内容还挺多,留到下一篇中吧。