說在前面java
本次主要介紹DispatcherServlet組件,更多精彩請關注」天河聊架構「微信公衆號。ios
springmvc配置解析web
進入到這個方法org.springframework.web.servlet.FrameworkServlet#servicespring
@Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpMethod httpMethod = HttpMethod.resolve(request.getMethod()); if (httpMethod == HttpMethod.PATCH || httpMethod == null) { // 執行request請求 processRequest(request, response); } else { super.service(request, response); } }
進入到這個方法org.springframework.web.servlet.FrameworkServlet#processRequest跨域
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null; LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); LocaleContext localeContext = buildLocaleContext(request); // 獲取請求綁定屬性 -》 RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); // 獲取異步請求管理器 -》 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); // 註冊callable返回值攔截器 asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); // 初始化上下文 initContextHolders(request, localeContext, requestAttributes); try { // 執行request請求模板方法 -》 doService(request, response); } catch (ServletException ex) { failureCause = ex; throw ex; } catch (IOException ex) { failureCause = ex; throw ex; } catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed", ex); } finally { resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { // -》 requestAttributes.requestCompleted(); } if (logger.isDebugEnabled()) { if (failureCause != null) { this.logger.debug("Could not complete request", failureCause); } else { if (asyncManager.isConcurrentHandlingStarted()) { logger.debug("Leaving response open for concurrent processing"); } else { this.logger.debug("Successfully completed request"); } } } // 發佈請求處理事件 -》 publishRequestHandledEvent(request, response, startTime, failureCause); } }
進入到這個方法org.springframework.web.context.request.RequestContextHolder#getRequestAttributes微信
public static RequestAttributes getRequestAttributes() { RequestAttributes attributes = requestAttributesHolder.get(); if (attributes == null) { attributes = inheritableRequestAttributesHolder.get(); } return attributes; }
往上返回到這個方法org.springframework.web.context.request.async.WebAsyncUtils#getAsyncManager(javax.servlet.ServletRequest)架構
public static WebAsyncManager getAsyncManager(ServletRequest servletRequest) { WebAsyncManager asyncManager = null; // 從request中獲取請求管理器 Object asyncManagerAttr = servletRequest.getAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE); if (asyncManagerAttr instanceof WebAsyncManager) { asyncManager = (WebAsyncManager) asyncManagerAttr; } // 若是沒有就建立後綁定到request中 if (asyncManager == null) { asyncManager = new WebAsyncManager(); servletRequest.setAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE, asyncManager); } return asyncManager; }
進入到這個方法org.springframework.web.servlet.DispatcherServlet#doServicemvc
@Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { if (logger.isDebugEnabled()) { String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : ""; logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]"); } // Keep a snapshot of the request attributes in case of an include,保存請求屬性的快照,以防包含, // to be able to restore the original attributes after the include.才能恢復包含後的原始屬性。 Map<String, Object> attributesSnapshot = null; //爲include請求保存了一個參數map快照 // 若是是include請求 if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<String, Object>(); // 獲取請求參數 Enumeration<?> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } // Make framework objects available to handlers and view objects.使框架對象對處理程序和視圖對象可用。 request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); try { // 請求轉發 -》 doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } } }
進入到這個方法org.springframework.web.servlet.DispatcherServlet#doDispatchapp
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; // 獲取異步請求管理器 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { // 是不是上傳文件請求 -》 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. 獲取請求的handler -》 mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { // 找不到handler報錯 -》 noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request.肯定當前請求的處理程序適配器。-》 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // 執行前置攔截器 -》 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler.執行handler -》 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // 若是是異步直接返回 if (asyncManager.isConcurrentHandlingStarted()) { return; } // 解析默認的視圖 applyDefaultViewName(processedRequest, mv); // 執行後置攔截器 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { // 異常執行攔截器的完成事件 triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
進入到這個方法org.springframework.web.servlet.DispatcherServlet#checkMultipartcors
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { // 判斷是不是上傳文件請求 -》 if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " + "this typically results from an additional MultipartFilter in web.xml"); } else if (hasMultipartException(request) ) { logger.debug("Multipart resolution failed for current request before - " + "skipping re-resolution for undisturbed error rendering"); } else { try { // 解析文件上傳 -》 return this.multipartResolver.resolveMultipart(request); } catch (MultipartException ex) { if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) { logger.debug("Multipart resolution failed for error dispatch", ex); // Keep processing error dispatch with regular request handle below } else { throw ex; } } } } // If not returned before: return original request. return request; }
進入到這個方法org.springframework.web.multipart.support.StandardServletMultipartResolver#isMultipart
@Override public boolean isMultipart(HttpServletRequest request) { // Same check as in Commons FileUpload... 若是不是post請求直接返回 if (!"post".equalsIgnoreCase(request.getMethod())) { return false; } // 請求資源類型中是否包含multipart/ String contentType = request.getContentType(); return StringUtils.startsWithIgnoreCase(contentType, "multipart/"); }
這種是springmvc標準實現,另外一種是common uploiad實現。
往上返回到這個方法org.springframework.web.multipart.support.StandardServletMultipartResolver#resolveMultipart
@Override public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException { return new StandardMultipartHttpServletRequest(request, this.resolveLazily); }
這種是springmvc標準實現。
往上返回到這個方法org.springframework.web.multipart.commons.CommonsMultipartResolver#resolveMultipart
@Override public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException { Assert.notNull(request, "Request must not be null"); if (this.resolveLazily) { return new DefaultMultipartHttpServletRequest(request) { @Override // 解析上傳文件請求 -》 protected void initializeMultipart() { MultipartParsingResult parsingResult = parseRequest(request); setMultipartFiles(parsingResult.getMultipartFiles()); setMultipartParameters(parsingResult.getMultipartParameters()); setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes()); } }; } else { MultipartParsingResult parsingResult = parseRequest(request); return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(), parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes()); } }
這種是common upload實現。
進入到這個方法org.springframework.web.multipart.commons.CommonsMultipartResolver#parseRequest
protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException { // 獲取請求的編碼 -》 String encoding = determineEncoding(request); // 準備上傳 -》 FileUpload fileUpload = prepareFileUpload(encoding); try { // 處理上傳請求 List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request); // 解析上傳文件 return parseFileItems(fileItems, encoding); } catch (FileUploadBase.SizeLimitExceededException ex) { throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex); } catch (FileUploadBase.FileSizeLimitExceededException ex) { throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex); } catch (FileUploadException ex) { throw new MultipartException("Failed to parse multipart servlet request", ex); } }
進入到這個方法org.springframework.web.multipart.commons.CommonsMultipartResolver#determineEncoding
protected String determineEncoding(HttpServletRequest request) { String encoding = request.getCharacterEncoding(); if (encoding == null) { encoding = getDefaultEncoding(); } return encoding; }
往上返回到這個方法org.springframework.web.multipart.commons.CommonsFileUploadSupport#prepareFileUpload
protected FileUpload prepareFileUpload(String encoding) { FileUpload fileUpload = getFileUpload(); FileUpload actualFileUpload = fileUpload; // Use new temporary FileUpload instance if the request specifies 若是請求指定,則使用新的臨時FileUpload實例 // its own encoding that does not match the default encoding.不匹配默認編碼的自身編碼。 if (encoding != null && !encoding.equals(fileUpload.getHeaderEncoding())) { actualFileUpload = newFileUpload(getFileItemFactory()); actualFileUpload.setSizeMax(fileUpload.getSizeMax()); // 文件上傳文件最大值 actualFileUpload.setFileSizeMax(fileUpload.getFileSizeMax()); actualFileUpload.setHeaderEncoding(encoding); } return actualFileUpload; }
往上返回到這個方法org.springframework.web.servlet.DispatcherServlet#getHandler
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { for (HandlerMapping hm : this.handlerMappings) { if (logger.isTraceEnabled()) { logger.trace( "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); } // 獲取request的handler執行鏈 -》 HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } return null; }
進入到這個方法org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
@Override public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { // 根據請求找到hander -》 Object handler = getHandlerInternal(request); if (handler == null) { // 若是找不到handler就使用默認的handler handler = getDefaultHandler(); } if (handler == null) { return null; } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; // 從上下文中獲取handler handler = getApplicationContext().getBean(handlerName); } // 根據handler和request獲取執行鏈 攔截器和要執行的handler -》 HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); // 若是是跨域請求,header中有Origin -》 if (CorsUtils.isCorsRequest(request)) { // 根據請求獲取跨域配置信息 -》 CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request); // 根據handler和request獲取跨域配置 -》 CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); // 獲取跨域請求的執行鏈 -》 executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain; }
進入到這個方法org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal
@Override protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { // 根據請求路徑匹配到業務hander方法的路徑 String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); if (logger.isDebugEnabled()) { logger.debug("Looking up handler method for path " + lookupPath); } this.mappingRegistry.acquireReadLock(); try { // 匹配業務handler方法 -》 HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); if (logger.isDebugEnabled()) { if (handlerMethod != null) { logger.debug("Returning handler method [" + handlerMethod + "]"); } else { logger.debug("Did not find handler method for [" + lookupPath + "]"); } } return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { this.mappingRegistry.releaseReadLock(); } }
進入到這個方法org.springframework.web.util.UrlPathHelper#getLookupPathForRequest
public String getLookupPathForRequest(HttpServletRequest request) { // Always use full path within current servlet context? 老是在當前servlet上下文中使用完整路徑? -》 if (this.alwaysUseFullPath) { return getPathWithinApplication(request); } // Else, use path within current servlet mapping if applicable 不然,在當前servlet映射中使用path(若是適用) String rest = getPathWithinServletMapping(request); if (!"".equals(rest)) { return rest; } else { return getPathWithinApplication(request); } }
進入到這個方法org.springframework.web.util.UrlPathHelper#getPathWithinApplication
public String getPathWithinApplication(HttpServletRequest request) { // 獲取servlet上下文路徑 -》 String contextPath = getContextPath(request); // 獲取資源請求路徑 -》 String requestUri = getRequestUri(request); String path = getRemainingPath(requestUri, contextPath, true); if (path != null) { // Normal case: URI contains context path. return (StringUtils.hasText(path) ? path : "/"); } else { return requestUri; } }
進入到這個方法org.springframework.web.util.UrlPathHelper#getContextPath
public String getContextPath(HttpServletRequest request) { // 獲取servlet上下文路徑 String contextPath = (String) request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE); if (contextPath == null) { contextPath = request.getContextPath(); } if ("/".equals(contextPath)) { // Invalid case, but happens for includes on Jetty: silently adapt it. contextPath = ""; } // 解碼請求 -》 return decodeRequestString(request, contextPath); }
進入到這個方法org.springframework.web.util.UrlPathHelper#decodeRequestString
public String decodeRequestString(HttpServletRequest request, String source) { if (this.urlDecode && source != null) { // 標準解碼 -》 return decodeInternal(request, source); } return source; }
進入到這個方法org.springframework.web.util.UrlPathHelper#decodeInternal
private String decodeInternal(HttpServletRequest request, String source) { // 從request中獲取編碼格式,若是沒有采用ISO-8859-1編碼 -》 String enc = determineEncoding(request); try { return UriUtils.decode(source, enc); } catch (UnsupportedEncodingException ex) { if (logger.isWarnEnabled()) { logger.warn("Could not decode request string [" + source + "] with encoding '" + enc + "': falling back to platform default encoding; exception message: " + ex.getMessage()); } return URLDecoder.decode(source); } }
進入到這個方法org.springframework.web.util.UrlPathHelper#determineEncoding
protected String determineEncoding(HttpServletRequest request) { String enc = request.getCharacterEncoding(); if (enc == null) { enc = getDefaultEncoding(); } return enc; }
往上返回到這個方法org.springframework.web.util.UrlPathHelper#getRequestUri
public String getRequestUri(HttpServletRequest request) { // 從request中獲取請求資源路徑 String uri = (String) request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE); if (uri == null) { uri = request.getRequestURI(); } return decodeAndCleanUriString(request, uri); }
往上返回到這個方法org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<Match>(); // 根據url查找映射 List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); if (directPathMatches != null) { // 添加匹配的映射 -》 addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { // No choice but to go through all mappings... addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); } if (!matches.isEmpty()) { Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); Collections.sort(matches, comparator); if (logger.isTraceEnabled()) { logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches); } Match bestMatch = matches.get(0); if (matches.size() > 1) { if (CorsUtils.isPreFlightRequest(request)) { return PREFLIGHT_AMBIGUOUS_MATCH; } Match secondBestMatch = matches.get(1); 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 '" + request.getRequestURL() + "': {" + m1 + ", " + m2 + "}"); } } request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod); // hander匹配 handleMatch(bestMatch.mapping, lookupPath, request); return bestMatch.handlerMethod; } else { return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); } }
進入到這個方法org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#addMatchingMappings
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) { for (T mapping : mappings) { // 獲取匹配的映射 -》 T match = getMatchingMapping(mapping, request); if (match != null) { matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping))); } } }
進入到這個方法org.springframework.web.servlet.mvc.method.RequestMappingInfo#getMatchingCondition
@Override public RequestMappingInfo getMatchingCondition(HttpServletRequest request) { // 獲取匹配的方法 -》 RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request); // 參數匹配 ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request); // headers匹配 HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request); ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request); // -》 ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request); if (methods == null || params == null || headers == null || consumes == null || produces == null) { return null; } PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request); if (patterns == null) { return null; } RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request); if (custom == null) { return null; } return new RequestMappingInfo(this.name, patterns, methods, params, headers, consumes, produces, custom.getCondition()); }
進入到這個方法org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition#getMatchingCondition
@Override public RequestMethodsRequestCondition getMatchingCondition(HttpServletRequest request) { // 是否跨域 -》 if (CorsUtils.isPreFlightRequest(request)) { // 跨域匹配 -》 return matchPreFlight(request); } if (getMethods().isEmpty()) { if (RequestMethod.OPTIONS.name().equals(request.getMethod()) && !DispatcherType.ERROR.equals(request.getDispatcherType())) { return null; // No implicit match for OPTIONS (we handle it) } return this; } return matchRequestMethod(request.getMethod()); }
進入到這個方法org.springframework.web.cors.CorsUtils#isPreFlightRequest
public static boolean isPreFlightRequest(HttpServletRequest request) { return (isCorsRequest(request) && HttpMethod.OPTIONS.matches(request.getMethod()) && request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD) != null); }
往上返回到這個方法org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition#matchPreFlight
private RequestMethodsRequestCondition matchPreFlight(HttpServletRequest request) { if (getMethods().isEmpty()) { return this; } String expectedMethod = request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD); // 匹配請求方法-》 return matchRequestMethod(expectedMethod); }
進入到這個方法org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition#matchRequestMethod
private RequestMethodsRequestCondition matchRequestMethod(String httpMethodValue) { HttpMethod httpMethod = HttpMethod.resolve(httpMethodValue); if (httpMethod != null) { for (RequestMethod method : getMethods()) { if (httpMethod.matches(method.name())) { return new RequestMethodsRequestCondition(method); } } if (httpMethod == HttpMethod.HEAD && getMethods().contains(RequestMethod.GET)) { return GET_CONDITION; } } return null; }
往上返回到這個方法org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandlerExecutionChain
說到最後
本次源碼解析僅表明我的觀點,僅供參考。