[Re] SpringMVC-2
阿新 • • 發佈:2020-09-15
資料輸出
Spring MVC 提供了以下幾種途徑輸出模型資料:
Map&Model
-
Spring MVC 在內部使用了一個 org.springframework.ui.Model 介面儲存模型資料。
-
Spring MVC 在呼叫方法前會建立一個隱含的模型物件(BindingAwareModelMap) 作為模型資料的儲存容器。如果方法形參為
org.springframework.ui.Model
、org.springframework.ui.ModelMap
或java.util.Map
型別,Spring MVC 會將隱含模型的引用傳遞給這些形參。在方法體內,開發者可以通過這個形參物件訪問到模型中的所有資料,也可以向模型中新增新的屬性資料。 -
底層其實都是 BindingAwareModelMap 在工作,而存在此物件中的資料最後都會被放在請求域中。
ModelAndView
控制器處理方法的返回值如果為 ModelAndView, 則其既包含檢視資訊,也包含模型資料資訊。而且資料是放在請求域中。
- 新增模型資料
MoelAndView addObject(String attributeName, Object attributeValue) ModelAndView addAllObject(Map<String, ?> modelMap)
- 設定檢視
void setView(View view) void setViewName(String viewName)
@SessionAttributes
- 若希望在多個請求之間共用某個模型屬性資料,則可以在 控制器類(只能標記在類上) 上標註一個 @SessionAttributes,Spring MVC 將在模型中對應的屬性暫存到 HttpSession 中。
- @SessionAttributes 除了可以通過屬性名指定需要放到會話中的屬性外,還可以通過模型屬性的物件型別指定哪些模型屬性需要放到會話中。
@SessionAttributes(types=User.class) 會將隱含模型中所有型別為 User.class 的屬性新增到會話中 @SessionAttributes(value={"user1", "user2"}) 會將隱含模型中屬性名為 user1 或 user2 的屬性新增到會話中 @SessionAttributes(types={User.class, Dept.class}) @SessionAttributes(value={"user1", "user2"}, types={Dept.class})
- 可能會引發異常:如果在處理類定義處標註了 @SessionAttributes("xxx"),則嘗試從會話中獲取該屬性,並將其賦給該形參,然後再用請求訊息填充該形參物件。如果在會話中找不到對應的屬性,則丟擲 HttpSessionRequiredException 異常。所以,還是使用原生 API 來存。
測試上述功能
@SessionAttributes(value= {"msg", "attr"}, types=String.class)
@Controller
public class OutputController {
@RequestMapping("/handle01")
public String handle01(Map<String, Object> map) {
// Map 型別:class org.springframework.validation.support.BindingAwareModelMap
System.out.println("Map 型別:" + map.getClass());
map.put("msg", "[Map] Can you hear me?");
map.put("attr", "val1");
return "success";
}
@RequestMapping("/handle02")
public String handle02(Model model) {
// Model 型別:class org.springframework.validation.support.BindingAwareModelMap
System.out.println("Model 型別:" + model.getClass());
model.addAttribute("msg", "[Model] Can you hear me?");
model.addAttribute("attr", "val2");
return "success";
}
@RequestMapping("/handle03")
public String handle03(ModelMap modelMap) {
// ModelMap 型別:class org.springframework.validation.support.BindingAwareModelMap
System.out.println("ModelMap 型別:" + modelMap.getClass());
modelMap.addAttribute("msg", "[ModelMap] Can you hear me?");
return "success";
}
@RequestMapping("/handle04")
public ModelAndView handle04() {
ModelAndView mav = new ModelAndView();
mav.addObject("msg", "[ModelAndView] Can you hear me?");
mav.setViewName("success");
return mav;
}
}
@ModelAttribute
使用情景
- 當修改 Book 物件時,SpringMVC 提供的要封裝請求引數的 Book 物件不應該是自己 new 出來的,而應該是從 DB 中取出來的物件。
- 用這個準備好的物件封裝請求引數,實現對這個物件的部分屬性覆蓋。
使用註解
- @ModelAttribute 註解可加在方法和引數上
- 在方法定義上使用該註解:Spring MVC 在呼叫目標處理方法前,會先逐個呼叫在方法級上標註了 @ModelAttribute 的方法
- 在方法的形參前使用該註解:可以從隱含的模型資料中獲取物件,再將請求引數繫結到物件中,再傳入形參
- 應用於使用場景
- 將註解加在方法上,可以在提前執行的方法中去 DB 查 Book 的資訊, 將這個 Book 資訊儲存起來(方便下一個方法還能接著使用)
- 提前方法的形參處宣告一個 Map/Model/ModelMap,在其中存放 Book
- 在真正的處理方法處的 Book 形參上,也加上該註解。待該方法執行時,又會自動將之前查處的 Book 資訊直接放入該形參
@RequestMapping("/updateBook")
public String updateBook(@RequestParam(value="author")String author, HttpServletRequest
request, Map<String, Object> model, @ModelAttribute("haha")Book book){
o2 = model;
b2 = book;
Object haha = model.get("haha");
// System.out.println("傳入的Model:" + model.getClass()); // BindingAwareModelMap
System.out.println("o1==o2?" + (o1 == o2)); // true
System.out.println("b1==b2?" + (b1 == b2)+"-->" + (b2 == haha)); // true-->true
System.out.println("頁面要提交過來的圖書資訊:" + book);
return "success";
}
@ModelAttribute
public void myModelAttribute(Map<String, Object> map){
Book book = new Book(1101, "狐狸在說什麼", "韓", 98, 10, 98.98);
System.out.println("資料庫中查到的圖書資訊是:" + book);
map.put("haha", book);
b1 = book;
o1 = map;
System.out.println("Map的型別:" + map.getClass()); // BindingAwareModelMap
}
原始碼
doDispatcher 原始碼
- 介面卡執行目標方法:L54 (原始碼對應 L945)
- 請求轉發到對應檢視:L71 (原始碼對應 L959)
protected void doDispatch(HttpServletRequest request
, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 檢查是否是檔案上傳請求
processedRequest = checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// 根據當前的請求 URI 找到目標處理器,拿到執行鏈
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response); // 找不到:404/throw
return;
}
// 拿到能執行目標處理器(類)所有方法的 [介面卡]
// 介面卡: 反射工具, 型別為 AnnotationMethodHandlerAdapter
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request
, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
logger.debug("Last-Modified value for ["
+ requestUri + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response)
.checkNotModified(lastModified) && isGet) {
return;
}
}
// 執行所有攔截器的 preHandle()
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// Actually invoke the handler. [介面卡] 執行目標方法,會將目標方法的返回值
// 作為檢視名儲存到 ModelAndView 物件的 view 屬性中。注意:目標方法無論怎麼寫
// [介面卡] 執行完成後都會將有關資訊(請求域屬性/檢視)封裝到 ModelAndView 中
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
// 如果目標方法返回 void,給其設定一個預設檢視名
applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
// 轉發到目標檢視 (根據 ModelAndView 封裝的 View 資訊將請求轉發
// 到對應頁面,還可以從請求域中獲取其中封裝的 ModelMap 資料)
processDispatchResult(processedRequest, response
, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
return;
}
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
[小結] 請求過來,DispatcherServlet 收到請求,呼叫 doDispatcher() 進行處理:
getHandler()
根據當前請求地址找到能處理這個請求的類(處理器)
根據當前請求,在 HandlerMapping 中找到這個請求的對映資訊,獲取對應的目標處理器類。getHandlerAdapter()
根據當前處理器類獲取到能執行這個處理器方法的介面卡 ha
根據當前處理器類,找到 support 該處理器類的 HandlerAdapter(介面卡)ha.handle()
使用介面卡(AnnotationMethodHandlerAdapter) 執行目標方法,會返回一個 ModelAndView 物件processDispatchResult()
根據 ModelAndView 的資訊轉發到具體的檢視,並可以在請求域中取出對應的模型資料
getHandler 細節
返回的是目標處理器的執行鏈:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler map [" + hm
+ "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
HandlerMapping 處理器對映;裡面儲存了每一個處理器能處理哪些請求
getHandlerAdapter 細節
如何找到目標處理器類的介面卡(要拿介面卡去執行目標方法)。
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler
+ "]: The DispatcherServlet configuration needs to include"
+ "a HandlerAdapter that supports this handler");
}
SpringMVC 九大元件
SpringMVC 在工作的時候,關鍵位置都是由如下屬性(元件) 來完成的,故稱為 "SpringMVC的 9 大元件" (共同點:全是介面 → 介面就是規範,提供了擴充套件性)。
/** 檔案上傳解析器 */
private MultipartResolver multipartResolver;
/** 區域資訊解析器,和國際化有關 */
private LocaleResolver localeResolver;
/** 主題解析器,支援主題效果更換 */
private ThemeResolver themeResolver;
/** Handler 對映資訊 */
private List<HandlerMapping> handlerMappings;
/** Handler 介面卡 */
private List<HandlerAdapter> handlerAdapters;
/** SpringMVC 異常解析器 */
private List<HandlerExceptionResolver> handlerExceptionResolvers;
/** 檢視名轉換,當處理器方法返回void,該解析器將檢視名設定為請求URI */
private RequestToViewNameTranslator viewNameTranslator;
/** (FlashMap + Manager) SpringMVC 中允許重定向攜帶資料(放Session域)的功能 */
private FlashMapManager flashMapManager;
/** 檢視解析器 */
private List<ViewResolver> viewResolvers;
handlerMappings 和 handlerAdapters 是什麼時候有值的?
以初始化 HandlerMappings 為例:
handle 細節
要執行的目標方法
@RequestMapping("/hello")
public String hello(@RequestParam("bookName")String bookName
, Map<String, Object> map, HttpSession session
, @ModelAttribute("book") Book book) {
System.out.println("Hello~");
return "success";
}
@ModelAttribute
public void myModelAttribute(Map<String, Object> map){
Book book = new Book(1101, "狐狸在說什麼", "韓", 98, 10, 199.9);
System.out.println("資料庫中查到的圖書資訊是:" + book);
map.put("book", book);
System.out.println("modelAttribute方法查詢圖書並儲存到Map中:" + map.getClass());
}
AnnotationMethodHandlerAdapter
@Override
public ModelAndView handle(HttpServletRequest request
, HttpServletResponse response, Object handler) throws Exception {
// ...
return invokeHandlerMethod(request, response, handler);
}
protected ModelAndView invokeHandlerMethod(HttpServletRequest request
, HttpServletResponse response, Object handler) throws Exception {
// 拿到 [方法解析器]
ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
// [方法解析器] 根據當前請求解析得到當前請求的目標處理方法
Method handlerMethod = methodResolver.resolveHandlerMethod(request);
// 通過 [方法解析器] 來建立 [方法執行器]
ServletHandlerMethodInvoker methodInvoker
= new ServletHandlerMethodInvoker(methodResolver);
// 包裝原生 request 和 response
ServletWebRequest webRequest = new ServletWebRequest(request, response);
// 建立本次請求的隱含模型!
ExtendedModelMap implicitModel = new BindingAwareModelMap();
// 真正執行目標方法:目標方法利用反射執行期間確定引數值,提前執行
// @ModelAttribute 標註的方法等所有操作都在其中,詳見 2.5.3
Object result = methodInvoker.invokeHandlerMethod(handlerMethod
, handler, webRequest, implicitModel);
ModelAndView mav = methodInvoker.getModelAndView(handlerMethod
, handler.getClass(), result, implicitModel, webRequest);
methodInvoker.updateModelAttributes(handler
, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
return mav;
}
// 該方法被下面 HandlerMethodInvoker 在解析普通(沒加註解的)引數時呼叫
@Override
protected Object resolveStandardArgument(Class<?> parameterType
, NativeWebRequest webRequest) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
if (ServletRequest.class.isAssignableFrom(parameterType) ||
MultipartRequest.class.isAssignableFrom(parameterType)) {
Object nativeRequest = webRequest.getNativeRequest(parameterType);
if (nativeRequest == null) {
throw new IllegalStateException("Current request is not of type ["
+ parameterType.getName() + "]: " + request);
}
return nativeRequest;
}
else if (ServletResponse.class.isAssignableFrom(parameterType)) {
this.responseArgumentUsed = true;
Object nativeResponse = webRequest.getNativeResponse(parameterType);
if (nativeResponse == null) {
throw new IllegalStateException("Current response is not of type ["
+ parameterType.getName() + "]: " + response);
}
return nativeResponse;
}
else if (HttpSession.class.isAssignableFrom(parameterType)) {
return request.getSession();
}
else if (Principal.class.isAssignableFrom(parameterType)) {
return request.getUserPrincipal();
}
else if (Locale.class.equals(parameterType)) {
return RequestContextUtils.getLocale(request);
}
else if (InputStream.class.isAssignableFrom(parameterType)) {
return request.getInputStream();
}
else if (Reader.class.isAssignableFrom(parameterType)) {
return request.getReader();
}
else if (OutputStream.class.isAssignableFrom(parameterType)) {
this.responseArgumentUsed = true;
return response.getOutputStream();
}
else if (Writer.class.isAssignableFrom(parameterType)) {
this.responseArgumentUsed = true;
return response.getWriter();
}
return super.resolveStandardArgument(parameterType, webRequest);
}
HandlerMethodInvoker
public final Object invokeHandlerMethod(Method handlerMethod, Object handler,
NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {
Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod);
boolean debug = logger.isDebugEnabled();
for (String attrName : this.methodResolver.getActualSessionAttributeNames()) {
Object attrValue = this.sessionAttributeStore
.retrieveAttribute(webRequest, attrName);
if (attrValue != null) {
implicitModel.addAttribute(attrName, attrValue);
}
}
// 找到所有 @ModelAttribute 註解標註的方法
for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) {
Method attributeMethodToInvoke = BridgeMethodResolver
.findBridgedMethod(attributeMethod);
// 來解析 @ModelAttribute 方法執行所需要的每一個引數的值(該方法還傳入了隱含模型)
Object[] args = resolveHandlerArguments(attributeMethodToInvoke
, handler, webRequest, implicitModel);
if (debug) {
logger.debug("Invoking model attribute method: " + attributeMethodToInvoke);
}
// 將方法上標註的 @ModelAttribute 的 value值取出來賦給 attrName,沒設就給個空串""
String attrName = AnnotationUtils.findAnnotation(
attributeMethod, ModelAttribute.class).value();
if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) {
continue;
}
ReflectionUtils.makeAccessible(attributeMethodToInvoke);
// 提前反射執行帶有 @ModelAttribute 方法,確保在目標方法執行前先執行。
Object attrValue = attributeMethodToInvoke.invoke(handler, args);
// 方法上標註的 @ModelAttribute 註解如果有 value 值,attrName = value的值。
// 如果沒設定該屬性(前面給了空串),attrName 就會變為返回值型別(resolvedType)
// 首字母小寫,比如 void、book
if ("".equals(attrName)) {
Class<?> resolvedType = GenericTypeResolver.resolveReturnType(
attributeMethodToInvoke, handler.getClass());
attrName = Conventions.getVariableNameForReturnType(
attributeMethodToInvoke, resolvedType, attrValue);
}
// 把提前執行的 @ModelAttribute 方法的返回值也放入隱含模型中!這是該註解標在方法上的
// 另一個作用:註解的 value 屬性值為 key,以方法執行後的返回值為 value,放入到隱含
// 模型中,如 void=null。
if (!implicitModel.containsAttribute(attrName)) {
implicitModel.addAttribute(attrName, attrValue);
}
}
// 來解析目標方法執行所需要的每一個引數的值(該方法還傳入了隱含模型)
Object[] args = resolveHandlerArguments(handlerMethodToInvoke
, handler, webRequest, implicitModel);
if (debug) {
logger.debug("Invoking request handler method: " + handlerMethodToInvoke);
}
ReflectionUtils.makeAccessible(handlerMethodToInvoke);
// 這裡才是真正目標方法執行!
return handlerMethodToInvoke.invoke(handler, args);
}
// 如下方法,就確定目標方法執行時使用的每一個引數的值
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();
// 找到目標方法的 i 位置處引數的所有註解,如果有註解就解析並儲存註解的資訊
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(...);
}
// 沒找到註解的情況
if (annotationsFound == 0) {
// 解析普通引數,底層實際呼叫 resolveStandardArgument(paramType
// , webRequest) 就是確定當前引數是否是 ServletAPI,詳見 #2.5.2
Object argValue = resolveCommonArgument(methodParam, webRequest);
// Object UNRESOLVED = new Object();
if (argValue != WebArgumentResolver.UNRESOLVED) {
args[i] = argValue;
}
else if (defaultValue != null) {
args[i] = resolveDefaultValue(defaultValue);
}
else {
Class<?> paramType = methodParam.getParameterType();
// 判斷引數型別是否是 Model / Map 旗下的型別
if (Model.class.isAssignableFrom(paramType)
|| Map.class.isAssignableFrom(paramType)) {
if (!paramType.isAssignableFrom(implicitModel.getClass())) {
throw new IllegalStateException(...);
}
// 如果是,將隱含模型物件的引用賦值給該引數
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(...);
}
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) {
// 將請求引數中提交的每一個屬性和JavaBean進行繫結
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;
}
// 確定自定義型別引數的值
private WebDataBinder resolveModelAttribute(String attrName, MethodParameter
methodParam, ExtendedModelMap implicitModel, NativeWebRequest
webRequest, Object handler) throws Exception {
// Bind request parameter onto object...
String name = attrName;
// 如果 attrName 是空串,就將引數型別的首字母小寫作為值
if ("".equals(name)) {
name = Conventions.getVariableNameForParameter(methodParam);
}
Class<?> paramType = methodParam.getParameterType();
// SpringMVC 確定 POJO 的 3 步
Object bindObject;
// 1) 如果隱含模型中有這個 key (標了 @ModelAttribute 就是註解
// 指定的 value,沒標就是引數型別首字母小寫)
if (implicitModel.containsKey(name)) {
bindObject = implicitModel.get(name);
}
// 2) 如果是 @SessionAttributes 標註的屬性,就從 session 中拿
else if (this.methodResolver.isSessionAttribute(name, paramType)) {
bindObject = this.sessionAttributeStore.retrieveAttribute(webRequest, name);
if (bindObject == null) {
raiseSessionRequiredException("Session attribute '" + name
+ "' required - not found in session");
}
}
// 3) 如果都不是,就利用反射建立物件
else {
bindObject = BeanUtils.instantiateClass(paramType);
}
WebDataBinder binder = createBinder(webRequest, bindObject, name);
initBinder(handler, name, binder, webRequest);
return binder;
}