从上一篇 SpringMVC源码分析(1) 中我们了解到在DispatcherServlet.doDispatch方法中会通过 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()) 这样的方式来执行request的handler方法。
先来分析一下ha.handle方法的调用过程:HandlerAdapter接口有一个抽象实现类AbstractHandlerMethodAdapter,在该抽象类中通过具体方法handle调用抽象方法handleInternal:
1 public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered { 2 3 private int order = Ordered.LOWEST_PRECEDENCE; 4 @Override 5 public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) 6 throws Exception { 7 return handleInternal(request, response, (HandlerMethod) handler); 8 } 9 //抽象方法,由具体的Adapter实现 10 protected abstract ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception; 11 12 }
RequestMappingHandlerAdapter类继承了抽象类AbstractHandlerMethodAdapter,实现了抽象方法 handleInternal,下面看看handleInternal方法的具体实现(需要注意,handler方法在synchronizeOnSession为true的情况下会放在同步代码块中进行执行):
1 public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean { 2 3 //省略若干代码... 4 5 @Override 6 protected ModelAndView handleInternal(HttpServletRequest request, 7 HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { 8 9 if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { 10 // Always prevent caching in case of session attribute management. 11 checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true); 12 } 13 else { 14 // Uses configured default cacheSeconds setting. 15 checkAndPrepare(request, response, true); 16 } 17 18 // Execute invokeHandlerMethod in synchronized block if required. 19 /* 20 * synchronizeOnSession默认为false,如果其为true,那么request对于的handler将会被放置在同步代码块 21 * 中进行执行。问题:什么时候???通过怎样的方式将synchronizeOnSession设置为true??? 22 */ 23 if (this.synchronizeOnSession) { 24 HttpSession session = request.getSession(false); 25 if (session != null) { 26 Object mutex = WebUtils.getSessionMutex(session); 27 synchronized (mutex) { 28 return invokeHandleMethod(request, response, handlerMethod); 29 } 30 } 31 } 32 // 不在同步块中执行handler方法 33 return invokeHandleMethod(request, response, handlerMethod); 34 } 35 }
现在就分析上面代码块中的 invokeHandleMethod(request, response, handlerMethod) 方法的执行流程,看看在调用handler前后又完成了什么工作,同时分析出@ModelAttribute的作用。先来总体看看,然后再各个部分分别做 分析,一共分为6个步骤(step1 ~ step6):
1 /** 2 * Invoke the @RequestMapping handler method preparing a @ModelAndView 3 * if view resolution is required. 4 */ 5 private ModelAndView invokeHandleMethod(HttpServletRequest request, 6 HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { 7 8 ServletWebRequest webRequest = new ServletWebRequest(request, response); 9 10 WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); 11 ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); 12 ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory); 13 14 //step 1 15 //新建一个mavContainer,用于存放所有可能会用到的ModelAndView 16 ModelAndViewContainer mavContainer = new ModelAndViewContainer(); 17 18 //step 2 19 /* 20 * Attributes can be set two ways. The servlet container may set attributes 21 * to make available custom information about a request. For example, for 22 * requests made using HTTPS, the attribute 23 * <code>javax.servlet.request.X509Certificate</code> can be used to 24 * retrieve information on the certificate of the client. Attributes can 25 * also be set programatically using {@link ServletRequest#setAttribute}. 26 * This allows information to be embedded into a request before a 27 * {@link RequestDispatcher} call. 28 * 29 * RequestContextUtils.getInputFlashMap(request)可以获取到request中的attribute, 30 * 并且将所有的request中的attribute放置在mavContainer中 31 */ 32 mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); 33 34 //step 3 35 /* 36 * 会在这个方法里将所有标注了@ModelAttribute的方法调用一遍,并且将该方法相关的ModelAndView放入到mavContainer中: 37 * 1、最常见的就是@ModelAttribute标注的方法入参中有Map,Model 38 * 2、如果方法有返回值,那么也会结果处理后放入到mavContainer中(是否只有ModelAndView类型的返回值才能放,有待考察??) 39 */ 40 modelFactory.initModel(webRequest, mavContainer, requestMappingMethod); 41 mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); 42 43 //step 4 44 //许多和 asyncManager 相关的东西,这个貌似和拦截器有关。 45 AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); 46 asyncWebRequest.setTimeout(this.asyncRequestTimeout); 47 48 final WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); 49 asyncManager.setTaskExecutor(this.taskExecutor); 50 asyncManager.setAsyncWebRequest(asyncWebRequest); 51 asyncManager.registerCallableInterceptors(this.callableInterceptors); 52 asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); 53 54 if (asyncManager.hasConcurrentResult()) { 55 Object result = asyncManager.getConcurrentResult(); 56 mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0]; 57 asyncManager.clearConcurrentResult(); 58 59 if (logger.isDebugEnabled()) { 60 logger.debug("Found concurrent result value [" + result + "]"); 61 } 62 requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result); 63 } 64 65 //step 5 66 /* 67 * Invokes the method and handles the return value through one of the configured {@link HandlerMethodReturnValueHandler}s. 68 * 在这里调用handler方法 69 */ 70 requestMappingMethod.invokeAndHandle(webRequest, mavContainer); 71 72 //step 6 73 //要么返回ModelAndView,要么返回null 74 if (asyncManager.isConcurrentHandlingStarted()) { 75 return null; 76 } 77 return getModelAndView(mavContainer, modelFactory, webRequest); 78 }
下面从step1~step6逐一的进行分析。
step1:ModelAndViewContainer mavContainer = new ModelAndViewContainer();
首先看一下ModelMap是如何定义的,这对理解ModelAndViewContainer以及后面的代码有帮助,主要是理解:ModelMap实际上就是一个LinkedHashMap,而且这个Map的”值“是Object类型,能够放置所有类型的Java对象:
/** * 可以看出ModelMap实际上就是一个LinkedHashMap,且“值”为超类Object类型 * 能够放置所有的Java对象 */ public class ModelMap extends LinkedHashMap<String, Object> { public ModelMap() { } // 调用这个构造函数之前会先调用其父类构造函数,得到一个Map对象 public ModelMap(String attributeName, Object attributeValue) { addAttribute(attributeName, attributeValue); } // 就是将attributeValue对象放置到Map末尾,同时指定键值为attributeName public ModelMap addAttribute(String attributeName, Object attributeValue) { put(attributeName, attributeValue); return this; } // attributes是一个Map集合;所谓merge无非是将attributes这个集合放置到现有集合的末尾 public ModelMap mergeAttributes(Map<String, ?> attributes) { if (attributes != null) { for (Map.Entry<String, ?> entry : attributes.entrySet()) { String key = entry.getKey(); if (!containsKey(key)) { put(key, entry.getValue()); } } } return this; } //省略一些方法的定义... }
再来看ModelAndViewContainer到底是个什么东西,从下面的ModelAndViewContainer定义中不难理解其含有两个ModelMap对象defaultModel和redirectModel默认情况下使用defaultModel,也可以通过其方法设置使用redirectModel。
1 /** 2 * 定义了两个ModelMap对象:defaultModel和redirectModel,实际上也就是两个Map<String, Object> 3 * 其中defaultModel已经完成了初始化。默认使用defaultModel。 4 * 也可以通过其中的方法来设置使用redirectModel,这个是方便移植和使用其它框架而设定的。 5 */ 6 public class ModelAndViewContainer { 7 8 private boolean ignoreDefaultModelOnRedirect = false; 9 // view的用法值得去探究 10 private Object view; 11 12 /* BindingAwareModelMap实际上也就是一个Map, 13 * 看看定义 public class BindingAwareModelMap extends ModelMap implements Model 14 * 15 * 从这里可以看出,ModelAndViewContainer对象都有一个默认的ModelMap 16 */ 17 private final ModelMap defaultModel = new BindingAwareModelMap(); 18 19 // 这个为方便移植其它框架的Model而设置的,SpringMVC本身使用的是defaultModel 20 private ModelMap redirectModel; 21 22 private boolean redirectModelScenario = false; 23 24 private final SessionStatus sessionStatus = new SimpleSessionStatus(); 25 26 private boolean requestHandled = false; 27 28 /** 29 * Set a view name to be resolved by the DispatcherServlet via a ViewResolver. 30 * Will override any pre-existing view name or View. 31 */ 32 public void setViewName(String viewName) { 33 this.view = viewName; 34 } 35 36 /** 37 * 返回"default" 或者是 "redirect" 模型,具体根据redirectModelScenario等 38 * 属性的值来确定(具体用法参看javadoc) 39 */ 40 public ModelMap getModel() { 41 if (useDefaultModel()) { 42 return this.defaultModel; 43 } 44 else { 45 return (this.redirectModel != null) ? this.redirectModel : new ModelMap(); 46 } 47 } 48 49 // 返回默认的Model,而不考虑其它的属性值如何 50 public ModelMap getDefaultModel() { 51 return this.defaultModel; 52 } 53 54 }
到现在为止,step1完成的工作已经分析完全。
step2: mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request))
/* * * 检索request域中的attribute,并将其放置在mavContainer末尾 * * RequestContextUtils.getInputFlashMap(request)会调用HttpServletRequest.getAttribute方法。 * 可以获取到request中的attribute属性。这个attribute就是我们属性的attribute,它可以通过两 * 种方式来设置:①servlet容器为request设置的;②通过ServletRequest.setAttribute方法来设置。 * */ mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
step3:modelFactory.initModel(webRequest, mavContainer, requestMappingMethod)
这个方法的作用在javadoc中描述得很清楚:
Populate the model in the following order:
- Retrieve "known" session attributes listed as
@SessionAttributes
. - Invoke
@ModelAttribute
methods - Find
@ModelAttribute
method arguments also listed as@SessionAttributes
and ensure they‘re present in the model raising an exception if necessary.
也就是说初始化模型的时候会按顺序完成三件事情:
①、检索现有的session域中的attributes,并将其放置于mavContainer末尾;
②、调用所有@ModelAttribute注解标注的方法;
③、找出handler方法中使用@ModelAttribute注解修饰的入参(主要是@ModelAttribute指定的value属性值,如果没有指定则是类名第一个字母小写得到,我们假定它为V),如果V同时被@SessionAttributes
的value属性值指定,那么就必须保证在此时的mavContainer中必须含有”key“为V的对象。如果没有,则会抛出一个异常。
带着这个印象我们分析代码就会容易很多:
1 /* 2 * 会在这个方法里将所有标注了@ModelAttribute的方法调用一遍,并且将该方法相关的ModelAndView放入到mavContainer中: 3 * 1、最常见的就是@ModelAttribute标注的方法入参中有Map,Model 4 * 2、如果方法有返回值,那么也会结果处理后放入到mavContainer中(是否只有ModelAndView类型的返回值才能放,有待考察??) 5 */ 6 modelFactory.initModel(webRequest, mavContainer, requestMappingMethod){ 7 //完成①:检索现有的session域中的attributes,并将其放置于mavContainer末尾 8 Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request); 9 mavContainer.mergeAttributes(sessionAttributes); 10 11 //完成②:将所有的@ModelAttribute标注的方法都调用一遍。调用完了以后,将调用方法的结果放置到mavContainer中 12 invokeModelAttributeMethods(request, mavContainer){ 13 //modelMethods中包含了所有@ModelAttribute标注的方法,在这个while循环中将会把所有@ModelAttribute标注 14 //的方法都调用一遍 15 while (!this.modelMethods.isEmpty()) { 16 InvocableHandlerMethod attrMethod = getNextModelMethod(mavContainer).getHandlerMethod(); 17 String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value(); 18 //如果,mavContainer中已经包含有modelName名的attribute,那么,将不会调用@ModelAttribute标注的方法 19 if (mavContainer.containsAttribute(modelName)) { 20 continue; 21 } 22 23 //真正的调用@ModelAttribute标注的方法 24 Object returnValue = attrMethod.invokeForRequest(request, mavContainer); 25 26 //如果@ModelAttribute标注的方法不是void类型,则将其返回结果转换成returnValueName;如果mavContainer中 27 //没有包含returnValueName,则将方法返回的结果放置到mavContainer中。 28 if (!attrMethod.isVoid()){ 29 String returnValueName = getNameForReturnValue(returnValue, attrMethod.getReturnType()); 30 if (!mavContainer.containsAttribute(returnValueName)) { 31 mavContainer.addAttribute(returnValueName, returnValue); 32 } 33 } 34 } 35 }; 36 37 38 // 完成③: 39 40 /* 41 * 找出handler方法中使用@ModelAttribute注解修饰的入参(主要是@ModelAttribute指定的value属性值, 42 * 如果没有指定则是类名第一个字母小写得到,我们假定它为V),如果V同时被@SessionAttributes的value 43 * 属性值指定,则将这样的V放入到nameList中。 44 * 45 */ 46 List<String> nameList = findSessionAttributeArguments(handlerMethod){ 47 List<String> result = new ArrayList<String>(); 48 //遍历处理方法的所有参数 49 for (MethodParameter parameter : handlerMethod.getMethodParameters()) { 50 //如果处理方法有@ModelAttribute标注 51 if (parameter.hasParameterAnnotation(ModelAttribute.class)) { 52 //得到参数的String型的名字 53 String name = getNameForParameter(parameter); 54 //如果在处理方法所在的类定义处使用了@SessionAttributes注解,那么就将该参数放入到List中 55 if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, parameter.getParameterType())) { 56 result.add(name); 57 } 58 } 59 } 60 return result; 61 }; 62 63 // 保证nameList中记录的V必须在mavContainer中存在,如果不存在则会抛出异常 64 for (String name : nameList) { 65 //如果mavContainer中没有包含有name名字的attribute,那么再一次检查request中是否包含了name名字的attribute 66 if (!mavContainer.containsAttribute(name)) { 67 //再一次检查request中是否包含了name名字的attribute 68 Object value = this.sessionAttributesHandler.retrieveAttribute(request, name); 69 //如果没有检测到,则会抛出一个异常 70 if (value == null) { 71 throw new HttpSessionRequiredException("Expected session attribute ‘" + name + "‘"); 72 } 73 //如果检测到了,那么会将这个attribute放入到mavContainer中 74 mavContainer.addAttribute(name, value); 75 } 76 } 77 };
step4: 暂时不做分析...
step5: 调用handler方法同时处理返回结果
1 requestMappingMethod.invokeAndHandle(webRequest, mavContainer){ 2 //一、 调用处理方法,并的到返回结果 3 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs){ 4 //为调用处理方法准备参数 5 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs){ 6 7 //... 8 9 //从这里可以看出@ModelAttribute能够修饰handler的入参 10 String name = ModelFactory.getNameForParameter(parameter){ 11 ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class); 12 //如果当前参数使用了@ModelAttribute标注,则获取到该标签的value属性值 13 String attrName = (annot != null) ? annot.value() : null; 14 //如果attrName有text,则返回该attrName值,也就是@ModelAttribute的value属性值。 15 //反之,则返回参数类型第一个字母小写后得到的字符串 16 return StringUtils.hasText(attrName) ? attrName : Conventions.getVariableNameForParameter(parameter); 17 }; 18 19 //检测mavContainer中是否包含有name关键字的ModelAndView,如果包含有,则获取它并返回。如果没有,则创建一个attribute 20 Object attribute = (mavContainer.containsAttribute(name) ? 21 mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest)); 22 23 //对于WebDataBinder而言,最重要的参数就是name和attribute 24 //将请求域中表单数据绑定到上面的到的attribute中:request域中有的就重新赋值,没有的就保持原有的属性值不变。 25 /* 26 * 结合前面name和attribute的获取过程可以分析出request表单数据绑定的过程: 27 * ①、如果handler的入参使用了@ModelAttribute,同时还指定了其value属性值,那么attrName就是其value属性值; 28 * ②、如果handler的入参处没有指定@ModelAttribute的value属性值,或者是根本就没有使用该注解,那么其 29 * attrName就是参数类名第一个字母小写的到; 30 * ③、搜索mavContainer中是否有键值为attrName的attribute对象,如果有,则将这个对象作为表单数据绑定的对象; 31 * 如果没有,则新创建一个作为数据绑定的对象; 32 */ 33 WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); 34 if (binder.getTarget() != null) { 35 //绑定参数 36 bindRequestParameters(binder, webRequest); 37 validateIfApplicable(binder, parameter); 38 if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { 39 throw new BindException(binder.getBindingResult()); 40 } 41 } 42 43 // Add resolved attribute and BindingResult at the end of the model 44 Map<String, Object> bindingResultModel = binder.getBindingResult().getModel(); 45 //删除原有的attribute 46 mavContainer.removeAttributes(bindingResultModel); 47 //将处理过的attribute添加到末尾,这样mavContainer中相对应的attribute就是更新以后的attribute 48 mavContainer.addAllAttributes(bindingResultModel); 49 50 51 //返回需要格式的args 52 return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); 53 }; 54 55 //**真正的调用处理方法,此时的args是依据request表单数据更新过的args 56 Object returnValue = doInvoke(args); 57 58 //返回处理方法返回的结果,这个结果将会进一步处理 59 return returnValue; 60 }; 61 62 //二、处理目标方法的返回结果 63 //设置应答状态 64 setResponseStatus(webRequest); 65 66 if (returnValue == null) { 67 if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) { 68 mavContainer.setRequestHandled(true); 69 return; 70 } 71 } 72 else if (StringUtils.hasText(this.responseReason)) { 73 mavContainer.setRequestHandled(true); 74 return; 75 } 76 77 mavContainer.setRequestHandled(false); 78 try { 79 //处理返回结果 80 this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest){ 81 //获取返回结果对应的处理方法 82 HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType); 83 Assert.notNull(handler, "Unknown return value type [" + returnType.getParameterType().getName() + "]"); 84 //利用获取到的结果处理方法来处理结果 85 handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest){ 86 if (returnValue == null) { 87 return; 88 } 89 else if (returnValue instanceof String) { 90 String viewName = (String) returnValue; 91 mavContainer.setViewName(viewName); 92 if (isRedirectViewName(viewName)) { 93 mavContainer.setRedirectModelScenario(true); 94 } 95 } 96 else { 97 // should not happen 98 throw new UnsupportedOperationException("Unexpected return type: " + 99 returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); 100 } 101 }; 102 }; 103 } 104 105 };
从上面的代码分析可以得出表单数据绑定的流程:
1、request的表单数据绑定首先需要创建一个WebDataBinder对象: binder = binderFactory.createBinder(webRequest, attribute, name)。表单数据在webRequest中,还要确定两个关键的参数:attribute【Object类型】, name【String类型】。
2、确定name(也就是attrName):
①、如果handler的入参处使用了@ModelAttribute注解,同时该注解还制定了value属性值,那么name就是value的属性值;
②、如果handler的入参数使用了@ModelAttribute注解,但是没有指定value属性值;或者是,入参处根本就没有使用@ModelAttribute注解;那么这2种情况下其name值就是handler入参类名第一个字母小写得到的String;
3、确定attribute:
①、查看mavContainer中是否包含有key=name的attribute对象(mavContainer.getModel()实际上得到的是一个Map<String, Object>)。如果有,则attribute就是该对象;如果没有,则新创建一个对象赋给attribute。其代码如下:
Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest));
4、通过已经确定好的attribute和name就能够完成数据的绑定了。
step6: 返回ModelAndView
1 if (asyncManager.isConcurrentHandlingStarted()) { 2 return null; 3 } 4 5 return getModelAndView(mavContainer, modelFactory, webRequest);
返回有两种情况,第一个貌似和同步机制有关,asyncManager的工作机制后续继续分析。这里主要是分析getModelAndView(mavContainer, modelFactory, webRequest)方法的源码。
1 private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, 2 ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { 3 4 modelFactory.updateModel(webRequest, mavContainer); 5 if (mavContainer.isRequestHandled()) { 6 return null; 7 } 8 ModelMap model = mavContainer.getModel(); 9 ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model); 10 if (!mavContainer.isViewReference()) { 11 mav.setView((View) mavContainer.getView()); 12 } 13 if (model instanceof RedirectAttributes) { 14 Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); 15 HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); 16 RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); 17 } 18 return mav; 19 }
视图模型是一个大的主题,后面再仔细分析。