SpringMVC中處理請求的方法叫做HandlerMethod
但是現在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 SynthesizingMethodParameter(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();
Annotation[] var21 = paramAnns;
int var22 = paramAnns.length;
for(int var23 = 0; var23 < var22; ++var23) {
Annotation paramAnn = var21[var23];
if (RequestParam.class.isInstance(paramAnn)) {
RequestParam requestParam = (RequestParam)paramAnn;
paramName = requestParam.name();
required = requestParam.required();
defaultValue = this.parseDefaultValueAttribute(requestParam.defaultValue());
++annotationsFound;
} else if (RequestHeader.class.isInstance(paramAnn)) {
RequestHeader requestHeader = (RequestHeader)paramAnn;
headerName = requestHeader.name();
required = requestHeader.required();
defaultValue = this.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.name();
required = cookieValue.required();
defaultValue = this.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 {
Validated validatedAnn = (Validated)AnnotationUtils.getAnnotation(paramAnn, Validated.class);
if (validatedAnn != null || paramAnn.annotationType().getSimpleName().startsWith("Valid")) {
validate = true;
Object hints = validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(paramAnn);
validationHints = hints instanceof Object[] ? (Object[])((Object[])hints) : new Object[]{hints};
}
}
}
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 = this.resolveCommonArgument(methodParam, webRequest);
if (argValue != WebArgumentResolver.UNRESOLVED) {
args[i] = argValue;
} else if (defaultValue != null) {
args[i] = this.resolveDefaultValue(defaultValue);
} else {
Class<?> paramType = methodParam.getParameterType();
if (!Model.class.isAssignableFrom(paramType) && !Map.class.isAssignableFrom(paramType)) {
if (SessionStatus.class.isAssignableFrom(paramType)) {
args[i] = this.sessionStatus;
} else if (HttpEntity.class.isAssignableFrom(paramType)) {
args[i] = this.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!");
}
if (BeanUtils.isSimpleProperty(paramType)) {
paramName = "";
} else {
attrName = "";
}
}
} else {
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;
}
}
}
if (paramName != null) {
args[i] = this.resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler);
} else if (headerName != null) {
args[i] = this.resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler);
} else if (requestBodyFound) {
args[i] = this.resolveRequestBody(methodParam, webRequest, handler);
} else if (cookieName != null) {
args[i] = this.resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler);
} else if (pathVarName != null) {
args[i] = this.resolvePathVariable(pathVarName, methodParam, webRequest, handler);
} else if (attrName != null) {
WebDataBinder binder = this.resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);
boolean assignBindingResult = args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]);
if (binder.getTarget() != null) {
this.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;
}
最終請求到了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
* SpringMVC 确定目标方法 POJO 類型入參的過程
* 1. 确定一個 key:
* 1). 若目标方法的 POJO 類型的參數木有使用 @ModelAttribute 作為修飾, 則 key 為 POJO 類名第一個字母的小寫
* 2). 若使用了 @ModelAttribute 來修飾, 則 key 為 @ModelAttribute 注解的 value 屬性值.
* 2. 在 implicitModel 中查找 key 對應的對象, 若存在, 則作為入參傳入
* 1). 若在 @ModelAttribute 标記的方法中在 Map 中儲存過, 且 key 和 1 确定的 key 一緻, 則會擷取到.
* 3. 若 implicitModel 中不存在 key 對應的對象, 則檢查目前的 Handler 是否使用 @SessionAttributes 注解修飾,
* 若使用了該注解, 且 @SessionAttributes 注解的 value 屬性值中包含了 key, 則會從 HttpSession 中來擷取 key 所
* 對應的 value 值, 若存在則直接傳入到目标方法的入參中. 若不存在則将抛出異常.
* 4. 若 Handler 沒有辨別 @SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key, 則
* 會通過反射來建立 POJO 類型的參數, 傳入為目标方法的參數
* 5. SpringMVC 會把 key 和 POJO 類型的對象儲存到 implicitModel 中, 進而會儲存到 request 中.
* 運作流程:
* 1. 執行 @ModelAttribute 注解修飾的方法: 從資料庫中取出對象, 把對象放入到了 Map 中. 鍵為: user
* 2. SpringMVC 從 Map 中取出 User 對象, 并把表單的請求參數賦給該 User 對象的對應屬性.
* 3. SpringMVC 把上述對象傳入目标方法的參數.
*
* 注意: 在 @ModelAttribute 修飾的方法中, 放入到 Map 時的鍵需要和目标方法入參類型的第一個字母小寫的字元串一緻!
*
* SpringMVC 确定目标方法 POJO 類型入參的過程
* 1. 确定一個 key:
* 1). 若目标方法的 POJO 類型的參數木有使用 @ModelAttribute 作為修飾, 則 key 為 POJO 類名第一個字母的小寫
* 2). 若使用了 @ModelAttribute 來修飾, 則 key 為 @ModelAttribute 注解的 value 屬性值.
* 2. 在 implicitModel 中查找 key 對應的對象, 若存在, 則作為入參傳入
* 1). 若在 @ModelAttribute 标記的方法中在 Map 中儲存過, 且 key 和 1 确定的 key 一緻, 則會擷取到.
* 3. 若 implicitModel 中不存在 key 對應的對象, 則檢查目前的 Handler 是否使用 @SessionAttributes 注解修飾,
* 若使用了該注解, 且 @SessionAttributes 注解的 value 屬性值中包含了 key, 則會從 HttpSession 中來擷取 key 所
* 對應的 value 值, 若存在則直接傳入到目标方法的入參中. 若不存在則将抛出異常.
* 4. 若 Handler 沒有辨別 @SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key, 則
* 會通過反射來建立 POJO 類型的參數, 傳入為目标方法的參數
* 5. SpringMVC 會把 key 和 POJO 類型的對象儲存到 implicitModel 中, 進而會儲存到 request 中.
*
* 源代碼分析的流程
* 1. 調用 @ModelAttribute 注解修飾的方法. 實際上把 @ModelAttribute 方法中 Map 中的資料放在了 implicitModel 中.
* 2. 解析請求處理器的目标參數, 實際上該目标參數來自于 WebDataBinder 對象的 target 屬性
* 1). 建立 WebDataBinder 對象:
* ①. 确定 objectName 屬性: 若傳入的 attrName 屬性值為 "", 則 objectName 為類名第一個字母小寫.
* *注意: attrName. 若目标方法的 POJO 屬性使用了 @ModelAttribute 來修飾, 則 attrName 值即為 @ModelAttribute
* 的 value 屬性值
*
* ②. 确定 target 屬性:
* > 在 implicitModel 中查找 attrName 對應的屬性值. 若存在, ok
* > *若不存在: 則驗證目前 Handler 是否使用了 @SessionAttributes 進行修飾, 若使用了, 則嘗試從 Session 中
* 擷取 attrName 所對應的屬性值. 若 session 中沒有對應的屬性值, 則抛出了異常.
* > 若 Handler 沒有使用 @SessionAttributes 進行修飾, 或 @SessionAttributes 中沒有使用 value 值指定的 key
* 和 attrName 相比對, 則通過反射建立了 POJO 對象
*
* 2). SpringMVC 把表單的請求參數賦給了 WebDataBinder 的 target 對應的屬性.
* 3). *SpringMVC 會把 WebDataBinder 的 attrName 和 target 給到 implicitModel.
* 近而傳到 request 域對象中.
* 4). 把 WebDataBinder 的 target 作為參數傳遞給目标方法的入參.