Spring5源碼解析-@ModelAttribute

之前我們討論了Spring中如何通過驗證器來達(dá)到校驗?zāi)康耐芄 F渲杏袔仔形覀兲岬搅薂ModelAttribute注解缺虐。但是割坠,單單理解這個概念還不夠抽诉,總感覺飄如浮萍陨簇。

本文將對@ModelAttribute進(jìn)行解析。將分為兩部分迹淌。首先將介紹此注解的用法河绽。第二部分將通過具體的代碼來分析這個注解和其相應(yīng)的解析器的細(xì)節(jié)己单。

什么是@ModelAttribute注解?

@ModelAttribute注解主要用來將請求轉(zhuǎn)換為使用此注解指定的對象葵姥。例如荷鼠,如果在@ModelAttribute旁邊指定了一個Article實例,則與Article的字段對應(yīng)的所有請求參數(shù)將被用作Article的字段值榔幸。什么意思呢允乐,例如,POST提交后參數(shù)title的值將被設(shè)置為Article的title 字段削咆。這里推薦一篇文章解釋的很清晰:http://blog.csdn.net/hejingyuan6/article/details/49995987**
因此婚脱,此注解允許開發(fā)人員通過請求來持久化一個對象挚赊。沒有它梨睁,Spring認(rèn)為必須創(chuàng)建一個新對象盛嘿。另外,它直接顯示一個對象模型來查看瞻惋。你不需要在方法中再調(diào)用model.setAttribute()厦滤。在視圖部分,可以通過注解中的指定值查找指定對象(例如歼狼,@ModelAttribute(“articleView”)可以在jsp中通過${articleView}獲取相應(yīng)的值)或?qū)ο蟮念惷Q(例如@ModelAttribute()Article article將在視圖層獲取方式就是${article})掏导。


@ModelAttribute注解相關(guān)代碼詳解

還是分兩波來說吧,也參考了不少其他解析的文章羽峰,看了很多相關(guān)評論趟咆,大都覺得各種迷迷糊糊所以就舊版新版都說說咯,反正都是源碼學(xué)習(xí)梅屉,4.3版本之前和之后(4.2之后機制已經(jīng)改了值纱,下面講新版本的時候會看到源碼相關(guān)注釋),4.3版本之后被廢棄掉了坯汤,先談老版本的虐唠。


老版本

總體來看,有三個關(guān)鍵類協(xié)助@ModelAttribute來轉(zhuǎn)換得到我們所需要的預(yù)期對象惰聂。第一個是org.springframework.web.bind.annotation.support.HandlerMethodResolver凿滤。它包含一個Set類型的私有字段,稱為modelAttributeMethods庶近。此字段包含被@ModelAttribute注解了的方法。在init()方法中眷蚓,解析器將所有相關(guān)方法放在此集合中鼻种。

private final Set<Method> modelAttributeMethods = new LinkedHashSet<Method>();
...
  /**
     * Initialize a new HandlerMethodResolver for the specified handler type.
     * @param handlerType the handler class to introspect
     */
    public void init(final Class<?> handlerType) {
        Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>();
        Class<?> specificHandlerType = null;
        if (!Proxy.isProxyClass(handlerType)) {
            handlerTypes.add(handlerType);
            specificHandlerType = handlerType;
        }
        handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces()));
        for (Class<?> currentHandlerType : handlerTypes) {
            final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
            ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
                @Override
                public void doWith(Method method) {
                    Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
                    Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
                    if (isHandlerMethod(specificMethod) &&
                            (bridgedMethod == specificMethod || !isHandlerMethod(bridgedMethod))) {
                        handlerMethods.add(specificMethod);
                    }
                    else if (isInitBinderMethod(specificMethod) &&
                            (bridgedMethod == specificMethod || !isInitBinderMethod(bridgedMethod))) {
                        initBinderMethods.add(specificMethod);
                    }
                  //此處代碼可知
                    else if (isModelAttributeMethod(specificMethod) &&
                            (bridgedMethod == specificMethod || !isModelAttributeMethod(bridgedMethod))) {
                        modelAttributeMethods.add(specificMethod);
                    }
                }
            }, ReflectionUtils.USER_DECLARED_METHODS);
        }
        this.typeLevelMapping = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
        SessionAttributes sessionAttributes = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class);
        this.sessionAttributesFound = (sessionAttributes != null);
        if (this.sessionAttributesFound) {
            this.sessionAttributeNames.addAll(Arrays.asList(sessionAttributes.names()));
            this.sessionAttributeTypes.addAll(Arrays.asList(sessionAttributes.types()));
        }
    }

之后,org.springframework.web.bind.annotation.support.HandlerMethodInvoker就可以開始干活了沙热。在其方法invokeHandlerMethod()中叉钥,它從modelAttributeMethods Set遍歷所有方法罢缸。如果之前model屬性沒有解析,它將通過創(chuàng)建對象來將請求參數(shù)綁定到對象的相應(yīng)字段投队。

for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) {
                Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod);
                Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);
                if (debug) {
                    logger.debug("Invoking model attribute method: " + attributeMethodToInvoke);
                }
                String attrName = AnnotationUtils.findAnnotation(attributeMethod, ModelAttribute.class).value();
                if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) {
                    continue;
                }
                ReflectionUtils.makeAccessible(attributeMethodToInvoke);
                Object attrValue = attributeMethodToInvoke.invoke(handler, args);
                if ("".equals(attrName)) {
                    Class<?> resolvedType = GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass());
                    attrName = Conventions.getVariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue);
                }
                if (!implicitModel.containsAttribute(attrName)) {
                    implicitModel.addAttribute(attrName, attrValue);
                }
            }

通過org.springframework.web.method.annotation.ModelAttributeMethodProcessor來做綁定枫疆。更確切地說,它是通過方法protected void bindRequestParameters(WebDataBinder binder敷鸦,NativeWebRequest request)來將請求綁定到目標(biāo)對象息楔。而更準(zhǔn)確地說,它使用WebRequestDataBinder的bind()方法來做到這一點扒披。

/**
     * Extension point to bind the request to the target object.
     * @param binder the data binder instance to use for the binding
     * @param request the current request
     */
    protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
        ((WebRequestDataBinder) binder).bind(request);
    }
    ...

    /**
     * 此處會在新版本的最后提到值依,到時可從此開始看的
     * Resolve the argument from the model or if not found instantiate it with
     * its default if it is available. The model attribute is then populated
     * with request values via data binding and optionally validated
     * if {@code @java.validation.Valid} is present on the argument.
     * @throws BindException if data binding and validation result in an error
     * and the next method parameter is not of type {@link Errors}.
     * @throws Exception if WebDataBinder initialization fails.
     */
    @Override
    public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        String name = ModelFactory.getNameForParameter(parameter);
        Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) :
                createAttribute(name, parameter, binderFactory, webRequest));
        if (!mavContainer.isBindingDisabled(name)) {
            ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
            if (ann != null && !ann.binding()) {
                mavContainer.setBindingDisabled(name);
            }
        }
        //此處來做綁定
        WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
        if (binder.getTarget() != null) {
            if (!mavContainer.isBindingDisabled(name)) {
                bindRequestParameters(binder, webRequest);
            }
            validateIfApplicable(binder, parameter);
            if (binder.getBindingResult().hasErrors() && 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.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
    }

WebRequestDataBinder的bind()

/**
     * Bind the parameters of the given request to this binder's target,
     * also binding multipart files in case of a multipart request.
     * <p>This call can create field errors, representing basic binding
     * errors like a required field (code "required"), or type mismatch
     * between value and bean property (code "typeMismatch").
     * <p>Multipart files are bound via their parameter name, just like normal
     * HTTP parameters: i.e. "uploadedFile" to an "uploadedFile" bean property,
     * invoking a "setUploadedFile" setter method.
     * <p>The type of the target property for a multipart file can be Part, MultipartFile,
     * byte[], or String. The latter two receive the contents of the uploaded file;
     * all metadata like original file name, content type, etc are lost in those cases.
     * @param request request with parameters to bind (can be multipart)
     * @see org.springframework.web.multipart.MultipartRequest
     * @see org.springframework.web.multipart.MultipartFile
     * @see javax.servlet.http.Part
     * @see #bind(org.springframework.beans.PropertyValues)
     */
    public void bind(WebRequest request) {
        MutablePropertyValues mpvs = new MutablePropertyValues(request.getParameterMap());
        if (isMultipartRequest(request) && request instanceof NativeWebRequest) {
            MultipartRequest multipartRequest = ((NativeWebRequest) request).getNativeRequest(MultipartRequest.class);
            if (multipartRequest != null) {
                bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
            }
            else if (servlet3Parts) {
                HttpServletRequest serlvetRequest = ((NativeWebRequest) request).getNativeRequest(HttpServletRequest.class);
                new Servlet3MultipartHelper(isBindEmptyMultipartFiles()).bindParts(serlvetRequest, mpvs);
            }
        }
        doBind(mpvs);
    }

跟著源碼 再追下去的話,會發(fā)現(xiàn)在其父類DataBinder中:

/**
     * Actual implementation of the binding process, working with the
     * passed-in MutablePropertyValues instance.
     * @param mpvs the property values to bind,
     * as MutablePropertyValues instance
     * @see #checkAllowedFields
     * @see #checkRequiredFields
     * @see #applyPropertyValues
     */
    protected void doBind(MutablePropertyValues mpvs) {
        checkAllowedFields(mpvs);
        checkRequiredFields(mpvs);
        applyPropertyValues(mpvs);
    }

DataBinder的applyPropertyValues方法中來對字段值進(jìn)行設(shè)置:

protected void applyPropertyValues(MutablePropertyValues mpvs) {
    try {
        // Bind request parameters onto target object.
        getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
    }
    catch (PropertyBatchUpdateException ex) {
        // Use bind error processor to create FieldErrors.
        for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
            getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
        }
    }
}

首先碟案,它得到一個org.springframework.beans.AbstractPropertyAccessor類(getPropertyAccessor)的實現(xiàn)愿险。之后,通過具體實現(xiàn)這個抽象方法public void setPropertyValue(String propertyName价说,Object value)將HTTP請求中找到的值放入解析對象中辆亏。此方法由org.springframework.beans包中的BeanWrapperImpl和DirectFieldAccessor類實現(xiàn)。默認(rèn)情況下鳖目,ModelAttributeMethodProcessor使用的類是org.springframework.beans.BeanWrapperImpl扮叨,這是BeanWrapper的默認(rèn)實現(xiàn)。此默認(rèn)實現(xiàn)可以設(shè)置和獲取bean的屬性(類字段)疑苔。它以這種方式實現(xiàn)一個setPropertyValue方法:

public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyAccessor {
  ...
@Override
    public void setPropertyValue(String propertyName, Object value) throws BeansException {
        AbstractNestablePropertyAccessor nestedPa; //此處看下一段代碼一眼便知
        try {
            nestedPa = getPropertyAccessorForPropertyPath(propertyName);
        }
        catch (NotReadablePropertyException ex) {
            throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
                    "Nested property in path '" + propertyName + "' does not exist", ex);
        }
        PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
        nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value));
    }
  ...
}


/**
 * Default {@link BeanWrapper} implementation that should be sufficient
 * for all typical use cases. Caches introspection results for efficiency.
 *
 * <p>Note: Auto-registers default property editors from the
 * {@code org.springframework.beans.propertyeditors} package, which apply
 * in addition to the JDK's standard PropertyEditors. Applications can call
 * the {@link #registerCustomEditor(Class, java.beans.PropertyEditor)} method
 * to register an editor for a particular instance (i.e. they are not shared
 * across the application). See the base class
 * {@link PropertyEditorRegistrySupport} for details.
 *
 * <p><b>NOTE: As of Spring 2.5, this is - for almost all purposes - an
 * internal class.</b> It is just public in order to allow for access from
 * other framework packages. For standard application access purposes, use the
 * {@link PropertyAccessorFactory#forBeanPropertyAccess} factory method instead.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @author Rob Harrop
 * @author Stephane Nicoll
 * @since 15 April 2001
 * @see #registerCustomEditor
 * @see #setPropertyValues
 * @see #setPropertyValue
 * @see #getPropertyValue
 * @see #getPropertyType
 * @see BeanWrapper
 * @see PropertyEditorRegistrySupport
 */
public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements BeanWrapper {

結(jié)果被轉(zhuǎn)移到private void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv)甫匹,這里就不詳細(xì)介紹了。你只需要知道它是一個設(shè)置一個字段的值的方法惦费。set字段可以是一個簡單的類型(String兵迅,int等),也可以是一個集合(List薪贫,Map)恍箭。

以上介紹了在老版本中關(guān)于@ModelAttribute如何在Spring Web應(yīng)用程序解析的。如上所看到的瞧省,代碼執(zhí)行的基本流程以HandlerMethodResolver對象開頭扯夭,并以ModelAttributeMethodProcessor實例解析的可選對象結(jié)束。整個過程基于數(shù)據(jù)綁定鞍匾,在DataBinder子類中實現(xiàn)交洗。他們通過屬性訪問器(默認(rèn)BeanWrapperImpl)從請求中獲取鍵值對并將其放在目標(biāo)對象中。


新版本

通過上面可以看出橡淑,老版本的代碼其實穿梭的蠻復(fù)雜的构拳,這里就通過新版的代碼再來梳理下:

@ModelAttribute注解的方法是作用于整個Controller的,實際上在執(zhí)行Controller的每個請求時都會執(zhí)行@ModelAttribute注解的方法。

執(zhí)行過程在org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter中查看置森,每次執(zhí)行Controller時都會執(zhí)行@ModelAttribute注解的方法:

/**
     * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}
     * if view resolution is required.
     * @since 4.2 可以看到4.2開始啟用了
     * @see #createInvocableHandlerMethod(HandlerMethod)
     */
    @Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        try {
            WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
            ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
            if (this.argumentResolvers != null) {
                invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            }
            if (this.returnValueHandlers != null) {
                invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            }
            invocableMethod.setDataBinderFactory(binderFactory);
            invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
            ModelAndViewContainer mavContainer = new ModelAndViewContainer();
            mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
           //執(zhí)行@ModelAttribute注解的方法  
            modelFactory.initModel(webRequest, mavContainer, invocableMethod);
            mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
            AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
            asyncWebRequest.setTimeout(this.asyncRequestTimeout);
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
            asyncManager.setTaskExecutor(this.taskExecutor);
            asyncManager.setAsyncWebRequest(asyncWebRequest);
            asyncManager.registerCallableInterceptors(this.callableInterceptors);
            asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
            if (asyncManager.hasConcurrentResult()) {
                Object result = asyncManager.getConcurrentResult();
                mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
                asyncManager.clearConcurrentResult();
                if (logger.isDebugEnabled()) {
                    logger.debug("Found concurrent result value [" + result + "]");
                }
                invocableMethod = invocableMethod.wrapConcurrentResult(result);
            }
            //執(zhí)行Controller中的方法  
            invocableMethod.invokeAndHandle(webRequest, mavContainer);
            if (asyncManager.isConcurrentHandlingStarted()) {
                return null;
            }
            return getModelAndView(mavContainer, modelFactory, webRequest);
        }
        finally {
            webRequest.requestCompleted();
        }
    }

modelFactory.initModel(webRequest, mavContainer, invocableMethod)中會執(zhí)行@ModelAttribute注解的方法(org.springframework.web.method.annotation.ModelFactory中可查看):

/**
     * Populate the model in the following order:
     * <ol>
     * <li>Retrieve "known" session attributes listed as {@code @SessionAttributes}.
     * <li>Invoke {@code @ModelAttribute} methods
     * <li>Find {@code @ModelAttribute} method arguments also listed as
     * {@code @SessionAttributes} and ensure they're present in the model raising
     * an exception if necessary.
     * </ol>
     * @param request the current request
     * @param container a container with the model to be initialized
     * @param handlerMethod the method for which the model is initialized
     * @throws Exception may arise from {@code @ModelAttribute} methods
     */
    public void initModel(NativeWebRequest request, ModelAndViewContainer container,
            HandlerMethod handlerMethod) throws Exception {
        Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
        container.mergeAttributes(sessionAttributes);
      //執(zhí)行@ModelAttribute注解的方法  
        invokeModelAttributeMethods(request, container);
        ////方法執(zhí)行結(jié)果的值放到container  
        for (String name : findSessionAttributeArguments(handlerMethod)) {
            if (!container.containsAttribute(name)) {
                Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
                if (value == null) {
                    throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);
                }
                container.addAttribute(name, value);
            }
        }
    }

在private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container)中會判斷方法上是否被@ModelAttribute注解斗埂,如果是則會執(zhí)行這個方法,并將返回值放到container中:

/**
 * Invoke model attribute methods to populate the model.
 * Attributes are added only if not already present in the model.
 */
private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container)
        throws Exception {
    while (!this.modelMethods.isEmpty()) {
        InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod();
         //判斷方法是否被@ModelAttribute注解  
        ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class);
        Assert.state(ann != null, "No ModelAttribute annotation");
        if (container.containsAttribute(ann.name())) {
            if (!ann.binding()) {
                container.setBindingDisabled(ann.name());
            }
            continue;
        }
     //執(zhí)行被@ModelAttribute注解的方法  
        Object returnValue = modelMethod.invokeForRequest(request, container);
        if (!modelMethod.isVoid()){
            String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType());
            if (!ann.binding()) {
                container.setBindingDisabled(returnValueName);
            }
            if (!container.containsAttribute(returnValueName)) {
                container.addAttribute(returnValueName, returnValue);
            }
        }
    }
}

我們進(jìn)入org.springframework.web.method.support.InvocableHandlerMethod 的invokeForRequest方法凫海,在給定request請求的上下文中解析其參數(shù)值后調(diào)用該方法呛凶,參數(shù)值通常通過 HandlerMethodArgumentResolver來解析。

/**
 * Invoke the method after resolving its argument values in the context of the given request.
 * <p>Argument values are commonly resolved through {@link HandlerMethodArgumentResolver}s.
 * The {@code providedArgs} parameter however may supply argument values to be used directly,
 * i.e. without argument resolution. Examples of provided argument values include a
 * {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance.
 * Provided argument values are checked before argument resolvers.
 * @param request the current request
 * @param mavContainer the ModelAndViewContainer for this request
 * @param providedArgs "given" arguments matched by type, not resolved
 * @return the raw value returned by the invoked method
 * @exception Exception raised if no suitable argument resolver can be found,
 * or if the method raised an exception
 */
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
    //看下面的方法
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
        logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
                "' with arguments " + Arrays.toString(args));
    }
    Object returnValue = doInvoke(args);
    if (logger.isTraceEnabled()) {
        logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
                "] returned [" + returnValue + "]");
    }
    return returnValue;
}
/**
 * Get the method argument values for the current request.
 */
private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable 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);
        args[i] = resolveProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }
        if (this.argumentResolvers.supportsParameter(parameter)) {
            try {
                 //又回歸到解析參數(shù)的老路上了行贪,就不多解析了
                args[i] = this.argumentResolvers.resolveArgument(
                        parameter, mavContainer, request, this.dataBinderFactory);
                continue;
            }
            catch (Exception ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
                }
                throw ex;
            }
        }
        if (args[i] == null) {
            throw new IllegalStateException("Could not resolve method parameter at index " +
                    parameter.getParameterIndex() + " in " + parameter.getExecutable().toGenericString() +
                    ": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
        }
    }
    return args;
}

org.springframework.web.method.support.HandlerMethodArgumentResolverComposite

/**
 * Iterate over registered {@link HandlerMethodArgumentResolver}s and invoke the one that supports it.
 * @throws IllegalStateException if no suitable {@link HandlerMethodArgumentResolver} is found.
 */
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    if (resolver == null) {
        throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
    }
     //又回到老版本的resolveArgument路上了
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
/**
 * Find a registered {@link HandlerMethodArgumentResolver} that supports the given method parameter.
 */
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    if (result == null) {
        for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
            if (logger.isTraceEnabled()) {
                logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
                        parameter.getGenericParameterType() + "]");
            }
            if (methodArgumentResolver.supportsParameter(parameter)) {
                result = methodArgumentResolver;
                this.argumentResolverCache.put(parameter, result);
                break;
            }
        }
    }
    return result;
}

回到org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,可以看到:

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
        implements BeanFactoryAware, InitializingBean {
    @Nullable
    private List<HandlerMethodArgumentResolver> customArgumentResolvers;
    @Nullable
    private HandlerMethodArgumentResolverComposite argumentResolvers;
    @Nullable
    private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers;
    @Nullable
    private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;

又回到老版本的resolveArgument這里了,就不往下解釋了
關(guān)于@ModelAttribute的例子請看這篇博客文章的漾稀,自己就不整例子了http://blog.csdn.net/hejingyuan6/article/details/49995987**
總之,通過源碼可以看出,當(dāng)@ModelAttribute注解方法時,這個方法在每次訪問Controller時都會被執(zhí)行,其執(zhí)行到的原理就是在每次執(zhí)行Controller時都會判斷一次,并執(zhí)行@ModelAttribute的方法,將執(zhí)行后的結(jié)果值放到container中,其實我們只需要知道這么多就成了,背后的機制無論新老版本都是解析綁定這4個字瓮顽。

原文:Spring5源碼解析-@ModelAttribute

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末县好,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子暖混,更是在濱河造成了極大的恐慌缕贡,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拣播,死亡現(xiàn)場離奇詭異晾咪,居然都是意外死亡,警方通過查閱死者的電腦和手機贮配,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門谍倦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人泪勒,你說我怎么就攤上這事昼蛀。” “怎么了圆存?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵叼旋,是天一觀的道長。 經(jīng)常有香客問我沦辙,道長夫植,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任油讯,我火速辦了婚禮详民,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘陌兑。我一直安慰自己沈跨,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布兔综。 她就那樣靜靜地躺著谒出,像睡著了一般隅俘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上笤喳,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機與錄音碌宴,去河邊找鬼杀狡。 笑死,一個胖子當(dāng)著我的面吹牛贰镣,可吹牛的內(nèi)容都是我干的呜象。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼碑隆,長吁一口氣:“原來是場噩夢啊……” “哼恭陡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起上煤,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤休玩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后劫狠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拴疤,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年独泞,在試婚紗的時候發(fā)現(xiàn)自己被綠了呐矾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡懦砂,死狀恐怖蜒犯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情荞膘,我是刑警寧澤罚随,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站衫画,受9級特大地震影響毫炉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜削罩,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一瞄勾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧弥激,春花似錦进陡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春糙麦,著一層夾襖步出監(jiān)牢的瞬間辛孵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工赡磅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留魄缚,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓焚廊,卻偏偏與公主長得像冶匹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子咆瘟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理嚼隘,服務(wù)發(fā)現(xiàn),斷路器袒餐,智...
    卡卡羅2017閱讀 134,702評論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,856評論 6 342
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,293評論 25 707
  • 這部分主要是開源Java EE框架方面的內(nèi)容飞蛹,包括Hibernate、MyBatis匿乃、Spring桩皿、Spring ...
    雜貨鋪老板閱讀 1,391評論 0 2
  • spring官方文檔:http://docs.spring.io/spring/docs/current/spri...
    牛馬風(fēng)情閱讀 1,691評論 0 3