上一篇文章中咱們瞭解了Spring如何處理@RequestMapping註解,並將請求映射信息保存到系統中以處理客戶端發送來的請求,可是Spring是怎樣接受請求,並根據請求URL來匹配正確的處理器方法呢,更重要的是Spring容許咱們定義簽名靈活的處理器方法,也就是說你的參數類型,順序,返回類型均可以自定義,只要你方便就好。真的很棒,可是他怎樣作到的呢? 這篇文章咱們就研究這個問題。java
HandlerMethodArgumentResolver 一個策略接口,根據請求解析處理方法的參數值(這是重點),咱們就是經過實現它來自定義咱們的參數類型的。Spring默認提供了十多個類型的解析器來處理常見的方法參數,如@PathVariabe,@RequestParam, Model,@ModelAttribute等等。
ios
HandlerMethodReturnValueHandler 一個策略接口,用來處理處理器方法的返回值(重點啊), 咱們經過實現它來自定義咱們的返回類型web
RequestMappingHandlerAdapter 一個HandlerAdapter的實現類,用來處理HandlerMethod,它包含了上面兩個接口一系列實現類的列表,用於處理不一樣的參數和返回類型算法
HandlerMethodArgumentResolverComposite 這個類 維護了一個MethodParameter 與 HandlerMethodArgumentResolver的映射表,能夠快速檢索某一MethodParameter對應的Resolver,並調用Resolver進行參數解析。設計模式
HandlerMethodReturnValueHandlerComposite 這個類只維護了一個全部HandlerMethodReturnValueHandler的列表,每次遍歷檢索支持某返回類型的處理器。數組
ModelAndViewContainer 這個類記錄了Model和View的對應關係,可快速檢索某個視圖對應的Model。緩存
說明:當咱們閱讀@RequestMapping註解的說明時會了解到,Spring的處理器方法默認支持諸多參數類型和返回值類型,並提供了每個參數類型,返回值類型的解析器和處理器,上述1,2兩個接口的實現類,咱們大體看一下他們的類層次結構:session
以上是方法參數的可能類型以及他們的解析器,Spring默認支持仍是挺棒的,能夠應付絕大多數需求了。mvc
以上是可能的返回值類型和他們的處理器,一樣Spring的默認支持很強大,咱們均可以實現本身的參數解析器和返回值處理器。app
咱們能夠看到支持的類型很是多,因爲篇幅限制,咱們只講解最經常使用的一個或幾個,如@PathVariable,@RequestParam,Model等,若是有興趣你們能夠自行研究,思路都同樣。
咱們知道Spring會經過DispatcherServlet來處理全部的請求,那麼咱們就看他是怎麼處理的
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; AsyncExecutionChain asyncChain = AsyncExecutionChain.getForCurrentRequest(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); // 肯定當前請求的處理器(HandlerExecutionChain 包含Handler和Interceptor列表) mappedHandler = getHandler(processedRequest, false); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response);//404異常 return; } // 肯定當前請求的處理器適配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); //......省略諸多代碼...... // 調用處理處理器方法,返回ModelAndView對象 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); }//處理返回結果 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } }
相信這個方法你們都很熟了,我刪除了一些與當前主體無關的代碼,下面咱們看getHandler是怎麼作的:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //這裏有一個handlerMappings實例變量,若是你看過<mvc:annotation-driven/>那篇文章,相信你就知道這 //個handlerMappings都包括哪些HandlerMapping實例了? for (HandlerMapping hm : this.handlerMappings) { HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } return null; }
<mvc:annotation-driven/>這篇博客 中說明了在啓用該標籤時Spring會默認註冊RequestMappingHandlerMapping實例在處理@RequestMapping註解,而@RequestMapping (1) 這篇博客說明了該HandlerMapping是如何處理@RequestMapping註解,以及怎麼保存請求映射關係的。下面咱們就看RequestMappingHandlerMapping的getHandler方法:
//..... //AbstractHandlerMapping //..... public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { Object handler = getHandlerInternal(request);//調用下面的方法 if (handler == null) { handler = getDefaultHandler(); } if (handler == null) { return null; } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = getApplicationContext().getBean(handlerName); }//返回該請求對應的HandlerExecutionChain(包括處理器方法和攔截器) return getHandlerExecutionChain(handler, request); } //..... //AbstractHandlerMethodMapping(RequestMappingHandlerMapping 的祖先類) //返回值是HandlerMethod //..... @Override protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); ///查找請求路徑對應的HandlerMethod實例 HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); //確保HandlerMethod中的handler是處理器實例而不是處理器名字 return (handlerMethod != null) ? handlerMethod.createWithResolvedBean() : null; }
以上兩個方法都是RequestMappingHandlerMapping的祖先類,邏輯很簡單,先獲取當前請求的路徑,而後查找該路徑對應的HandlerMethod實例。@RequestMapping (1)
這篇博客最後講到了,RequestMappingHandlerMapping中的兩個映射表實例,urlMap和handlerMethods,第一個是路徑與RequestMappingInfo的映射,第二個是RequestMappingInfo和HandlerMethod的映射,不用說,lookupHandlerMethod方法確定是檢索這兩個變量了:
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request){ List<Match> matches = new ArrayList<Match>(); //查找urlMap,獲取直接匹配的RequestMappingInfo列表。如 //URL 是/work/produce/2, @RequestMapping("/work/produce/2")直接匹配 List<T> directPathMatches = this.urlMap.get(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) {//若是沒有找到直接匹配項,遍歷全部的註冊的RequestMappingInfo來查找 //遍歷全部可能的RequestMappingInfo,找到徹底匹配的RequestMappingInfo實例,並生成Match對象 //添加到Match列表中,Match是RequestMappingInfo和HandlerMethod的臨時映射表。 //舉個例子:請求URL多是GET:/work/produce/2, //而@RequestMapping("/work/produce/{no}" "GET")此時須要匹配是不是GET請求,以及模式是否匹配 addMatchingMappings(this.handlerMethods.keySet(), matches, request); } if (!matches.isEmpty()) {//排序,找出最佳匹配 Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); Collections.sort(matches, comparator); Match bestMatch = matches.get(0); if (matches.size() > 1) {//若是可能的方法多餘一個 Match secondBestMatch = matches.get(1);//而且兩個方法的@RequestMapping內容相同 if (comparator.compare(bestMatch, secondBestMatch) == 0) {//拋出異常 Method m1 = bestMatch.handlerMethod.getMethod(); Method m2 = secondBestMatch.handlerMethod.getMethod(); throw new IllegalStateException( "Ambiguous handler methods mapped for HTTP path"); } } //這裏是處理請求路徑中的變量,若是/work/produce/{no}匹配的/work/produce/2中將no=2 //添加到Request的屬性表中,以便後面@PathVarible參數的處理 handleMatch(bestMatch.mapping, lookupPath, request); return bestMatch.handlerMethod; } else { return handleNoMatch(handlerMethods.keySet(), lookupPath, request); } }
在進行URL匹配中,Spring會先查找是否存在直接匹配的RequestMappingInfo實例,即@RequestMapping中的value,method屬性徹底匹配請求的,若是沒有找到一般是存在PathVariable的,若是上面講的/{no}和/222的狀況等也是匹配的, 找到匹配項後,須要找出最優解,而後將路徑中的變量存入Request的變量表中,咱們分別詳細的瞭解下:
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) { for (T mapping : mappings) {//遍歷全部的RequestMappingInfo列表 T match = getMatchingMapping(mapping, request);//獲取匹配的RequestMappingInfo實例 if (match != null) {//並生成匹配的RequestMappingInfo實例和對應HandlerMethod的Match實例 matches.add(new Match(match, handlerMethods.get(mapping))); } } }
繼續看getMatchingMapping的實現:
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) { //查看RequestMappingInfo的全部屬性是否匹配 RequestMethodsRequestCondition methods = methodsCondition.getMatchingCondition(request); ParamsRequestCondition params = paramsCondition.getMatchingCondition(request); HeadersRequestCondition headers = headersCondition.getMatchingCondition(request); ConsumesRequestCondition consumes = consumesCondition.getMatchingCondition(request); ProducesRequestCondition produces = producesCondition.getMatchingCondition(request); if (methods == null || params == null || headers == null || consumes == null || produces == null) { return null; }//咱們重點看這個,路徑是否匹配 PatternsRequestCondition patterns = patternsCondition.getMatchingCondition(request); if (patterns == null) { return null; } RequestConditionHolder custom = customConditionHolder.getMatchingCondition(request); if (custom == null) { return null; } return new RequestMappingInfo(patterns, methods, params, headers, produces, custom.getCondition()); }
咱們知道RequestMappingInfo就是@RequestMapping註解的抽象,它包含@RequestMapping中的全部屬性,所以在查找匹配項時,須要查看全部這些屬性是否與請求匹配。咱們這裏只看路徑模式是否匹配,其餘屬性自行研究,都很簡單:
public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) { if (this.patterns.isEmpty()) { return this; }//獲取請求路徑如/work/produce/2 String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); List<String> matches = new ArrayList<String>(); //遍歷@RequestMapping中的全部模式 for (String pattern : patterns) { //找出與請求路徑匹配的模式,如/work/produce/{no} String match = getMatchingPattern(pattern, lookupPath); if (match != null) { matches.add(match); } }//排序 Collections.sort(matches, this.pathMatcher.getPatternComparator(lookupPath)); //返回匹配的請求模式實例 return matches.isEmpty() ? null : new PatternsRequestCondition(matches, this.urlPathHelper, this.pathMatcher, this.useSuffixPatternMatch, this.useTrailingSlashMatch); } private String getMatchingPattern(String pattern, String lookupPath) { if (pattern.equals(lookupPath)) { return pattern;//直接匹配 } if (this.useSuffixPatternMatch) {//是否使用後綴模式,/abc/de匹配/abc boolean hasSuffix = pattern.indexOf('.') != -1; if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) { return pattern + ".*"; } }//匹配/work/produce/{no} 和/work/produce/2 if (this.pathMatcher.match(pattern, lookupPath)) { return pattern; }//是否使用結尾的斜線匹配 boolean endsWithSlash = pattern.endsWith("/"); if (this.useTrailingSlashMatch) { if (!endsWithSlash && this.pathMatcher.match(pattern + "/", lookupPath)) { return pattern +"/"; } } return null; }
至於上面的pathMatcher.match方法這裏就不分析了,能夠本身看看,匹配算法仍是比較複雜的,主要是儘量的全面,除了進行匹配外,還會將路徑中的變量保存起來以便@PathVariable參數使用。
以上即是整個的匹配過好麻煩,或許你會說這會不會下降Spring的性能?實話說,在處理首次請求時,效率是不好,可是Spring使用了各類緩存策略,一旦程序進入正軌,效率就很是高了。
如今咱們已經查找到了對應請求的處理器方法,下面咱們就看Spring是如何在運行時動態地調用處理器方法的,並傳遞正確的參數。在doDispatch方法中,咱們看到,肯定了處理器(方法)後,Spring接着獲取了該處理器方法的適配器(HandlerAdapter概念講解中說到過,用來調用處理器方法的)
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { //這個handlerAdapters跟handlerMappings同樣,啓用<mvc:annoation-driven/>後默認註冊 //RequestMappingHandlerAdapter(since3.1) for (HandlerAdapter ha : this.handlerAdapters) { if (ha.supports(handler)) { return ha; } } throw new ServletException("No adapter for handler [" + handler + "]: Does your handler implement a supported interface like Controller?"); } //是否支持當前處理器,其實就是看看處理器是否是HandlerMethod實例 public final boolean supports(Object handler) { return handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler); }
獲取了HandlerAdapter後,Spring就會調用handlerAdapter實例的handle方法,並返回ModelAndView實例:
@Override protected final ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { //若是HandlerMethod所屬的處理器被@SessionAttribute註解標記了 if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { //設置響應頭信息,防止緩存以便Session屬性的管理 checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true); } else { //設置響應頭,緩存默認時間 checkAndPrepare(request, response, true); } //要求在Session級別上進行同步,即同一個客戶端的多個請求須要阻塞調用該處理器方法 if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) {//調用 return invokeHandleMethod(request, response, handlerMethod); } } }//調用 return invokeHandleMethod(request, response, handlerMethod); }
咱們接着看invokeHandlerMethod方法:
private ModelAndView invokeHandleMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { //包裝請求和響應對象 ServletWebRequest webRequest = new ServletWebRequest(request, response); //獲取與HandlerMethod對應的DataBinderFactory。 WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); //獲取處理器方法所屬處理器中被@ModelAttribute標記,可是沒有被@RequestMapping標記的方法 ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); //建立請求映射方法,並將HandlerAdapter中的參數解析器列表和返回值處理器列表傳遞給它。 ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory); //建立ModelAndViewContainer ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); modelFactory.initModel(webRequest, mavContainer, requestMappingMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); //獲取並設置當前請求的異步調用鏈實例 AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request); chain.addDelegatingCallable(getAsyncCallable(mavContainer, modelFactory, webRequest)); chain.setAsyncWebRequest(createAsyncWebRequest(request, response)); chain.setTaskExecutor(this.taskExecutor); //調用該處理器方法。 requestMappingMethod.invokeAndHandle(webRequest, mavContainer); if (chain.isAsyncStarted()) { return null; } //獲取並返回ModelAndView return getModelAndView(mavContainer, modelFactory, webRequest); }
關於異步調用鏈那塊咱們暫不關心,後續文章會專題討論。從上面代碼能夠看到,在調用方法前,分別檢查了處理器中存在的@InitBinder註解的方法和@ModelAttribute註解的方法,InitBinder方法用於類型轉化,如將String轉化爲Date類型等,能夠經過@InitBinder方法實現,感興趣能夠本身看看,不在詳細分析。至於@ModelAttribute註解的方法,其返回值會被放入Model對象中供視圖使用。下面咱們看invokeAndHandle方法:
public final void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //....省略異步調用方法,暫不考慮 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); //....省略幾行代碼,暫不考慮 try {//處理返回結果 this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } }
接着咱們看下invokeForRequest方法:----we are so close.
public final Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //獲取方法參數值。 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); //傳遞參數值,調用,返回返回值 Object returnValue = invoke(args); return returnValue; }
啊哈上面的代碼貌似很簡單,實則不是,重點就在getMethodArgumentValues方法,這纔是咱們這片文章的真正主題呢。打起精神來了:
private Object[] getMethodArgumentValues( NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //獲取處理器方法的MethodParameter數組,就是方法的「參數定義」列表。 MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; //遍歷全部方法參數 for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(parameterNameDiscoverer); //肯定泛型參數的類型 GenericTypeResolver.resolveParameterType(parameter, getBean().getClass()); //根據提供的參數值,解析當前參數的值 args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } //根據內置的參數解析器(上面的圖片中列出了),來解析當前的參數值 if (argumentResolvers.supportsParameter(parameter)) { try { args[i] = argumentResolvers.resolveArgument(parameter, mavContainer, request, dataBinderFactory); continue; } } //若是參數值依舊爲空,拋出異常。 if (args[i] == null) { String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i); throw new IllegalStateException(msg); } } return args; }
該方法是HandlerMethod中的方法,所以能夠調用getMethodParameters()方法獲取參數列表,而後遍歷這些參數,分別用參數解析器來解析當前參數值,其中,argumentResolvers是HandlerMethodArgumentResolverComposite,概念講解中已經闡述,它包含了全部的參數解析器的列表,以及參數類型和解析器的映射表,咱們不妨看看到底什麼怎麼回事:
//是的,這個方法是HandlerAdapter中的方法,上一篇文章咱們介紹了這是InitializingBean接口中的方法, //會被自動調用 public void afterPropertiesSet() { if (this.argumentResolvers == null) { //調用下面的方法,獲取全部某人蔘數解析器。 List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); //這個是HandlerAdapterComposite實例。 this.argumentResolvers = new HandlerMethodArgumentResolverComposite() .addResolvers(resolvers); } if (this.initBinderArgumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers(); this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite() .addResolvers(resolvers); }//註冊默認返回值處理器 if (this.returnValueHandlers == null) { List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite() .addHandlers(handlers); } } /** * Return the list of argument resolvers to use including built-in resolvers * and custom resolvers provided via {@link #setCustomArgumentResolvers}. */ private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>(); // Annotation-based argument resolution resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); resolvers.add(new RequestParamMapMethodArgumentResolver()); resolvers.add(new PathVariableMethodArgumentResolver()); resolvers.add(new PathVariableMapMethodArgumentResolver()); resolvers.add(new ServletModelAttributeMethodProcessor(false)); resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters())); resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters())); resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory())); resolvers.add(new RequestHeaderMapMethodArgumentResolver()); resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); // Type-based argument resolution resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); resolvers.add(new HttpEntityMethodProcessor(getMessageConverters())); resolvers.add(new RedirectAttributesMethodArgumentResolver()); resolvers.add(new ModelMethodProcessor()); resolvers.add(new MapMethodProcessor()); resolvers.add(new ErrorsMethodArgumentResolver()); resolvers.add(new SessionStatusMethodArgumentResolver()); resolvers.add(new UriComponentsBuilderMethodArgumentResolver()); // Custom arguments if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } // Catch-all resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); resolvers.add(new ServletModelAttributeMethodProcessor(true)); return resolvers; }
這下清晰了吧,RequestMappingHandlerAdapter實現了InitializingBean接口,所以Spring啓動是會調用它的afterPropertySet方法,進行上述參數解析器的註冊。而後在處理器方法調用過程當中會遍歷這些解析器找到支持當前參數的解析器並解析參數。Perfect。咱們再回到以前的resolveArgument方法:
public Object resolveArgument( MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { //調用下面的方法回去支持當前參數類型的解析器。 HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter); //調用該解析器的解析方法進行解析。 return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); } //前面說過了,HandlerAdapterComposite會維護一個MethodParameter到解析器的映射關係。沒錯吧 private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); if (result == null) { for (HandlerMethodArgumentResolver methodArgumentResolver : argumentResolvers) { //判斷解析器是否支持當前參數類型 if (methodArgumentResolver.supportsParameter(parameter)) { result = methodArgumentResolver; this.argumentResolverCache.put(parameter, result); break; } } } return result; }
再往下就到某個解析器怎樣解析具體參數了,咱們知道就像上面圖片中描述的,有大約一二十個解析器,咱們不可能所有分析,這裏咱們只分析其中常見的一個:@PathVariable註解的解析器。
public boolean supportsParameter(MethodParameter parameter) { //若是該參數沒有被@PathVariable註解標記,則返回false,不支持 if (!parameter.hasParameterAnnotation(PathVariable.class)) { return false; } //若是該參數是Map類型的。則判斷@PathVariable是否設置了value屬性 if (Map.class.isAssignableFrom(parameter.getParameterType())) { String paramName = parameter.getParameterAnnotation(PathVariable.class).value(); return StringUtils.hasText(paramName); } return true; }
從上面代碼咱們知道PathVariableMethodArgumentResolver支持被@PathVariable註解的參數。下面咱們看它怎樣解析參數值得:
public final Object resolveArgument( MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { //獲取參數類型 Class<?> paramType = parameter.getParameterType(); //獲取參數的名-值信息,如@PathVariable("NO") 則NO爲名稱,值爲請求路徑中對應 //@RequestMapping("/work/produce/NO")中NO的值。 NamedValueInfo namedValueInfo = getNamedValueInfo(parameter); //解析請求路徑中對應名稱的值。上例中NO對應的值。 Object arg = resolveName(namedValueInfo.name, parameter, webRequest); if (arg == null) { if (namedValueInfo.defaultValue != null) { arg = resolveDefaultValue(namedValueInfo.defaultValue); } else if (namedValueInfo.required) { handleMissingValue(namedValueInfo.name, parameter); } arg = handleNullValue(namedValueInfo.name, arg, paramType); }//DataBinder,後續講解。 if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name); arg = binder.convertIfNecessary(arg, paramType, parameter); } //將上面解析到的名稱和值放到Request的屬性表中。 handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest); return arg; }
上面代碼首先獲取參數的@PathVariable的value屬性值,若是value是空,則將參數的名稱做爲NameValueInfo的name值,而後用這個name值匹配請求路徑中的變量值,做爲NameValueInfo的value值。
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request){ Map<String, String> uriTemplateVars =//從請求的屬性表中獲取值 (Map<String, String>) request.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); return (uriTemplateVars != null) ? uriTemplateVars.get(name) : null; }
上面再lookupHandlerMethod方法中,調用了handleMatch方法,咱們說了它會將解析到的路徑變量放到request的變量表中,這不,這裏就用到了,這樣咱們就獲取到了@PathVariable對應參數的值了。到此爲止,@PathVariable參數的解析就算完成了,其餘類型的參數解析思路同樣不一樣的就是resolveArgument方法中的邏輯了,你們能夠自行了解。
注:這裏Spring用到了多種設計模式,包括組合模式,策略模式,適配器模式等。其實這些實現都是3.1v的,以前的版本,參數解析這塊至關亂,幾乎徹底在一個方法內實現的,拓展性,維護性至關差,3.1後咱們能夠很輕鬆的實現本身的參數解析器等,真的很棒。
真的很累了。。。。。。
上一節咱們分析了參數的解析,及方法的調用,下面咱們再來看返回值的處理,回到HandlerMethod的invokeAndHandle方法來,該方法的最後調用了returnValueHandlers.handleReturenValue方法,其中returnValueHandlers是HandlerMethodReturnValueHandlerComposite實例,就像HandlermethodArgumentResolverComposite同樣,它包含了全部HandlerMethodReturnValueHandler的列表,並在Spring啓動時完成註冊。
public void handleReturnValue( Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType); handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); } private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) { for (HandlerMethodReturnValueHandler returnValueHandler : returnValueHandlers) { if (returnValueHandler.supportsReturnType(returnType)) { return returnValueHandler; } } return null; } //這個是處理String類型的返回值,即將返回值解析爲視圖名------ViewNameMethodReturnValueHandler public void handleReturnValue( Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue == null) { return; } else if (returnValue instanceof String) { String viewName = (String) returnValue; //將返回值存儲爲視圖名 mavContainer.setViewName(viewName); if (isRedirectViewName(viewName)) { mavContainer.setRedirectModelScenario(true); } } else { throw new UnsupportedOperationException("Unexpected return type: "); } }
返回值的處理思路與參數的處理幾乎同樣了,根據不一樣的返回值類型,查找匹配的處理器,而後進行處理(主要就是設置Model和View了,如上面的代碼將返回值解析爲視圖名),這裏就很少說了。返回值處理完了,剩下的就是將返回值響應給客戶端了,再往下就是視圖的解析了,也就是咱們下篇文章的主題了。
歡迎你們評論,交流。