Spring5源码,@ModelAttribute


一、什么是@ModelAttribute注解

@ModelAttribute注解主要用来将请求转换为使用此注解指定的对象。例如,如果在@ModelAttribute旁边指定了一个Article实例,则与Article的字段对应的所有请求参数将被用作Article的字段值。什么意思呢,例如,POST提交后参数title的值将被设置为Article的title 字段。

http://blog.csdn.net/hejingyuan6/article/details/49995987

因此,此注解允许开发人员通过请求来持久化一个对象。没有它,Spring认为必须创建一个新对象。另外,它直接显示一个对象模型来查看。你不需要在方法中再调用model.setAttribute()。在视图部分,可以通过注解中的指定值查找指定对象(例如,@ModelAttribute(“articleView”)可以在jsp中通过&{articleView}获取相应的值)或对象的类名称(例如@ModelAttribute()Article article将在视图层获取方式就是${article})。


二、@ModelAttribute注解相关代码详解

@ModelAttribute注解的方法是作用于整个Controller的,实际上在执行Controller的每个请求时都会执行@ModelAttribute注解的方法。

执行过程在org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter中查看,每次执行Controller时都会执行@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));
           //执行@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);
            }
            //执行Controller中的方法
            invocableMethod.invokeAndHandle(webRequest, mavContainer);
            if (asyncManager.isConcurrentHandlingStarted()) {
                return null;
            }

            return getModelAndView(mavContainer, modelFactory, webRequest);
        }
        finally {
            webRequest.requestCompleted();
        }
    }

modelFactory.initModel(webRequest, mavContainer, invocableMethod)中会执行@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);
      //执行@ModelAttribute注解的方法
        invokeModelAttributeMethods(request, container);
        ////方法执行结果的值放到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注解,如果是则会执行这个方法,并将返回值放到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;
        }
     //执行被@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);
            }
        }
    }
}

我们进入org.springframework.web.method.support.InvocableHandlerMethod 的invokeForRequest方法,在给定request请求的上下文中解析其参数值后调用该方法,参数值通常通过 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 {
                 //又回归到解析参数的老路上了,就不多解析了
                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

扫描二维码关注公众号,回复: 2780234 查看本文章
/**
 * 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;

http://blog.csdn.net/hejingyuan6/article/details/49995987

当@ModelAttribute注解方法时,这个方法在每次访问Controller时都会被执行,其执行到的原理就是在每次执行Controller时都会判断一次,并执行@ModelAttribute的方法,将执行后的结果值放到container中.

转载:
芋道源码

猜你喜欢

转载自blog.csdn.net/An1090239782/article/details/80912921