• 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

    /**
     * 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中.

    转载:
    芋道源码

  • 相关阅读:
    [五、交互操作]15使用DisclosureGroup视图实现点餐功能
    [五、交互操作]13使用AppStore Overlay向用户推荐其他的应用
    [五、交互操作]11预览视图在正常模式和黑暗模式下的效果
    [五、交互操作]10在预览窗口使用不同的模拟器预览用户界面
    [五、交互操作]8借助sizeCategory预览不同字体下的文本视图
    [五、交互操作]14使用fileExporter将文档导出到iCloud
    int(1) 和 int(10) 有什么区别?资深开发竟然都理解错了!
    Spring Boot 3.0 M1 发布,正式弃用 Java 8,最低要求 Java 17。。。
    发了 20w 年终奖,太激动了。。。
    头条面试官:如何设计群聊消息的已读未读功能?懵了。。
  • 原文地址:https://www.cnblogs.com/aixing/p/13327531.html
Copyright © 2020-2023  润新知