Spring源碼研究:數據綁定


在做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方法設置該成員變量值。

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM