在做Spring MVC時,我們只需用@Controllor來標記Controllor的bean,再用@RequestMapping("標記")來標記需要接受請求的方法,方法中第一個參數為HttpServletRequest類型,最后一個參數為Model類型,中間可以為任何POJO,只要符合標准,有set和get,Spring即可以根據網頁請求中的參數名,自動綁定到POJO對象的屬性名,這是相當方便的。其中的原理是什么呢?看下源代碼就可以知道了。
首先,要知道Method對象的invoke(調用,借助)方法,看下這一段代碼:
Class clazz = Class.forName("TaskProvidePropsList");//這里的類的全名 Object obj = clazz.newInstance();//獲取類的實例 Field[] fields = clazz.getDeclaredFields();//獲取屬性列表 //寫數據 for(Field f : fields) { PropertyDescriptor pd = new PropertyDescriptor(f.getName(), clazz);//獲取屬性對象,要標明是哪個類 Method wM = pd.getWriteMethod();//獲得寫方法,即setter obj = wM.invoke(obj, 2);//執行obj對象的wm方法,2為參數。因為知道是int類型的屬性,所以傳個int過去就是了。。實際情況中需要判斷下他的參數類型,這里可以有返回值,為obj對象的引用 } //讀數據 for(Field f : fields) { PropertyDescriptor pd = new PropertyDescriptor(f.getName(), clazz);//獲取屬性對象,要標明是哪個類 Method rM = pd.getReadMethod();//獲得讀方法,即getter Integer num = (Integer) rM.invoke(obj);//執行obj對象的rm方法。因為知道是int類型的屬性,所以轉換成integer就是了。。也可以不轉換直接打印 System.out.println(num); }
用法還是比較簡單的。
接下來就是Spring中是怎么調用這個方法為POJO賦值的。
最先要追溯到 org.springframework.web.servlet.FrameworkServlet類中的doGet(HttpServletRequest request, HttpServletResponse response)等do方法(do方法是servlet中的基本方法,相當於Spring接管了所有do方法,然后去分發方法。若不使用框架,則需要自己去寫servlet繼承HttpServlet實現所有do方法,也是可以使用的,但是服務器較大時這樣就太復雜了,框架就是為了這個而存在的)
這個方法中調用processRequest(request, response),processRequest中調用doService(request, response),doService是所有do方法的核心,負責把該請求根據情況分發給不同的service(@RequestMapping注解標明的方法)。
doService在FrameworkServlet的子類DispatcherServlet(Dispatch:派遣,調度)中實現,doService中又調用doDispatch(request, response),把當前請求分配給合適的Service的合適方法。
doDispatch方法中調用(HandlerAdapter)ha.handle(processedRequest, response, mappedHandler.getHandler())方法去尋找與該請求掛鈎的方法(processedRequest是由checkMultipart(request)轉換而來的)(注釋中寫的Actually invoke the handler.)。
接着HandlerAdapter中的handle方法調用handleInternal(request, response, (HandlerMethod) handler)返回一個ModelAndView。
handleInternal方法中調用invokeHandleMethod(request, response, handlerMethod)方法返回一個ModelAndView。
然后invokeHandleMethod中調用ServletInvocableHandlerMethod類的invokeAndHandle(webRequest, mavContainer)方法。
該方法中第一句為:Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);這里終於到核心了,為請求調用綁定類。
webRequest為httpRequest包裝后的類,添加了uri成員,標記請求uri以便方便分配。mavContainer為ModelAndViewContainer類對象,用於綁定模塊(POJO)和視圖(view即網頁)。
在invokeForRequest方法中,第一句為Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);該方法返回與該uri請求匹配的Mapping方法的參數列表。
getMethodArgumentValues中第一句為MethodParameter[] parameters = getMethodParameters();獲取方法列表,經過處理后,返回方法中所有參數類型的Bean的一個實例並賦值,為args數組。
關鍵點來的:上一步中有對實例賦值的過程,這個值是從哪里來的呢?沒錯就是從Request的參數列表中來的,綁定數據就是在getMethodArgumentValues方法中做的。具體是怎么做的呢?
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters();//獲取所有參數所屬的類 Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); GenericTypeResolver.resolveParameterType(parameter, getBean().getClass()); args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } if (this.argumentResolvers.supportsParameter(parameter)) { try { args[i] = this.argumentResolvers.resolveArgument( parameter, mavContainer, request, this.dataBinderFactory); continue; } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(getArgumentResolutionErrorMessage("Error resolving argument", i), ex); } throw ex; } } if (args[i] == null) { String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i); throw new IllegalStateException(msg); } } return args; } private String getArgumentResolutionErrorMessage(String message, int index) { MethodParameter param = getMethodParameters()[index]; message += " [" + index + "] [type=" + param.getParameterType().getName() + "]"; return getDetailedErrorMessage(message); }
就是這一句:args[i] = resolveProvidedArgument(parameter, providedArgs);和args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);(dataBinderFactory為數據綁定工廠方法)
主要起作用的是后一句,HandlerMethodArgumentResolverComposite.resolveArgument中調用org.springframework.web.method.support.HandlerMethodArgumentResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
在resolveArgument(多態方法,Request和Response和Model和普通POJO都有重寫方法)中,以POJO重寫方法為例:
public final Object resolveArgument( MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest request, WebDataBinderFactory binderFactory) throws Exception { String name = ModelFactory.getNameForParameter(parameter); Object attribute = (mavContainer.containsAttribute(name)) ? mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request); WebDataBinder binder = binderFactory.createBinder(request, attribute, name); if (binder.getTarget() != null) { bindRequestParameters(binder, request); validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors()) { if (isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } } // Add resolved attribute and BindingResult at the end of the model Map<String, Object> bindingResultModel = binder.getBindingResult().getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return binder.getTarget(); }
通過ModelFactiry獲取Model的類名,之后獲取mavContainer中該屬性名稱的對象,若沒有則調用createAttribute(name, parameter, binderFactory, request)創建,從String value = getRequestValueForAttribute(attributeName, request);Request中獲取這個名稱的值,如果沒有則調用父類的createAttribute,父類的createAttribute返回一個Bean的實體類,所有屬性為空。
之后binderFactory.createBinder(request, attribute, name);為實體類創建一個綁定器,綁定器包含實體類,實體類名和http請求。然后初始化綁定器,為綁定器賦Request。
接着調用bindRequestParameters(binder, request);開始為Bean綁定值。bindRequestParameters中獲取servetBinder和servletRequest,之后servletBinder.bind(servletRequest);
bind方法中,MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);獲取請求中所有參數。addBindValues(mpvs, request);doBind(mpvs);
doBind中checkFieldDefaults(mpvs);checkFieldMarkers(mpvs);再調用父類的doBind。checkAllowedFields(mpvs);checkRequiredFields(mpvs);檢查參數
applyPropertyValues(mpvs);方法把所有屬性綁定到bean實體類中。再調用getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());設置值。
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException { List<PropertyAccessException> propertyAccessExceptions = null; List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ? ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues())); for (PropertyValue pv : propertyValues) { try { // This method may throw any BeansException, which won't be caught // here, if there is a critical failure such as no matching field. // We can attempt to deal only with less serious exceptions. setPropertyValue(pv); } catch (NotWritablePropertyException ex) { if (!ignoreUnknown) { throw ex; } // Otherwise, just ignore it and continue... } catch (NullValueInNestedPathException ex) { if (!ignoreInvalid) { throw ex; } // Otherwise, just ignore it and continue... } catch (PropertyAccessException ex) { if (propertyAccessExceptions == null) { propertyAccessExceptions = new LinkedList<PropertyAccessException>(); } propertyAccessExceptions.add(ex); } } // If we encountered individual exceptions, throw the composite exception. if (propertyAccessExceptions != null) { PropertyAccessException[] paeArray = propertyAccessExceptions.toArray(new PropertyAccessException[propertyAccessExceptions.size()]); throw new PropertyBatchUpdateException(paeArray); } }
List<PropertyValue> propertyValues存儲所有PropertyValue,每個PropertyValue中包含屬性名(鍵名)與值(從網頁Request中傳過來的,json方式最好),還有一個屬性的map。之后調用setPropertyValue(pv);
其中nestedBw = getBeanWrapperForPropertyPath(propertyName);獲取上面實體類。tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName));使用propertyName存儲所有屬性(去除方括號和大括號),nestedBw.setPropertyValue(tokens, pv);為實體類設置值。最終有BeanWrapperImpl類的setPropertyValue方法,為Request中傳過來的所有參數name值,與RequestMapping中函數傳入參數Bean類型的成員變量名比較,如果有相同的,則調用相應的寫方法setter給該變量設置值(其實還有更復雜的List,Array,Map類型綁定,都在BeanWrapperImpl類中實現,可以詳細看看)。
總的來說:先遍歷所有Mapping中參數的Bean對象,再遍歷Request中所有參數和值對,接着再遍歷Bean對象中所有方法,找到Bean中有set方法的和Request中參數名相同的成員變量,調用set方法設置該成員變量值。