SpringCloud請求響應資料轉換(二)
上篇文章記錄了從後端介面返回資料經過切面和訊息轉換器處理後返回給前端的過程。接下來,記錄從請求發出後到後端介面呼叫過的過程。
web請求處理流程
原始碼分析
ApplicationFilterChain會調DispatcherServlet類的doService()(HttpServlet類),類繼承關係如下:
最終會調DispatcherServlet類的doDispatch方法,並由該方法控制web請求的全過程,包括確定請求方法、確定請求處理介面卡和請求實際呼叫和資料處理,程式碼如下:
1 /** 2* Process the actual dispatching to the handler. 3* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.按序遍歷並確定HadlerMapping 4* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters 5* to find the first that supports the handler class.找到第一個支援處理類的HandlerAdapters 6* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers 7* themselves to decide which methods are acceptable.所有HTTP請求都由該方法處理,然後由具體的HandlerAdapter和處理類確定呼叫方法 8* @param request current HTTP request 9* @param response current HTTP response 10* @throws Exception in case of any kind of processing failure 11*/ 12protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { 13HttpServletRequest processedRequest = request; 14HandlerExecutionChain mappedHandler = null; 15boolean multipartRequestParsed = false; 16 17WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); 18 19try { 20ModelAndView mv = null; 21Exception dispatchException = null; 22 23try { 24processedRequest = checkMultipart(request); 25multipartRequestParsed = (processedRequest != request); 26//1、獲取HandlerMethod 27// Determine handler for the current request. 28mappedHandler = getHandler(processedRequest); 29if (mappedHandler == null || mappedHandler.getHandler() == null) { 30noHandlerFound(processedRequest, response); 31return; 32} 33//2、確定介面卡 34// Determine handler adapter for the current request. 35HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 36 37// Process last-modified header, if supported by the handler.判斷是否支援If-Modified-Since 38String method = request.getMethod(); 39boolean isGet = "GET".equals(method); 40if (isGet || "HEAD".equals(method)) { 41long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); 42if (logger.isDebugEnabled()) { 43logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); 44} 45if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { 46return; 47} 48} 49 50if (!mappedHandler.applyPreHandle(processedRequest, response)) { 51return; 52} 53//3、請求實際處理,包括請求引數的處理、後臺介面的呼叫和返回資料的處理 54// Actually invoke the handler. 55mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 56 57if (asyncManager.isConcurrentHandlingStarted()) { 58return; 59} 60 61applyDefaultViewName(processedRequest, mv); 62mappedHandler.applyPostHandle(processedRequest, response, mv); 63} 64catch (Exception ex) { 65dispatchException = ex; 66} 67catch (Throwable err) { 68// As of 4.3, we're processing Errors thrown from handler methods as well, 69// making them available for @ExceptionHandler methods and other scenarios. 70dispatchException = new NestedServletException("Handler dispatch failed", err); 71} 72processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 73} 74catch (Exception ex) { 75triggerAfterCompletion(processedRequest, response, mappedHandler, ex); 76} 77catch (Throwable err) { 78triggerAfterCompletion(processedRequest, response, mappedHandler, 79new NestedServletException("Handler processing failed", err)); 80} 81finally { 82if (asyncManager.isConcurrentHandlingStarted()) { 83// Instead of postHandle and afterCompletion 84if (mappedHandler != null) { 85mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); 86} 87} 88else { 89// Clean up any resources used by a multipart request. 90if (multipartRequestParsed) { 91cleanupMultipart(processedRequest); 92} 93} 94} 95}
1、獲取HandlerMethod
首先是DispatcherServlet的 getHandler方法, 獲取處理器鏈,所有處理器(HandlerMapping)都註冊在handlerMappings中,如下圖所示。
1 /** 2* Return the HandlerExecutionChain for this request.返回處理器鏈,處理該請求 3* <p>Tries all handler mappings in order. 4* @param request current HTTP request 5* @return the HandlerExecutionChain, or {@code null} if no handler could be found 6*/ 7protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { 8 //遍歷所有處理器,如下圖 9for (HandlerMapping hm : this.handlerMappings) { 10if (logger.isTraceEnabled()) { 11logger.trace( 12"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); 13} 14HandlerExecutionChain handler = hm.getHandler(request); 15if (handler != null) { 16return handler; 17} 18} 19return null; 20}
然後,從前往後遍歷所有HandlerMapping,直到handler不為空(14-17行)。
對GET請求,確定HandlerMapping為RequestMappingHandlerMapping(繼承自AbstractHandlerMapping),其 getHandler方法如下:
1 /** 2* Look up a handler for the given request, falling back to the default 3* handler if no specific one is found. 4* @param request current HTTP request 5* @return the corresponding handler instance, or the default handler 6* @see #getHandlerInternal 7*/ 8@Override 9public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { 10 //獲取實際處理方法,如public java.util.List<com.service.entity.TaskVO> com.service.controller.TaskController.getTaskListById(java.lang.String),具體獲取方式,見下邊 獲取HandlerMethod 11Object handler = getHandlerInternal(request); 12if (handler == null) { 13handler = getDefaultHandler(); 14} 15if (handler == null) { 16return null; 17} 18// Bean name or resolved handler? 19if (handler instanceof String) { 20String handlerName = (String) handler; 21handler = getApplicationContext().getBean(handlerName); 22} 23 //獲取處理器執行鏈條,包含攔截器等,如下圖 24HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); 25if (CorsUtils.isCorsRequest(request)) { 26CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request); 27CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); 28CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); 29executionChain = getCorsHandlerExecutionChain(request, executionChain, config); 30} 31return executionChain; 32}
獲取HandlerMethod
上邊getHandlerInternal方法會調AbstractHandlerMethodMapping類的getHandlerInternal,如下:
1 /** 2* Look up a handler method for the given request. 3*/ 4@Override 5protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { 6 //獲取請求路徑,如/tasks 7String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); 8if (logger.isDebugEnabled()) { 9logger.debug("Looking up handler method for path " + lookupPath); 10} 11this.mappingRegistry.acquireReadLock(); 12try { 13 //①獲取請求處理方法HandlerMethod 14HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); 15if (logger.isDebugEnabled()) { 16if (handlerMethod != null) { 17logger.debug("Returning handler method [" + handlerMethod + "]"); 18} 19else { 20logger.debug("Did not find handler method for [" + lookupPath + "]"); 21} 22} 23 //②根據HandlerMethod解析容器中對應的bean(控制層bean) 24return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); 25} 26finally { 27this.mappingRegistry.releaseReadLock(); 28} 29}
①獲取請求處理方法HandlerMethod
HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request)方法,根據uri尋找與之匹配的HandlerMethod
1 /** 2* Look up the best-matching handler method for the current request. 3* If multiple matches are found, the best match is selected. 4* @param lookupPath mapping lookup path within the current servlet mapping 5* @param request the current request 6* @return the best-matching handler method, or {@code null} if no match 7* @see #handleMatch(Object, String, HttpServletRequest) 8* @see #handleNoMatch(Set, String, HttpServletRequest) 9*/ 10protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { 11List<Match> matches = new ArrayList<Match>(); 12 //lookupPath=/tasks,獲取與請求uri匹配的介面資訊,如 [{[/tasks],methods=[POST],produces=[application/json;charset=UTF-8]}, {[/tasks],methods=[GET],produces=[application/json;charset=UTF-8]}],其中MappingRegistry mappingRegistry包含了系統所有uri和介面資訊。 13List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); 14 //遍歷得到的uri,根據請求資訊,如GET方法等,選擇匹配的uri({[/tasks],methods=[GET],produces=[application/json;charset=UTF-8]}),在mappingRegistry中獲取匹配的HandlerMethod,包含後臺介面詳細資訊,如下圖。 15if (directPathMatches != null) { 16addMatchingMappings(directPathMatches, matches, request); 17} 18if (matches.isEmpty()) { 19// No choice but to go through all mappings... 20addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); 21} 22 23if (!matches.isEmpty()) { 24 //對所有匹配的介面進行排序,並使用第一個(排序規則後續再研究) 25Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); 26Collections.sort(matches, comparator); 27if (logger.isTraceEnabled()) { 28logger.trace("Found " + matches.size() + " matching mapping(s) for [" + 29lookupPath + "] : " + matches); 30} 31Match bestMatch = matches.get(0); 32if (matches.size() > 1) { 33if (CorsUtils.isPreFlightRequest(request)) { 34return PREFLIGHT_AMBIGUOUS_MATCH; 35} 36Match secondBestMatch = matches.get(1); 37if (comparator.compare(bestMatch, secondBestMatch) == 0) { 38Method m1 = bestMatch.handlerMethod.getMethod(); 39Method m2 = secondBestMatch.handlerMethod.getMethod(); 40throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" + 41request.getRequestURL() + "': {" + m1 + ", " + m2 + "}"); 42} 43} 44handleMatch(bestMatch.mapping, lookupPath, request); 45return bestMatch.handlerMethod; 46} 47else { 48return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); 49} 50}
其中,第13行為根據lookupPath(/tasks)獲取介面資訊,第16行根據介面資訊獲取後臺介面和bean等資訊,所有這些資訊都儲存在內部類MappingRegistry物件中。並且中間會構建一個Match物件,包含所有匹配的介面,並選擇第一個作為實際處理介面。MappingRegistry內部類如下所示:
1 /** 2* A registry that maintains all mappings to handler methods, exposing methods 3* to perform lookups and providing concurrent access. 4* 5* <p>Package-private for testing purposes. 6*/ 7class MappingRegistry { 8 //控制層uri介面資訊註冊 9private final Map<T, MappingRegistration<T>> registry = new HashMap<T, MappingRegistration<T>>(); 10 //儲存uri介面資訊和HandlerMethod,如{{[/tasks],methods=[POST],produces=[application/json;charset=UTF-8]}=public com.service.entity.TaskVO com.service.controller.TaskController.addTask(java.lang.String) throws com.service.exception.BizException, {[/tasks],methods=[GET],produces=[application/json;charset=UTF-8]}=public java.util.List<com.service.entity.TaskVO> com.service.controller.TaskController.getTaskListById(java.lang.String)} 11private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<T, HandlerMethod>(); 12 //儲存uri和uri介面資訊(一對多關係),如:{/tasks=[{[/tasks],methods=[POST],produces=[application/json;charset=UTF-8]}, {[/tasks],methods=[GET],produces=[application/json;charset=UTF-8]}]}private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<String, T>(); 13 14private final Map<String, List<HandlerMethod>> nameLookup = 15new ConcurrentHashMap<String, List<HandlerMethod>>(); 16 17private final Map<HandlerMethod, CorsConfiguration> corsLookup = 18new ConcurrentHashMap<HandlerMethod, CorsConfiguration>(); 19 20private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); 21 22/** 23* Return all mappings and handler methods. Not thread-safe. 24* @see #acquireReadLock() 25*/ 26public Map<T, HandlerMethod> getMappings() { 27return this.mappingLookup; 28} 29 30/** 31* Return matches for the given URL path. Not thread-safe. 32* @see #acquireReadLock() 33*/ 34public List<T> getMappingsByUrl(String urlPath) { 35return this.urlLookup.get(urlPath); 36} 37 ........... 38 }
②根據HandlerMethod解析容器中對應的bean(控制層bean)
根據上一步得到HandlerMethod,其中bean為bean的名字,將其替換成容器中的bean(控制層對應的bean),調HandlerMethod的 createWithResolvedBean方法,如下:
1 /** 2* If the provided instance contains a bean name rather than an object instance, 3* the bean name is resolved before a {@link HandlerMethod} is created and returned. 4*/ 5public HandlerMethod createWithResolvedBean() { 6Object handler = this.bean; 7if (this.bean instanceof String) { 8String beanName = (String) this.bean; 9handler = this.beanFactory.getBean(beanName); 10} 11return new HandlerMethod(this, handler); 12}
其中handler為控制層對應的bean,如下圖:
最後,重新構建HandlerMethod,用真實的bean替換掉原來的bean名。
另外,上邊涉及的HandlerMapping的類結構如下:
2、確定介面卡
存在3種介面卡,儲存在handlerAdapters中,如下圖。
DispatcherServlet方法getHandlerAdapter,根據上一步獲取到的處理器HandlerMethod,確定匹配的介面卡,程式碼如下:
/** * Return the HandlerAdapter for this handler object. * @param handler the handler object to find an adapter for * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error. */ protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { //遍歷所有介面卡,如下圖。其中handler值為public java.util.List<com.service.entity.TaskVO> com.service.controller.TaskController.getTaskListById(java.lang.String) ,判斷介面卡是否支援該介面,在本例中RequestMappingHandlerAdapter支援 for (HandlerAdapter ha : this.handlerAdapters) { if (logger.isTraceEnabled()) { logger.trace("Testing handler adapter [" + ha + "]"); } //判斷是否支援,程式碼見下邊 if (ha.supports(handler)) { return ha; } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }
在GET請求中,由於使用註解@RequestMapping,獲取到介面卡為:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter(繼承自AbstractHandlerMethodAdapter),support方法如下:
AbstractHandlerMethodAdapter 1 /** 2* This implementation expects the handler to be an {@link HandlerMethod}. 3* @param handler the handler instance to check 4* @return whether or not this adapter can adapt the given handler 5*/ 6@Override 7public final boolean supports(Object handler) { 8return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler)); 9}
RequestMappingHandlerAdapter的supportsInternal方法總返回true,因為介面方法引數和返回值可能存在其他的處理,引數可由HandlerMethodArgumentResolver處理(見後續文章),返回值可由HandlerMethodReturnValueHandler處理(見上篇)
/** * Always return {@code true} since any method argument and return value * type will be processed in some way. A method argument not recognized * by any HandlerMethodArgumentResolver is interpreted as a request parameter * if it is a simple type, or as a model attribute otherwise. A return value * not recognized by any HandlerMethodReturnValueHandler will be interpreted * as a model attribute. */ @Override protected boolean supportsInternal(HandlerMethod handlerMethod) { return true; }
最終介面卡返回結果如下:
3、請求實際處理,包括請求引數的處理、後臺介面的呼叫和返回資料的處理
調RequestMappingHandlerAdapter的handle方法,對請求進行處理,會調 ServletInvocableHandlerMethod的invokeAndHandle方法,其控制整個請求和響應返回的過程。
1 /** 2* Invokes the method and handles the return value through one of the 3* configured {@link HandlerMethodReturnValueHandler}s. 4* @param webRequest the current request 5* @param mavContainer the ModelAndViewContainer for this request 6* @param providedArgs "given" arguments matched by type (not resolved) 7*/ 8public void invokeAndHandle(ServletWebRequest webRequest, 9ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { 10 //①請求對應的方法,底層採用反射的方式(通過HandleMethod獲取控制層的方法和bean,實現反射。第一步已獲取到HandleMethod) 11Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); 12setResponseStatus(webRequest); 13 14if (returnValue == null) { 15if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) { 16mavContainer.setRequestHandled(true); 17return; 18} 19} 20else if (StringUtils.hasText(this.responseReason)) { 21mavContainer.setRequestHandled(true); 22return; 23} 24 25mavContainer.setRequestHandled(false); 26try { 27 //②對方法返回的資料,進行處理,包括切面處理和資料轉換(如json) 28this.returnValueHandlers.handleReturnValue( 29returnValue, getReturnValueType(returnValue), mavContainer, webRequest); 30} 31catch (Exception ex) { 32if (logger.isTraceEnabled()) { 33logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex); 34} 35throw ex; 36} 37}
①請求對應的方法,底層採用反射的方式
解析請求引數,調後臺介面,返回結果資料,程式碼如下:
1 /** 2* Invoke the method after resolving its argument values in the context of the given request. 3* <p>Argument values are commonly resolved through {@link HandlerMethodArgumentResolver}s. 4* The {@code providedArgs} parameter however may supply argument values to be used directly, 5* i.e. without argument resolution. Examples of provided argument values include a 6* {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance. 7* Provided argument values are checked before argument resolvers. 8* @param request the current request 9* @param mavContainer the ModelAndViewContainer for this request 10* @param providedArgs "given" arguments matched by type, not resolved 11* @return the raw value returned by the invoked method 12* @exception Exception raised if no suitable argument resolver can be found, 13* or if the method raised an exception 14*/ 15public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, 16Object... providedArgs) throws Exception { 17 //獲取並處理請求引數 18Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); 19if (logger.isTraceEnabled()) { 20StringBuilder sb = new StringBuilder("Invoking ["); 21sb.append(getBeanType().getSimpleName()).append("."); 22sb.append(getMethod().getName()).append("] method with arguments "); 23sb.append(Arrays.asList(args)); 24logger.trace(sb.toString()); 25} 26 //反射呼叫HandlerMethod中bean對應的介面 27Object returnValue = doInvoke(args); 28if (logger.isTraceEnabled()) { 29logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]"); 30} 31return returnValue; 32}
②對方法返回的資料,進行處理,包括切面處理和資料轉換
參見上篇文章 ofollow,noindex" target="_blank">SpringCloud請求響應資料轉換(一)
請求過程涉及的類