RequestMappingHandlerAdapter和RequestParam原理分析
我們要使用定義了RequestMapping方法或者類是,需要先準備好所需要的引數。如何準備引數,我們應該考慮些上面問題。
都有哪些引數需要繫結?
除了方法確定的引數,還有兩個方法的引數需要繫結,那就是當前處理器相對應註釋了@ModelAttribute和註釋了@InitBinder的方法。
引數的值的來源?
有六個引數的來源:
- request中相關的引數,主要包括url中的引數、post中過來body中的引數,以及請求頭包含的值;
- cookie中的引數
- session中給的引數
- 設定到FlashMap中的引數,這種引數用於redirect的引數傳遞
- SessionAttribute傳遞的引數,這類引數通過@SessionAttribute註釋傳遞
- 通過相應的註釋了@ModelAttribute的方法設定的引數
具體進行繫結的方法?
引數解析使用HandlerMethodArgumentResolver型別的元件完成的,不同型別的使用不同的ArgumentResolver來解析。有的Resolver內部使用了WebDataBinder,可以通過註釋了@InitBinder的方法來初始化,註釋了@InitBinder的方法也需要繫結引數,而且也是不確定的,所以@InitBinder註釋的方法也需要ArgumentResolver來解析引數,使用的和Handler不同的一套ArgumentResolver,另外註釋了ModelAttribute的方法也需要繫結引數,使用的和Handler使用的是同一套ArgumentResolver。
1.RequestMappingHandlerAdapter
此類的主要功能:
- 備好處理器所需要的引數(這個最難,引數的不確定性)
- 使用處理器處理請求 (這個比較簡單,直接用反射呼叫handleMethod處理就可以了)
- 處理返回值,也就是將不同型別的返回值統一處理成ModelAndView類
通過xml解析初始化過程如RequestMapping原理分析和RequestMappingHandlerMapping一般,重複部分就不在分析。
1.1RequestMappingHandlerAdapter繼承圖
上圖資訊比較多,我們查詢關鍵資訊。可以看到這個類間接實現了HandlerAdapter介面,是HandlerAdapter型別的例項。
除此之外還實現了ApplicationContextAware和IntitalzingBean 這兩個介面。
1.2RequestMappingHandlerAdapter類原始碼分析
既然RequestMappingHandlerAdapter實現了ApplicationContextAware介面,那例項化時候肯定會執行setApplicationContext方法,我們檢視其實現邏輯。
@Override public final void setApplicationContext(ApplicationContext context) throws BeansException { //isContextRequired()方法返回tru if (context == null && !isContextRequired()) { // Reset internal context state. this.applicationContext = null; this.messageSourceAccessor = null; } else if (this.applicationContext == null) { // Initialize with passed-in context. //所傳入的context如果不能被例項化,則丟擲異常 if (!requiredContextClass().isInstance(context)) { throw new ApplicationContextException( "Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]"); } this.applicationContext = context; //國際化 this.messageSourceAccessor = new MessageSourceAccessor(context); //初始化ApplicationContext initApplicationContext(context); } else { // Ignore reinitialization if same context passed in. if (this.applicationContext != context) { throw new ApplicationContextException( "Cannot reinitialize with different application context: current one is [" + this.applicationContext + "], passed-in one is [" + context + "]"); } } }
initApplicationContext裡面主要就是初始化ServletContext,就不在去分析了
RequestMappingHandlerAdapter也實現了InitializingBean介面,當設定完屬性後肯定會回撥afterPropertiesSet方法,我們重點分析一下afterPropertiesSet的方法,它的原始碼內容如下,下面我們一點一點的分析這個類:
@Override public void afterPropertiesSet() { // Do this first, it may add ResponseBody advice beans //初始化註釋了@ControllerAdvice的類,分別用於快取@ControllerAdvice註釋的類裡面註釋了@ModelAttribute和@InitBinder方法,也就是全域性的@ModelAttribute和InitBinder方法。 initControllerAdviceCache(); if (this.argumentResolvers == null) { //初始化argumentResolvers,用於處理器方法和註釋了@ModelAttribute的方法設定引數 List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.initBinderArgumentResolvers == null) { //初始化initBinderArgumentResolvers,用於給註釋了@initBinder的方法設定引數,使用得較少 List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers(); this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.returnValueHandlers == null) { //初始化returnValueHandlers,用於將處理器的返回值處理為ModelAndView的型別 List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } }
我們先看initControllerAdviceCache這個方法:
private void initControllerAdviceCache() { if (getApplicationContext() == null) { return; } if (logger.isInfoEnabled()) { logger.info("Looking for @ControllerAdvice: " + getApplicationContext()); } //獲取到所有註釋了@ControllerAdvice的bean List<ControllerAdviceBean> beans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); //對獲取到的ControllerAdvice註解的類進行排序,排序的規則是基於實現PriorityOrdered介面或者帶有Order註解 AnnotationAwareOrderComparator.sort(beans); List<Object> requestResponseBodyAdviceBeans = new ArrayList<Object>(); for (ControllerAdviceBean bean : beans) { //查詢註釋了@ModelAttribute而且沒有註釋@RequestMapping的方法 Set<Method> attrMethods = MethodIntrospector.selectMethods(bean.getBeanType(), MODEL_ATTRIBUTE_METHODS); if (!attrMethods.isEmpty()) { this.modelAttributeAdviceCache.put(bean, attrMethods); if (logger.isInfoEnabled()) { logger.info("Detected @ModelAttribute methods in " + bean); } } //獲取所有帶InitBinder註解的方法 Set<Method> binderMethods = MethodIntrospector.selectMethods(bean.getBeanType(), INIT_BINDER_METHODS); if (!binderMethods.isEmpty()) { this.initBinderAdviceCache.put(bean, binderMethods); if (logger.isInfoEnabled()) { logger.info("Detected @InitBinder methods in " + bean); } } //如果實現了RequestBodyAdvice介面 if (RequestBodyAdvice.class.isAssignableFrom(bean.getBeanType())) { requestResponseBodyAdviceBeans.add(bean); if (logger.isInfoEnabled()) { logger.info("Detected RequestBodyAdvice bean in " + bean); } } //如果實現了ResponseBodyAdvice介面 if (ResponseBodyAdvice.class.isAssignableFrom(bean.getBeanType())) { requestResponseBodyAdviceBeans.add(bean); if (logger.isInfoEnabled()) { logger.info("Detected ResponseBodyAdvice bean in " + bean); } } } // 將實現了RequestBodyAdvice和ResponseBodyAdvice介面的類放入requestResponseBodyAdviceBeans // 這裡是放入頂部,說明通過@@ControllerAdvice註解實現介面的處理優先順序最高 if (!requestResponseBodyAdviceBeans.isEmpty()) { this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans); } }
看getDefaultArgumentResolvers主要作用是解析傳入的引數:
// 獲取預設的 HandlerMethodArgumentResolver private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>(); // 1.基於註解的引數解析 <-- 解析的資料來源主要是 HttpServletRequest | ModelAndViewContainer // Annotation-based argument resolution // 解析被註解 @RequestParam, @RequestPart 修飾的引數, 資料的獲取通過 HttpServletRequest.getParameterValues resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); // 解析被註解 @RequestParam 修飾, 且型別是 Map 的引數, 資料的獲取通過 HttpServletRequest.getParameterMap resolvers.add(new RequestParamMapMethodArgumentResolver()); // 解析被註解 @PathVariable 修飾, 資料的獲取通過 uriTemplateVars, 而 uriTemplateVars 卻是通過 RequestMappingInfoHandlerMapping.handleMatch 生成, 其實就是 uri 中映射出的 key <-> value resolvers.add(new PathVariableMethodArgumentResolver()); // 解析被註解 @PathVariable 修飾 且資料型別是 Map, 資料的獲取通過 uriTemplateVars, 而 uriTemplateVars 卻是通過 RequestMappingInfoHandlerMapping.handleMatch 生成, 其實就是 uri 中映射出的 key <-> value resolvers.add(new PathVariableMapMethodArgumentResolver()); // 解析被註解 @MatrixVariable 修飾, 資料的獲取通過 URI提取了;後儲存的 uri template 變數值 resolvers.add(new MatrixVariableMethodArgumentResolver()); // 解析被註解 @MatrixVariable 修飾 且資料型別是 Map, 資料的獲取通過 URI提取了;後儲存的 uri template 變數值 resolvers.add(new MatrixVariableMapMethodArgumentResolver()); // 解析被註解 @ModelAttribute 修飾, 且型別是 Map 的引數, 資料的獲取通過 ModelAndViewContainer 獲取, 通過 DataBinder 進行繫結 resolvers.add(new ServletModelAttributeMethodProcessor(false)); // 解析被註解 @RequestBody 修飾的引數, 以及被@ResponseBody修飾的返回值, 資料的獲取通過 HttpServletRequest 獲取, 根據 MediaType通過HttpMessageConverter轉換成對應的格式, 在處理返回值時 也是通過 MediaType 選擇合適HttpMessageConverter, 進行轉換格式, 並輸出 resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); // 解析被註解 @RequestPart 修飾, 資料的獲取通過 HttpServletRequest.getParts() resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice)); // 解析被註解 @RequestHeader 修飾, 資料的獲取通過 HttpServletRequest.getHeaderValues() resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory())); // 解析被註解 @RequestHeader 修飾且引數型別是 Map, 資料的獲取通過 HttpServletRequest.getHeaderValues() resolvers.add(new RequestHeaderMapMethodArgumentResolver()); // 解析被註解 @CookieValue 修飾, 資料的獲取通過 HttpServletRequest.getCookies() resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory())); // 解析被註解 @Value 修飾, 資料在這裡沒有解析 resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); // 解析被註解 @SessionAttribute 修飾, 資料的獲取通過 HttpServletRequest.getAttribute(name, RequestAttributes.SCOPE_SESSION) resolvers.add(new SessionAttributeMethodArgumentResolver()); // 解析被註解 @RequestAttribute 修飾, 資料的獲取通過 HttpServletRequest.getAttribute(name, RequestAttributes.SCOPE_REQUEST) resolvers.add(new RequestAttributeMethodArgumentResolver()); // 2.基於型別的引數解析器 // Type-based argument resolution // 解析固定型別引數(比如: ServletRequest, HttpSession, InputStream 等), 引數的資料獲取還是通過 HttpServletRequest resolvers.add(new ServletRequestMethodArgumentResolver()); // 解析固定型別引數(比如: ServletResponse, OutputStream等), 引數的資料獲取還是通過 HttpServletResponse resolvers.add(new ServletResponseMethodArgumentResolver()); // 解析固定型別引數(比如: HttpEntity, RequestEntity 等), 引數的資料獲取還是通過 HttpServletRequest resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); // 解析固定型別引數(比如: RedirectAttributes), 引數的資料獲取還是通過 HttpServletResponse resolvers.add(new RedirectAttributesMethodArgumentResolver()); // 解析固定型別引數(比如: Model等), 引數的資料獲取通過 ModelAndViewContainer resolvers.add(new ModelMethodProcessor()); // 解析固定型別引數(比如: Model等), 引數的資料獲取通過 ModelAndViewContainer resolvers.add(new MapMethodProcessor()); // 解析固定型別引數(比如: Errors), 引數的資料獲取通過 ModelAndViewContainer resolvers.add(new ErrorsMethodArgumentResolver()); // 解析固定型別引數(比如: SessionStatus), 引數的資料獲取通過 ModelAndViewContainer resolvers.add(new SessionStatusMethodArgumentResolver()); // 解析固定型別引數(比如: UriComponentsBuilder), 引數的資料獲取通過 HttpServletRequest resolvers.add(new UriComponentsBuilderMethodArgumentResolver()); // 3.自定義引數解析器 // Custom arguments if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } // Catch-all //這兩個解析器可以解析所有型別的引數 resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); resolvers.add(new ServletModelAttributeMethodProcessor(true)); return resolvers; }
getDefaultArgumentResolvers中有四類解析器:
- 基於註解的引數解析器
- 基於型別的引數解析器
- 自定義引數解析器
- 可解析所有型別的解析器
從原始碼中可以看出,自定義的解析器是在前面兩種都無法解析是才會使用到,這個順序是無法改變的,例如如果想自己寫一個解析器來解析@PathVariable註釋的PathVariable引數,是無法實現的,即使寫出來並註冊到RequestMappingHanderAdapter中也不會被呼叫
getDefaultInitBinderArgumentResolvers用得較少,原理也是類似的,就不分析了。
分析getDefaultReturnValueHandlers方法:
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() { List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>(); // Single-purpose return value types // 支援 ModelAndView 型別的 HandlerMethodReturnValueHandler, 最後將資料寫入 ModelAndViewContainer handlers.add(new ModelAndViewMethodReturnValueHandler()); // 支援 Map 型別的 HandlerMethodReturnValueHandler, 最後將資料寫入 ModelAndViewContainer handlers.add(new ModelMethodProcessor()); // 支援 View 型別的 HandlerMethodReturnValueHandler, 最後將資料寫入 ModelAndViewContainer handlers.add(new ViewMethodReturnValueHandler()); // 支援 ResponseEntity 型別的 HandlerMethodReturnValueHandler, 最後將資料寫入 HttpServletResponse 的資料流中 OutputStream handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters())); // 支援 ResponseEntity | StreamingResponseBody 型別的 HandlerMethodReturnValueHandler, 最後將資料寫入 HttpServletResponse 的資料流中 OutputStream handlers.add(new StreamingResponseBodyReturnValueHandler()); // 支援 HttpEntity | !RequestEntity 型別的 HandlerMethodReturnValueHandler, 最後將資料寫入 HttpServletResponse 的資料流中 OutputStream handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),this.contentNegotiationManager, this.requestResponseBodyAdvice)); // 支援 HttpHeaders 型別的 HandlerMethodReturnValueHandler, 最後將資料寫入 HttpServletResponse 的資料頭部 handlers.add(new HttpHeadersReturnValueHandler()); // 支援 Callable 型別的 HandlerMethodReturnValueHandler handlers.add(new CallableMethodReturnValueHandler()); handlers.add(new DeferredResultMethodReturnValueHandler()); // 支援 WebAsyncTask 型別的 HandlerMethodReturnValueHandler handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory)); // Annotation-based return value types // 將資料加入 ModelAndViewContainer 的 HandlerMethodReturnValueHandler handlers.add(new ModelAttributeMethodProcessor(false)); // 返回值被 ResponseBody 修飾的返回值, 並且根據 MediaType 通過 HttpMessageConverter 轉化後進行寫入資料流中 handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),this.contentNegotiationManager, this.requestResponseBodyAdvice)); // Multi-purpose return value types // 支援返回值為 CharSequence 型別, 設定 ModelAndViewContainer.setViewName handlers.add(new ViewNameMethodReturnValueHandler()); // 支援返回值為 Map, 並將結果設定到 ModelAndViewContainer handlers.add(new MapMethodProcessor()); // Custom return value types if (getCustomReturnValueHandlers() != null) { handlers.addAll(getCustomReturnValueHandlers()); } // Catch-all if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) { handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers())); } else { handlers.add(new ModelAttributeMethodProcessor(true)); } return handlers; }
在這裡就初始化完成了,有了這些工具類後我們就可以對收到請求的引數和處理後返回不同的型別進行處理了。
DispatchServlet繼承自Servlet,那所有的請求都會在service()方法中進行處理。最後在DispatcherServlet#doDispatch中處理請求後。根據不同的請求會找到對應的HandlerMapping,然後在找到HandlerAdapter進行處理。
如果處理的HandlerAdapter是RequestMappingHandlerAdapter,最後會走到RequestMappingHandlerAdapter#handleInternal:
@Override protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ...... mav = invokeHandlerMethod(request, response, handlerMethod); ...... }
進入invokeHandlerMethod:
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { // 構建 ServletWebRequest <-- 主要由 HttpServletRequest, HttpServletResponse ServletWebRequest webRequest = new ServletWebRequest(request, response); try { // 構建 DataBinder 工廠 WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); // binderFactory 中儲存著被 @InitBinder, @ModelAttribute 修飾的方法 <- 最終包裹成 InvocableHandlerMethod ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); // 構建一個 ServletInvocableHandlerMethod ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); // 設定方法引數解析器 HandlerMethodArgumentValueResolver invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); // 返回值處理器 HandlerMethodReturnValueHandler invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); // 設定 WebDataBinderFactory invocableMethod.setDataBinderFactory(binderFactory); // 設定 引數名解析器 invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); // 獲取 HttpServletRequest 中儲存的 FlashMap mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); // 這裡是啟用 @ModelAttribute, @InitBinder 方法, 並將返回值放入 ModelAndViewContainer modelFactory.initModel(webRequest, mavContainer, invocableMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); // 將 HttpServletRequest 轉換成方法的引數, 啟用方法, 最後 通過 HandlerMethodReturnValueHandler 來處理返回值 invocableMethod.invokeAndHandle(webRequest, mavContainer); // 生成 ModelAndView return getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); // 標誌請求已經結束, 進行一些生命週期回撥函式的啟用 } }
在上面的程式碼中我們建立了一個ServletInvocableHandlerMethod物件,在這個物件中設定了引數解析器、返回值處理器、資料校驗工廠類等。接著我們進入到invocableMethod.invokeAndHandle這個方法中看一下:
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); ...... }
在invokeForRequest這個方法中,主要乾了兩件事,一是解析請求引數,二是呼叫Controller中的請求方法。這裡我們主要關注的是引數解析的部分:
public Object invokeForRequest(NativeWebRequest request, 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)); } //呼叫Controller中的請求方法 Object returnValue = doInvoke(args); if (logger.isTraceEnabled()) { logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + "] returned [" + returnValue + "]"); } return returnValue; }
進入getMethodArgumentValues:
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //這裡的 getMethodParameters 其實是在構造 MethodParameters 時建立的 MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; //從這裡開始對引數進行一個一個解析 <- 主要是通過 HandlerMethodArgumentResolver 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 { //通過 argumentResolvers 解析 HandlerMethod 裡面對應的引數內容 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.getMethod().toGenericString() + ": " + getArgumentResolutionErrorMessage("No suitable resolver for", i)); } } return args; }
這裡需要說的是argumentResolvers這個物件是HandlerMethodArgumentResolverComposite這個類。所有引數的解析都是委託這個類來完成的,這個類會呼叫真正的請求引數的解析的類:
@Override public boolean supportsParameter(MethodParameter parameter) { return (getArgumentResolver(parameter) != null); }
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; }
下一篇分析具體從URL引數中傳的值,如何繫結到對應controller引數上面。
&n