{"id":819,"date":"2012-03-11T21:09:25","date_gmt":"2012-03-11T20:09:25","guid":{"rendered":"http:\/\/doanduyhai.wordpress.com\/?p=819"},"modified":"2012-03-11T21:09:25","modified_gmt":"2012-03-11T20:09:25","slug":"spring-mvc-part-ii-requestmapping-internals","status":"publish","type":"post","link":"https:\/\/www.doanduyhai.com\/blog\/?p=819","title":{"rendered":"Spring MVC part II : @RequestMapping internals"},"content":{"rendered":"<p>This post follows upon the <a href=\"http:\/\/doanduyhai.wordpress.com\/2012\/03\/11\/spring-mvc-part-i-request-handling\/\" title=\"http:\/\/doanduyhai.wordpress.com\/2012\/03\/11\/spring-mvc-part-i-request-handling\/\" target=\"_blank\">Spring MVC part I : Request Handling<\/a> topic.<\/p>\n<p>In this post we discuss in details how Spring handles the <strong>@RequestMapping<\/strong> annotation set on handler methods.<\/p>\n<p><!--more--><\/p>\n<h1>I @RequestMapping arguments resolver<\/h1>\n<p>The argument resolver is done in the <em>invokeHandlerMethod()<\/em> method of the class <strong>org.springframework.web.bind.annotation.support.HandlerMethodInvoker<\/strong>.<\/p>\n<h3>A <em>@SessionAttributes<\/em> &amp; <em>@ModelAttribute<\/em> handling<\/h3>\n<pre class=\"brush: java; highlight: [7,13,28,31]; title: ; wrap-lines: false; notranslate\" title=\"\">\npublic final Object invokeHandlerMethod(Method handlerMethod, Object handler,\nNativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {\n\n\tMethod handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod);\n\ttry {\n\t\tboolean debug = logger.isDebugEnabled();\n\t\tfor (String attrName : this.methodResolver.getActualSessionAttributeNames()) {\n\t\t\tObject attrValue = this.sessionAttributeStore.retrieveAttribute(webRequest, attrName);\n\t\t\tif (attrValue != null) {\n\t\t\t\timplicitModel.addAttribute(attrName, attrValue);\n\t\t\t}\n\t\t}\n\t\tfor (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) {\n\t\t\tMethod attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod);\n\t\t\tObject[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);\n\t\t\t...\t\n\t\t\tString attrName = AnnotationUtils.findAnnotation(attributeMethod, ModelAttribute.class).value();\n\t\t\tif (!&amp;quot;&amp;quot;.equals(attrName) &amp;amp;&amp;amp; implicitModel.containsAttribute(attrName)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tReflectionUtils.makeAccessible(attributeMethodToInvoke);\n\t\t\tObject attrValue = attributeMethodToInvoke.invoke(handler, args);\n\t\t\tif (&amp;quot;&amp;quot;.equals(attrName)) {\n\t\t\t\tClass resolvedType = GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass());\n\t\t\t\tattrName = Conventions.getVariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue);\n\t\t\t}\n\t\t\tif (!implicitModel.containsAttribute(attrName)) {\n\t\t\t\timplicitModel.addAttribute(attrName, attrValue);\n\t\t\t}\n\t\t}\n\t\tObject[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel);\n\t\t...\t\n\t\tReflectionUtils.makeAccessible(handlerMethodToInvoke);\n\t\treturn handlerMethodToInvoke.invoke(handler, args);\n\t}\n\t...\n}\n<\/pre>\n<p> The first <strong>for<\/strong> loop at <strong>line 7<\/strong> simply injects into the implicit model HashMap all attributes declared in the <strong>@SessionAttributes<\/strong> annotation. These attributes are retrieved from the HTTP session.<\/p>\n<p> The second <strong>for<\/strong> loop at <strong>line 11<\/strong> executes all methods in the current request handler that are annotated with <strong>@ModelAttribute<\/strong><\/p>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\n...\n@ModelAttribute(&amp;quot;page&amp;quot;)\npublic String getPage()\n{\n\treturn &amp;quot;adminPage&amp;quot;;\n}\n...\n@ModelAttribute(&amp;quot;user&amp;quot;)\npublic String getUser()\n{\n\treturn this.userService.getCurrentLoggedUser();\n}\n...\n<\/pre>\n<p> These methods are supposed to be <strong>called for each request<\/strong> and the returned object is added to the implicit model HashMap (<strong>line 28<\/strong>).<\/p>\n<p> Finally, the method arguments are resolved by calling <em>resolveHandlerArguments()<\/em> at <strong>line 31<\/strong><br \/>\n&nbsp;<\/p>\n<h3>B Argument types resolution<\/h3>\n<pre class=\"brush: java; highlight: [23,24,31,38,42,49,54,62,69,70,73,86,88,91,106,109,112,115,118,122,125]; title: ; wrap-lines: false; notranslate\" title=\"\">\nprivate Object[] resolveHandlerArguments(Method handlerMethod, Object handler,\nNativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {\n\n\tClass[] paramTypes = handlerMethod.getParameterTypes();\n\tObject[] args = new Object[paramTypes.length];\n\n\tfor (int i = 0; i &amp;lt; args.length; i++) {\n\t\tMethodParameter methodParam = new MethodParameter(handlerMethod, i);\n\t\tmethodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);\n\t\tGenericTypeResolver.resolveParameterType(methodParam, handler.getClass());\n\t\tString paramName = null;\n\t\tString headerName = null;\n\t\tboolean requestBodyFound = false;\n\t\tString cookieName = null;\n\t\tString pathVarName = null;\n\t\tString attrName = null;\n\t\tboolean required = false;\n\t\tString defaultValue = null;\n\t\tboolean validate = false;\n\t\tObject[] validationHints = null;\n\t\tint annotationsFound = 0;\n\t\tAnnotation[] paramAnns = methodParam.getParameterAnnotations();\n\t\tfor (Annotation paramAnn : paramAnns) {\n\t\t\tif (RequestParam.class.isInstance(paramAnn)) {\n\t\t\t\tRequestParam requestParam = (RequestParam) paramAnn;\n\t\t\t\tparamName = requestParam.value();\n\t\t\t\trequired = requestParam.required();\n\t\t\t\tdefaultValue = parseDefaultValueAttribute(requestParam.defaultValue());\n\t\t\t\tannotationsFound++;\n\t\t\t}\n\t\t\telse if (RequestHeader.class.isInstance(paramAnn)) {\n\t\t\t\tRequestHeader requestHeader = (RequestHeader) paramAnn;\n\t\t\t\theaderName = requestHeader.value();\n\t\t\t\trequired = requestHeader.required();\n\t\t\t\tdefaultValue = parseDefaultValueAttribute(requestHeader.defaultValue());\n\t\t\t\tannotationsFound++;\n\t\t\t}\n\t\t\telse if (RequestBody.class.isInstance(paramAnn)) {\n\t\t\t\trequestBodyFound = true;\n\t\t\t\tannotationsFound++;\n\t\t\t}\n\t\t\telse if (CookieValue.class.isInstance(paramAnn)) {\n\t\t\t\tCookieValue cookieValue = (CookieValue) paramAnn;\n\t\t\t\tcookieName = cookieValue.value();\n\t\t\t\trequired = cookieValue.required();\n\t\t\t\tdefaultValue = parseDefaultValueAttribute(cookieValue.defaultValue());\n\t\t\t\tannotationsFound++;\n\t\t\t}\n\t\t\telse if (PathVariable.class.isInstance(paramAnn)) {\n\t\t\t\tPathVariable pathVar = (PathVariable) paramAnn;\n\t\t\t\tpathVarName = pathVar.value();\n\t\t\t\tannotationsFound++;\n\t\t\t}\n\t\t\telse if (ModelAttribute.class.isInstance(paramAnn)) {\n\t\t\t\tModelAttribute attr = (ModelAttribute) paramAnn;\n\t\t\t\tattrName = attr.value();\n\t\t\t\tannotationsFound++;\n\t\t\t}\n\t\t\telse if (Value.class.isInstance(paramAnn)) {\n\t\t\t\tdefaultValue = ((Value) paramAnn).value();\n\t\t\t}\n\t\t\telse if (paramAnn.annotationType().getSimpleName().startsWith(&amp;quot;Valid&amp;quot;)) {\n\t\t\t\tvalidate = true;\n\t\t\t\tObject value = AnnotationUtils.getValue(paramAnn);\n\t\t\t\tvalidationHints = (value instanceof Object[] ? (Object[]) value : new Object[] {value});\n\t\t\t}\n\t\t}\n\n\t\tif (annotationsFound &amp;gt; 1) {\n\t\t\tthrow new IllegalStateException(&amp;quot;Handler parameter annotations are exclusive choices - &amp;quot; +&amp;quot;do not specify more than one such annotation on the same parameter: &amp;quot; + handlerMethod);\n\t\t}\n\t\tif (annotationsFound == 0) {\n\t\t\tObject argValue = resolveCommonArgument(methodParam, webRequest);\n\t\t\tif (argValue != WebArgumentResolver.UNRESOLVED) {\n\t\t\t\targs[i] = argValue;\n\t\t\t}\n\t\t\telse if (defaultValue != null) {\n\t\t\t\targs[i] = resolveDefaultValue(defaultValue);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tClass&amp;lt;?&amp;gt; paramType = methodParam.getParameterType();\n\t\t\t\tif (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) {\n\t\t\t\t\tif (!paramType.isAssignableFrom(implicitModel.getClass())) {\n\t\t\t\t\t\tthrow new IllegalStateException(&amp;quot;Argument [&amp;quot; + paramType.getSimpleName() + &amp;quot;] is of type &amp;quot; +\t\t\t\t\t\t\t\t&amp;quot;Model or Map but is not assignable from the actual model. You may need to switch &amp;quot; +\t\t\t&amp;quot;newer MVC infrastructure classes to use this argument.&amp;quot;);\n\t\t\t\t\t}\n\t\t\t\t\targs[i] = implicitModel;\n\t\t\t\t}\n\t\t\t\telse if (SessionStatus.class.isAssignableFrom(paramType)) {\n\t\t\t\t\targs[i] = this.sessionStatus;\n\t\t\t\t}\n\t\t\t\telse if (HttpEntity.class.isAssignableFrom(paramType)) {\n\t\t\t\t\targs[i] = resolveHttpEntityRequest(methodParam, webRequest);\n\t\t\t\t}\n\t\t\t\telse if (Errors.class.isAssignableFrom(paramType)) {\n\t\t\t\t\tthrow new IllegalStateException(&amp;quot;Errors\/BindingResult argument declared &amp;quot; +&amp;quot;without preceding model attribute. Check your handler method signature!&amp;quot;);\n\t\t\t\t}\n\t\t\t\telse if (BeanUtils.isSimpleProperty(paramType)) {\n\t\t\t\t\tparamName = &amp;quot;&amp;quot;;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tattrName = &amp;quot;&amp;quot;;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (paramName != null) {\n\t\t\targs[i] = resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler);\n\t\t}\n\t\telse if (headerName != null) {\n\t\t\targs[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler);\n\t\t}\n\t\telse if (requestBodyFound) {\n\t\t\targs[i] = resolveRequestBody(methodParam, webRequest, handler);\n\t\t}\n\t\telse if (cookieName != null) {\n\t\t\targs[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler);\n\t\t}\n\t\telse if (pathVarName != null) {\n\t\t\targs[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler);\n\t\t}\n\t\telse if (attrName != null) {\n\t\t\tWebDataBinder binder =\n\t\t\t\t\tresolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);\n\t\t\tboolean assignBindingResult = (args.length &amp;gt; i + 1 &amp;amp;&amp;amp; Errors.class.isAssignableFrom(paramTypes[i + 1]));\n\t\t\tif (binder.getTarget() != null) {\n\t\t\t\tdoBind(binder, webRequest, validate, validationHints, !assignBindingResult);\n\t\t\t}\n\t\t\targs[i] = binder.getTarget();\n\t\t\tif (assignBindingResult) {\n\t\t\t\targs[i + 1] = binder.getBindingResult();\n\t\t\t\ti++;\n\t\t\t}\n\t\t\timplicitModel.putAll(binder.getBindingResult().getModel());\n\t\t}\n\t}\n\t\treturn args;\n}\n<\/pre>\n<p> The <strong>for<\/strong> loop at <strong>line 23<\/strong> lists all argument types having supported annotations:<\/p>\n<ul>\n<li><strong>line 24<\/strong>: <strong>@RequestPara<\/strong>m annotated argument<\/li>\n<li><strong>line 31<\/strong>: <strong>@RequestHeader<\/strong> annotated argument<\/li>\n<li><strong>line 38<\/strong>: <strong>@RequestBody<\/strong> annotated argument<\/li>\n<li><strong>line 42<\/strong>: <strong>@CookieValue<\/strong> annotated argument<\/li>\n<li><strong>line 49<\/strong>: <strong>@PathVariable<\/strong> annotated argument<\/li>\n<li><strong>line 54<\/strong>: <strong>@ModelAttribute<\/strong> annotated argument<\/li>\n<li><strong>line 62<\/strong>: <strong>@Valid<\/strong> annotated argument<\/li>\n<\/ul>\n<p> If an argument is annotated more than once, Spring will raise an exception as per <strong>line 70<\/strong><\/p>\n<p>Next, the method <em>resolveCommonArgument()<\/em> is called if the argument has no annotation (<strong>line 73<\/strong>).<\/p>\n<p>If the argument is of type<\/p>\n<ul>\n<li><strong>Model<\/strong> or <strong>Map<\/strong> (java.util.Map), the implicit model HashMap built earlier will be returned (<strong>line 86<\/strong>)<\/li>\n<li><strong>SessionStatus<\/strong>, the current session status object is returned (<strong>line 89<\/strong>)<\/li>\n<li><strong>HttpEntity<\/strong>, the method resolveHttpEntityRequest() is called for resolution (<strong>line 92<\/strong>)<\/li>\n<\/ul>\n<p>At lines <strong>106, 109, 112, 115, 118 &amp; 122<\/strong>, Spring resolves the arguments based on their annotation. Custom data binders, if any, are invoked at <strong>line 125<\/strong>.<\/p>\n<p>Let&#8217;s focus on the <em>resolveModelAttribute()<\/em> method at <strong>line 122<\/strong>. This method resolves all argument annotated by <strong>@ModelAttribute<\/strong><\/p>\n<pre class=\"brush: java; highlight: [12,15,23,24]; title: ; wrap-lines: false; notranslate\" title=\"\">\nprivate WebDataBinder resolveModelAttribute(String attrName, MethodParameter methodParam,\nExtendedModelMap implicitModel, NativeWebRequest webRequest, Object handler) throws Exception {\n\n\t\/\/ Bind request parameter onto object...\n\tString name = attrName;\n\tif (&amp;quot;&amp;quot;.equals(name)) {\n\t\tname = Conventions.getVariableNameForParameter(methodParam);\n\t}\n\tClass&amp;lt;?&amp;gt; paramType = methodParam.getParameterType();\n\tObject bindObject;\n\tif (implicitModel.containsKey(name)) {\n\t\tbindObject = implicitModel.get(name);\n\t}\n\telse if (this.methodResolver.isSessionAttribute(name, paramType)) {\n\t\tbindObject = this.sessionAttributeStore.retrieveAttribute(webRequest, name);\n\t\tif (bindObject == null) {\n\t\t\traiseSessionRequiredException(&amp;quot;Session attribute '&amp;quot; + name + &amp;quot;' required - not bound in session&amp;quot;);\n\t\t}\n\t}\n\telse {\n\t\tbindObject = BeanUtils.instantiateClass(paramType);\n\t}\n\tWebDataBinder binder = createBinder(webRequest, bindObject, name);\n\tinitBinder(handler, name, binder, webRequest);\n\treturn binder;\n}\n<\/pre>\n<p> As expected, if the arugment name is found in the implicit model HashMap, the value is returned (<strong>line 12<\/strong>) otherwise the value is search in the HTTP session itself (<strong>line 15<\/strong>).<\/p>\n<p> Please notice at <strong>lines 23 &amp; 24<\/strong> the initialization of a <strong>WebDataBinder<\/strong> to bind request parameter to a particular type.<\/p>\n<p> Now let&#8217;s see how Spring resolves common arguments:<\/p>\n<pre class=\"brush: java; highlight: [5,15]; title: ; wrap-lines: false; notranslate\" title=\"\">\nprotected Object resolveCommonArgument(MethodParameter methodParameter, NativeWebRequest webRequest)\nthrows Exception {\n\n\t\/\/ Invoke custom argument resolvers if present...\n\tif (this.customArgumentResolvers != null) {\n\t\tfor (WebArgumentResolver argumentResolver : this.customArgumentResolvers) {\n\t\t\tObject value = argumentResolver.resolveArgument(methodParameter, webRequest);\n\t\t\tif (value != WebArgumentResolver.UNRESOLVED) {\n\t\t\t\treturn value;\n\t\t\t}\n\t\t}\n\t}\n\t\/\/ Resolution of standard parameter types...\n\tClass paramType = methodParameter.getParameterType();\n\tObject value = resolveStandardArgument(paramType, webRequest);\n\tif (value != WebArgumentResolver.UNRESOLVED &amp;amp;&amp;amp; !ClassUtils.isAssignableValue(paramType, value)) {\n\t\tthrow new IllegalStateException(&amp;quot;Standard argument type [&amp;quot; + paramType.getName() +\n\t\t\t\t&amp;quot;] resolved to incompatible value of type [&amp;quot; + (value != null ? value.getClass() : null) +\n\t\t\t\t&amp;quot;]. Consider declaring the argument type in a less specific fashion.&amp;quot;);\n\t}\n\treturn value;\n}\n<\/pre>\n<p> If you have declared custom argument resolvers, they are invoked here (<strong>line 5<\/strong>) otherwise Spring tries to resolve the argument by calling <em>resolveStandardArgument()<\/em> (<strong>line 15<\/strong>) which matches only all argument of type <strong>WebRequest<\/strong> or <strong>NativeWebRequest<\/strong>.<\/p>\n<p>&nbsp;<\/p>\n<h1>II Model and View resolution<\/h1>\n<p>Remember the <em>invokedHandlerMethod()<\/em> discussed in the previous topic ? <\/p>\n<pre class=\"brush: java; highlight: [11]; title: ; wrap-lines: false; notranslate\" title=\"\">\nprotected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler)\tthrows Exception {\n\n\tServletHandlerMethodResolver methodResolver = getMethodResolver(handler);\n\tMethod handlerMethod = methodResolver.resolveHandlerMethod(request);\n\tServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver);\n\tServletWebRequest webRequest = new ServletWebRequest(request, response);\n\tExtendedModelMap implicitModel = new BindingAwareModelMap();\n\n\tObject result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);\n\tModelAndView mav =\nmethodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);\n\tmethodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);\n\treturn mav;\n}\n <\/pre>\n<p> In this chapter we zoom in the <em>getModelAndView()<\/em> internals<\/p>\n<pre class=\"brush: java; highlight: [5,21,32,36,39,69,72]; title: ; wrap-lines: false; notranslate\" title=\"\">\npublic ModelAndView getModelAndView(Method handlerMethod, Class handlerType, Object returnValue,\nExtendedModelMap implicitModel, ServletWebRequest webRequest) throws Exception {\n\n\tResponseStatus responseStatusAnn = AnnotationUtils.findAnnotation(handlerMethod, ResponseStatus.class);\n\tif (responseStatusAnn != null) {\n\t\t\tHttpStatus responseStatus = responseStatusAnn.value();\n\t\t\tString reason = responseStatusAnn.reason();\n\t\t\tif (!StringUtils.hasText(reason)) {\n\t\t\t\twebRequest.getResponse().setStatus(responseStatus.value());\n\t\t\t}\n\t\t\telse {\n\t\t\t\twebRequest.getResponse().sendError(responseStatus.value(), reason);\n\t\t\t}\n\t\t\t\/\/ to be picked up by the RedirectView\n\t\t\twebRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, responseStatus);\n\n\t\t\tresponseArgumentUsed = true;\n\t\t}\n\n\t\t\/\/ Invoke custom resolvers if present...\n\t\tif (customModelAndViewResolvers != null) {\n\t\t\tfor (ModelAndViewResolver mavResolver : customModelAndViewResolvers) {\n\t\t\t\tModelAndView mav = mavResolver.resolveModelAndView(\n\t\t\t\t\t\thandlerMethod, handlerType, returnValue, implicitModel, webRequest);\n\t\t\t\tif (mav != ModelAndViewResolver.UNRESOLVED) {\n\t\t\t\t\treturn mav;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (returnValue instanceof HttpEntity) {\n\t\t\thandleHttpEntityResponse((HttpEntity&amp;lt;?&amp;gt;) returnValue, webRequest);\n\t\t\treturn null;\n\t\t}\n\t\telse if (AnnotationUtils.findAnnotation(handlerMethod, ResponseBody.class) != null) {\n\t\t\thandleResponseBody(returnValue, webRequest);\n\t\t\treturn null;\n\t\t}\n\t\telse if (returnValue instanceof ModelAndView) {\n\t\t\tModelAndView mav = (ModelAndView) returnValue;\n\t\t\tmav.getModelMap().mergeAttributes(implicitModel);\n\t\t\treturn mav;\n\t\t}\n\t\telse if (returnValue instanceof Model) {\n\t\t\treturn new ModelAndView().addAllObjects(implicitModel).addAllObjects(((Model) returnValue).asMap());\n\t\t}\n\t\telse if (returnValue instanceof View) {\n\t\t\treturn new ModelAndView((View) returnValue).addAllObjects(implicitModel);\n\t\t}\n\t\telse if (AnnotationUtils.findAnnotation(handlerMethod, ModelAttribute.class) != null) {\n\t\t\taddReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel);\n\t\t\treturn new ModelAndView().addAllObjects(implicitModel);\n\t\t}\n\t\telse if (returnValue instanceof Map) {\n\t\t\treturn new ModelAndView().addAllObjects(implicitModel).addAllObjects((Map) returnValue);\n\t\t}\n\t\telse if (returnValue instanceof String) {\n\t\t\treturn new ModelAndView((String) returnValue).addAllObjects(implicitModel);\n\t\t}\n\t\telse if (returnValue == null) {\n\t\t\t\/\/ Either returned null or was 'void' return.\n\t\t\tif (this.responseArgumentUsed || webRequest.isNotModified()) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\telse {\n\t\t\t\t\/\/ Assuming view name translation...\n\t\t\t\treturn new ModelAndView().addAllObjects(implicitModel);\n\t\t\t}\n\t\t}\n\t\telse if (!BeanUtils.isSimpleProperty(returnValue.getClass())) {\n\t\t\t\/\/ Assume a single model attribute...\n\t\t\taddReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel);\n\t\t\treturn new ModelAndView().addAllObjects(implicitModel);\n\t\t}\n\t\telse {\n\t\t\tthrow new IllegalArgumentException(&amp;quot;Invalid handler method return value: &amp;quot; + returnValue);\n\t\t}\n\t}\n<\/pre>\n<p> The <strong>if<\/strong> block at <strong>line 5<\/strong> injects a <strong>ResponseStatus<\/strong> object into the request response.<br \/>\n Then, if <strong>custom view and model resolvers<\/strong> have been declared, they are invoked (<strong>line 21<\/strong>)<br \/>\n The case of <strong>HttpEntity<\/strong> and <strong>@ResponseBody<\/strong> returned value type are processed at <strong>lines 32 &amp; 36<\/strong><\/p>\n<p>From <strong>line 39<\/strong> to <strong>line 69<\/strong>, all returned types from <strong>Model<\/strong>, <strong>View<\/strong>, <strong>ModelAndView<\/strong>, <strong>String<\/strong>, <strong>Map<\/strong> or <strong>@ModelAttribute<\/strong> are handled. In most cases a <strong>ModelAndView<\/strong> object is created and the implicit model HashMap is added to this object as well as the view name, if present.<\/p>\n<p> If the returned value is of primitive or any type not listed above, a simple ModelAndView object is created, fileld with the implicit model and returned (<strong>line 72<\/strong>).<br \/>\n&nbsp;<\/p>\n<h1>III Model attributes update<\/h1>\n<p>Last but not least, let&#8217;s see how Spring updates the model attributes before calling view renderers<\/p>\n<pre class=\"brush: java; highlight: [6,24,31,32,33]; title: ; wrap-lines: false; notranslate\" title=\"\">\npublic final void updateModelAttributes(Object handler, Map&amp;lt;String, Object&amp;gt; mavModel,\nExtendedModelMap implicitModel, NativeWebRequest webRequest) throws Exception {\n\n\tif (this.methodResolver.hasSessionAttributes() &amp;amp;&amp;amp; this.sessionStatus.isComplete()) {\n\t\tfor (String attrName : this.methodResolver.getActualSessionAttributeNames()) {\n\t\t\tthis.sessionAttributeStore.cleanupAttribute(webRequest, attrName);\n\t\t}\n\t}\n\t\/\/ Expose model attributes as session attributes, if required.\n\t\/\/ Expose BindingResults for all attributes, making custom editors available.\n\tMap&amp;lt;String, Object&amp;gt; model = (mavModel != null ? mavModel : implicitModel);\n\tif (model != null) {\n\t\ttry {\n\t\t\tString[] originalAttrNames = model.keySet().toArray(new String[model.size()]);\n\t\t\tfor (String attrName : originalAttrNames) {\n\t\t\t\tObject attrValue = model.get(attrName);\n\t\t\t\tboolean isSessionAttr = this.methodResolver.isSessionAttribute(\n\t\t\t\t\t\tattrName, (attrValue != null ? attrValue.getClass() : null));\n\t\t\t\tif (isSessionAttr) {\n\t\t\t\t\tif (this.sessionStatus.isComplete()) {\n\t\t\t\t\t\timplicitModel.put(MODEL_KEY_PREFIX_STALE + attrName, Boolean.TRUE);\n\t\t\t\t\t}\n\t\t\t\t\telse if (!implicitModel.containsKey(MODEL_KEY_PREFIX_STALE + attrName)) {\n\t\t\t\t\t\tthis.sessionAttributeStore.storeAttribute(webRequest, attrName, attrValue);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!attrName.startsWith(BindingResult.MODEL_KEY_PREFIX) &amp;amp;&amp;amp;\n\t\t\t\t\t\t(isSessionAttr || isBindingCandidate(attrValue))) {\n\t\t\t\t\tString bindingResultKey = BindingResult.MODEL_KEY_PREFIX + attrName;\n\t\t\t\t\tif (mavModel != null &amp;amp;&amp;amp; !model.containsKey(bindingResultKey)) {\n\t\t\t\t\t\tWebDataBinder binder = createBinder(webRequest, attrValue, attrName);\n\t\t\t\t\t\tinitBinder(handler, attrName, binder, webRequest);\n\t\t\t\t\t\tmavModel.put(bindingResultKey, binder.getBindingResult());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tcatch (InvocationTargetException ex) {\n\t\t\t\/\/ User-defined @InitBinder method threw an exception...\n\t\t\tReflectionUtils.rethrowException(ex.getTargetException());\n\t\t}\n\t}\n}\n<\/pre>\n<p> At <strong>line 6<\/strong>, we can see that if the session status is set to complete, Spring will remove all attributes declared in <strong>@SessionAttributes<\/strong> from the HTTP session.<\/p>\n<p> Next, the normal processing flow is invoked. Please notice the <strong>line 24<\/strong> where all session attributes are registered into the HTTP session.<\/p>\n<p> For each model attribute candidate to a custom data binder, Spring will create one and call the binding method before putting the result into the ModelAndView map (<strong>lines 31 to 33<\/strong>).<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post follows upon the Spring MVC part I : Request Handling topic. In this post we discuss in details how Spring handles the @RequestMapping annotation set on handler methods.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[31,19],"tags":[],"_links":{"self":[{"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/819"}],"collection":[{"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=819"}],"version-history":[{"count":0,"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/819\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=819"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=819"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=819"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}