Spring源碼學習(七):Spring MVC的請求響應流程

目錄

1.方法到達service

1.1 processRequest方法

1.2 父類service方法

2.doDispatch方法

2.1 檢查上傳請求 —— checkMultipart方法

2.2 查找處理器 —— getHandler方法

2.2.1 RequestMappingHandlerMapping 的 getHandlerInternal 方法實現

2.2.2 getHandlerExecutionChain方法

2.2.3 CORS配置

2.2.4 DispatcherServlet對沒有找到Handler的處理

2.3 尋找HandlerAdapter —— getHandlerAdapter方法

2.4 緩存(LastModified屬性)的處理

2.5 攔截器發揮作用 —— preHandler、postHandler和afterCompletion

2.6 處理請求 —— handle方法、applyDefaultViewName方法

2.6.1 請求檢查 checkRequest

2.6.2 觸發處理器方法 invokeHandlerMethod

2.6.3 響應頭處理

2.6.4 doDispatch的異步處理

2.6.5 設置默認視圖名 —— applyDefaultViewName

2.7 處理分派結果 —— processDispatchResult

2.7.1 異常處理

2.7.2 頁面渲染 —— render方法


1.方法到達service

當Spring MVC啓動完畢,就可以開始準備接受來自客戶端的請求了。根據Servlet規範,請求必須先發送到service()方法。在Spring MVC中,該方法實現在DispatcherServlet的父類FrameworkServlet:

protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    if (HttpMethod.PATCH != httpMethod && httpMethod != null) {
        super.service(request, response);
    } else {
        this.processRequest(request, response);
    }
}

根據HttpMethod,即GET、POST、PUT、DELETE等請求類型來分派請求。這裏將請求分爲兩類:PATCH&空,以及其他請求。對於前者,調用processRequest處理,否則調用父類的service方法處理。

1.1 processRequest方法

它首先備份了請求的LocaleContext和RequestAttributes,然後copy了一份新的,並綁定到當前線程:

LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);

RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

initContextHolders(request, localeContext, requestAttributes);

initContextHolders就是將localeContext和requestAttributes兩個對象存入Holder。並且在方法最後,將備份的數據恢復過來,並觸發請求處理完畢事件:

finally {
	resetContextHolders(request, previousLocaleContext, previousAttributes);
	if (requestAttributes != null) {
		requestAttributes.requestCompleted();
	}
	logResult(request, response, failureCause, asyncManager);
	publishRequestHandledEvent(request, response, startTime, failureCause);
}

備份和綁定操作完成後,調用它的核心方法doService,這是一個抽象方法,在DispatcherServlet實現,首先也是備份屬性,並且最後也進行了恢復:

Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
	attributesSnapshot = new HashMap<>();
	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));
		}
	}
}
...
finally {
	if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
		if (attributesSnapshot != null) {
			restoreAttributesAfterInclude(request, attributesSnapshot);
		}
	}
}

然後爲request綁定Web容器、本地化解析器、主題解析器、主題、FlashMap管理器、inputFlashMap/outputFlashMap等屬性,並調用doDispatch方法。

1.2 父類service方法

在父類的方法中,根據HttpMethod類型,分別調用了doXXX方法:

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getMethod();
    if (method.equals(METHOD_GET)) {
        ...
        doGet(req, resp);
        ...
    } else if (method.equals(METHOD_HEAD)) {
        ...
        doHead(req, resp);
    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);        
    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);
    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);
    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req,resp);
    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req,resp);
    } else {
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}

對於未知類型的Http請求,則不進行分派,直接返回錯誤。這些do方法都實現在FrameworkServlet中,實際上都最終調用了processRequest,體現了集中入口、分協議轉發、統一處理的特點。

2.doDispatch方法

先上一張流程圖(來源於網絡):

很清晰地描述了doDispatch方法的流程。

2.1 檢查上傳請求 —— checkMultipart方法

由於Spring MVC默認不支持文件上傳,所以必須在請求處理的最開始就進行檢查,以免在處理過程中才發現沒有配置文件上傳解析器,導致處理失敗。checkMultipart方法源碼如下:

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
            if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
                logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
            }
        }
        else if (hasMultipartException(request)) {
            logger.debug("Multipart resolution previously failed for current request - " +	"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);
                }
                else {
                    throw ex;
                }
            }
        }
    }
    return request;
}

首先檢查是否配置了文件上傳解析器,以及請求中是否包含multipart部分,沒有的話就可以不必繼續了。

然後檢查請求是否已經被處理過,或者曾經處理過但是有未解決的異常,這兩種情況也不需要繼續處理。如果請求還沒處理過,則用文件上傳解析器進行解析,成功解析後,將生成一個MultipartHttpServletRequest對象。這裏以CommonsMultpartResolver爲例說明,其resolveMultipart方法源碼如下:

public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
    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());
    }
}

不難看出,懶加載方式下,只是重寫了initializeMultipart方法,真正的解析還要等後面,非懶加載模式下,直接就進行解析了:

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);
    }
}

這裏有三種異常情況:請求本身太大、上傳的文件太大、Request無法解析。

determineEncoding很簡單,就是讀取請求中character-encoding屬性,沒有就使用默認編碼ISO-8859-1。

prepareFileUpload也很簡單,就是創建一個FileUpload實例,賦一些屬性:

protected FileUpload prepareFileUpload(@Nullable String encoding) {
    FileUpload fileUpload = getFileUpload();
    FileUpload actualFileUpload = fileUpload;
    if (encoding != null && !encoding.equals(fileUpload.getHeaderEncoding())) {
        actualFileUpload = newFileUpload(getFileItemFactory());
        actualFileUpload.setSizeMax(fileUpload.getSizeMax());
        actualFileUpload.setFileSizeMax(fileUpload.getFileSizeMax());
        actualFileUpload.setHeaderEncoding(encoding);
    }
    return actualFileUpload;
}

FileUpload類的parseRequest方法可以用於解析MultipartServletRequest,生成文件對象列表,它的原理就是讀取請求中的數據,構造FileItem對象,然後存入列表中,去除了catch塊之後的代碼如下::

public List<FileItem> parseRequest(RequestContext ctx) throws FileUploadException {
    List<FileItem> items = new ArrayList<FileItem>();
    boolean successful = false;
    FileItemIterator iter = getItemIterator(ctx);
    FileItemFactory fac = getFileItemFactory();
    if (fac == null) {
        throw new NullPointerException("No FileItemFactory has been set.");
    }
    while (iter.hasNext()) {
        final FileItemStream item = iter.next();
        final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;
        FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(),item.isFormField(), fileName);
        items.add(fileItem);
        Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
        final FileItemHeaders fih = item.getHeaders();
        fileItem.setHeaders(fih);
    }
    successful = true;
    if (!successful) {
        for (FileItem fileItem : items) {
            try {
                fileItem.delete();
            } catch (Exception ignored) {
            }
        }
    }
}

getItemIterator方法從請求中讀取了Content-Type、Content-Length、Character-Encoding等屬性,構造了MultipartStream流,並返回了一個迭代器實例。每當調用hasNext時,就會調用findNextItem方法,解析MultipartStream並生成FileItemStream對象。

假如在生成FileItem的過程中拋出異常,則會執行fileItem.delete()方法顯式清除臨時文件。

FileItem是apache commons包的類,還需要進一步解析爲Spring的MultipartParsingResult對象。此處根據FileItem對象裏面封裝的數據是一個普通文本表單字段,還是一個文件表單字段採用不同的處理方式。

對於文本表單字段,調用getString方法讀取文件內容,然後存入multipartParameters中,讀取Content-Type屬性,存入multipartParameterContentTypes

String value;
String partEncoding = determineEncoding(fileItem.getContentType(), encoding);
try {
    value = fileItem.getString(partEncoding);
}
catch (UnsupportedEncodingException ex) {
    value = fileItem.getString();
}
String[] curParam = multipartParameters.get(fileItem.getFieldName());
if (curParam == null) {
    multipartParameters.put(fileItem.getFieldName(), new String[] {value});
}
else {
    String[] newParam = StringUtils.addStringToArray(curParam, value);
    multipartParameters.put(fileItem.getFieldName(), newParam);
}
multipartParameterContentTypes.put(fileItem.getFieldName(), fileItem.getContentType());

對於文件表單字段,則直接創建CommonsMultipartFile對象,並添加到multipartFiles

CommonsMultipartFile file = createMultipartFile(fileItem);
multipartFiles.add(file.getName(), file);

protected CommonsMultipartFile createMultipartFile(FileItem fileItem) {
	CommonsMultipartFile multipartFile = new CommonsMultipartFile(fileItem);
	multipartFile.setPreserveFilename(this.preserveFilename);
	return multipartFile;
}

無論如何,都將上述三個加了粗的Map對象傳入MultipartParsingResult的構造函數,返回一個新實例。

2.2 查找處理器 —— getHandler方法

該方法實際就是遍歷所有HandlerMapping,逐個調用其getHandler方法,看它對應的Handler能否處理該請求,假如某個HandlerMapping滿足條件,則直接返回,否則繼續循環。假如直到遍歷完畢還沒有結果,則返回null。

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

getHandler方法實現在AbstractHandlerMapping類中,關鍵源碼如下:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    Object handler = getHandlerInternal(request);
    if (handler == null) {
        handler = getDefaultHandler();
    }
    if (handler == null) {
        return null;
    }
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = obtainApplicationContext().getBean(handlerName);
    }
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    if (CorsUtils.isCorsRequest(request)) {
        CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
        CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }
    return executionChain;
}

defaultHandler如果沒有顯式注入的話一般都是null,所以不用考慮。顯然核心方法就是getHandlerInternal和getHandlerExecutionChain,以及下面的CORS處理邏輯。

2.2.1 RequestMappingHandlerMapping 的 getHandlerInternal 方法實現

這裏仍以RequestMappingHandlerMapping爲例說明。其getHandlerInternal方法實際上位於父類AbstractHandlerMethodMapping:

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    this.mappingRegistry.acquireReadLock();
    try {
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
        this.mappingRegistry.releaseReadLock();
    }
}

首先是對URL進行截取:

public String getLookupPathForRequest(HttpServletRequest request) {
	if (this.alwaysUseFullPath) {
		return getPathWithinApplication(request);
	}
	String rest = getPathWithinServletMapping(request);
	if (!"".equals(rest)) {
		return rest;
	}
	else {
		return getPathWithinApplication(request);
	}
}

先來看getPathWithinApplication方法,它獲取了request的contextPath屬性和URI,然後把URI中和contextPath相同的部分切除,返回剩餘的部分:

public String getPathWithinApplication(HttpServletRequest request) {
	String contextPath = getContextPath(request);
	String requestUri = getRequestUri(request);
	String path = getRemainingPath(requestUri, contextPath, true);
	if (path != null) {
		return (StringUtils.hasText(path) ? path : "/");
	}
	else {
		return requestUri;
	}
}

舉個例子,假如contextPath爲"/hello",URI爲"/hello/index.html",則返回"/index.html"。

再來看getPathWithinServletMapping方法,首先也是調用了getPathWithinApplication,然後獲取了request中的servletPath屬性,然後將pathWithinApp中的"//"替換爲"/"。servletPath就是URI中分號之前的部分,例如:URI爲"/hello/index.html;a=1;b=2",則其servletPath就是"/hello/index.html"。然後把URI中和servletPath相同的部分切除,保留剩餘的部分:

public String getPathWithinServletMapping(HttpServletRequest request) {
	String pathWithinApp = getPathWithinApplication(request);
	String servletPath = getServletPath(request);
	String sanitizedPathWithinApp = getSanitizedPath(pathWithinApp);
	String path;
    if (servletPath.contains(sanitizedPathWithinApp)) {
		path = getRemainingPath(sanitizedPathWithinApp, servletPath, false);
	}
	else {
		path = getRemainingPath(pathWithinApp, servletPath, false);
	}
	if (path != null) {
		return path;
	}
	else {
		String pathInfo = request.getPathInfo();
		if (pathInfo != null) {
			return pathInfo;
		}
		if (!this.urlDecode) {
			path = getRemainingPath(decodeInternal(request, pathWithinApp), servletPath, false);
			if (path != null) {
				return pathWithinApp;
			}
		}
		return servletPath;
	}
}

截取URL之後,就可以進行HandlerMethod的匹配了,首先要看下有沒有能完全匹配上的:

List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
    addMatchingMappings(directPathMatches, matches, request);
}

假如沒有,則對所有HandlerMethods進行查找,找出其中最好的一個繼續處理。Comparator按照請求類型、patternsCondition、paramsCondition、headersCondition、consumesCondition、producesCondition、methodsCondition、customConditionHolder的順序比較各屬性:

Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
	if (CorsUtils.isPreFlightRequest(request)) {
		return new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"));
	}
	Match secondBestMatch = matches.get(1);
	if (comparator.compare(bestMatch, secondBestMatch) == 0) {
		Method m1 = bestMatch.handlerMethod.getMethod();
		Method m2 = secondBestMatch.handlerMethod.getMethod();
		String uri = request.getRequestURI();
		throw new IllegalStateException("Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
	}
}
request.setAttribute(HandlerMapping.class.getName() + ".bestMatchingHandler", bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;

handleMatch方法在子類RequestMappingInfoHandlerMapping進行了重寫,除了將匹配/比較結果存放到request中,還從request裏解析了@MatrixVariable和@PathVariable註解的參數的值,也一併存入request。

假如沒有匹配到HandlerMethod(比如根本就沒有註冊任何HandlerMethod),就會執行handleNoMatch方法,該方法也被重寫,實際邏輯就是再遍歷一次RequestMappingInfo列表,看看是不是有漏網之魚,如果還是沒有,則拋出異常。

然後需要對HandlerMethod進行實例化。HandlerMethod在創建時(createHandlerMethod方法),傳入的handler參數實際是BeanName,顯然不包含實例,也就不可能進行方法調用,所以還需要進行實例化:

public HandlerMethod createWithResolvedBean() {
	Object handler = this.bean;
	if (this.bean instanceof String) {
		String beanName = (String) this.bean;
		handler = this.beanFactory.getBean(beanName);
	}
	return new HandlerMethod(this, handler);
}

2.2.2 getHandlerExecutionChain方法

該方法實質就是將Handler攔截器加入到處理鏈中,以便攔截器生效:

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
	HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
		(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
	String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
	for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
		if (interceptor instanceof MappedInterceptor) {
			MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
			if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
				chain.addInterceptor(mappedInterceptor.getInterceptor());
			}
		}
		else {
			chain.addInterceptor(interceptor);
		}
	}
	return chain;
}

2.2.3 CORS配置

首先獲取全局CORS配置,實際就是從Map中獲取值:

public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
	String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
	for (Map.Entry<String, CorsConfiguration> entry : this.corsConfigurations.entrySet()) {
		if (this.pathMatcher.match(entry.getKey(), lookupPath)) {
			return entry.getValue();
		}
	}
	return null;
}

然後是獲取處理器的CORS配置,本質上和上面的一樣:

// in AbstractHandlerMethodMapping.java
protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {
	CorsConfiguration corsConfig = super.getCorsConfiguration(handler, request);
	if (handler instanceof HandlerMethod) {
		HandlerMethod handlerMethod = (HandlerMethod) handler;
		if (handlerMethod.equals(PREFLIGHT_AMBIGUOUS_MATCH)) {
			return AbstractHandlerMethodMapping.ALLOW_CORS_CONFIG;
		}
		else {
			CorsConfiguration corsConfigFromMethod = this.mappingRegistry.getCorsConfiguration(handlerMethod);
			corsConfig = (corsConfig != null ? corsConfig.combine(corsConfigFromMethod) : corsConfigFromMethod);
		}
	}
	return corsConfig;
}

// in AbstractHandlerMapping.java
protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {
	Object resolvedHandler = handler;
	if (handler instanceof HandlerExecutionChain) {
		resolvedHandler = ((HandlerExecutionChain) handler).getHandler();
	}
	if (resolvedHandler instanceof CorsConfigurationSource) {
		return ((CorsConfigurationSource) resolvedHandler).getCorsConfiguration(request);
	}
	return null;
}

然後將本地配置和全局配置融合,用於創建CORS攔截器,添加到處理器執行鏈上:

protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
    HandlerExecutionChain chain, @Nullable CorsConfiguration config) {
    if (CorsUtils.isPreFlightRequest(request)) {
        HandlerInterceptor[] interceptors = chain.getInterceptors();
            chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
    }
    else {
        chain.addInterceptor(new CorsInterceptor(config));
    }
    return chain;
}

和上面的getHandlerExecutionChain原理一致,不再贅述。

2.2.4 DispatcherServlet對沒有找到Handler的處理

儘管在查找HandlerMethod的過程中,如果出現noMatch會拋出異常,但是Spring還是在DispatcherServlet進行了處理,原因很簡單:服務器拋出異常,僅有自己可以感知到,還必須通過Response返回錯誤,才能讓客戶端也感知到:

protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
	if (pageNotFoundLogger.isWarnEnabled()) {
		pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request));
	}
	if (this.throwExceptionIfNoHandlerFound) {
		throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
				new ServletServerHttpRequest(request).getHeaders());
	}
	else {
		response.sendError(HttpServletResponse.SC_NOT_FOUND);
	}
}

2.3 尋找HandlerAdapter —— getHandlerAdapter方法

這裏實現很簡單:遍歷適配器列表,看哪個可以支持找到的Handler,就返回哪個。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
	if (this.handlerAdapters != null) {
		for (HandlerAdapter adapter : this.handlerAdapters) {
			if (adapter.supports(handler)) {
				return adapter;
			}
		}
	}
	throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

對於RequestMappingHandlerAdapter來說,僅僅檢查了handler是否是HandlerMethod類型,是則返回true。

2.4 緩存(LastModified屬性)的處理

服務器具有緩存機制,在用戶第一次請求某資源時,如果請求成功,服務器會返回HTTP 200,並在響應頭中添加一個LastModified字段,記錄此資源在服務器上的最後更新時間;當再次請求該資源時,瀏覽器會在請求頭中加上If-Modified-Since字段,詢問服務器該資源自此時間起是否更新過,如果沒有更新,則直接返回HTTP 304,而不返回資源,這樣就節省了帶寬。

Spring在獲取了HandlerAdapter後,就會檢查Handler是否支持緩存機制,如果支持,且資源未過期,則直接返回:

String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
	long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
	if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
		return;
	}
}

可見資源更新時間是由HandlerAdapter決定的,這裏以SimpleControllerHandlerAdapter的實現爲例:

public long getLastModified(HttpServletRequest request, Object handler) {
    if (handler instanceof LastModified) {
        return ((LastModified) handler).getLastModified(request);
    }
    return -1L;
}

意思很簡單:如果Handler(即Controller類)實現了LastModified接口,則調用其getLastModified方法獲取,否則返回-1。

checkNotModified就是驗證獲取到的時間是否過期,並更新Response:

if (validateIfUnmodifiedSince(lastModifiedTimestamp)) {
	if (this.notModified && response != null) {
		response.setStatus(HttpStatus.PRECONDITION_FAILED.value());
	}
	return this.notModified;
}
boolean validated = validateIfNoneMatch(etag);
if (!validated) {
	validateIfModifiedSince(lastModifiedTimestamp);
}
if (response != null) {
	boolean isHttpGetOrHead = SAFE_METHODS.contains(getRequest().getMethod());
	if (this.notModified) {
		response.setStatus(isHttpGetOrHead ?
			HttpStatus.NOT_MODIFIED.value() : HttpStatus.PRECONDITION_FAILED.value());
	}
	if (isHttpGetOrHead) {
		if (lastModifiedTimestamp > 0 && parseDateValue(response.getHeader(LAST_MODIFIED)) == -1) {
	    	response.setDateHeader(LAST_MODIFIED, lastModifiedTimestamp);
		}
		if (StringUtils.hasLength(etag) && response.getHeader(ETAG) == null) {
			response.setHeader(ETAG, padEtagIfNecessary(etag));
		}
	}
}
return this.notModified;

validateIfUnmodifiedSince首先對剛剛獲取到的資源更新時間進行判斷,如果小於0直接返回false,否則解析If-Modified-Since屬性並進行比較:

private boolean validateIfUnmodifiedSince(long lastModifiedTimestamp) {
    if (lastModifiedTimestamp < 0) {
        return false;
    }
    long ifUnmodifiedSince = parseDateHeader(IF_UNMODIFIED_SINCE);
    if (ifUnmodifiedSince == -1) {
        return false;
    }
    this.notModified = (ifUnmodifiedSince < (lastModifiedTimestamp / 1000 * 1000));
    return true;
}

如果validateIfUnmodifiedSince返回了false,則需要進一步判斷,由於調用checkNotModified方法時沒有傳入etag,所以validateIfNoneMatch必定返回false,因此會調用validateIfModifiedSince再次判斷,內容和validateIfUnmodifiedSince類似,只是最後面的小於號換成了大於等於號。

如果經過兩次判斷,確認了資源未修改,則可以對repsonse進行更新。

2.5 攔截器發揮作用 —— preHandler、postHandler和afterCompletion

在進行了上面一系列預處理後,就可以正式開始使用Handler處理請求了。在Servlet規範中,設計了filter組件,可以在每個Web請求前後對它做處理,顯然這種處理粒度太粗。Spring MVC增加了攔截器的概念,從HandlerMapping初始化和Handler查找的過程中,我們可以看到它的身影。

攔截器接口HandlerInterceptor定義了三個方法:preHandle、postHandle、afterCompletion,分別作用於處理器方法調用前後、處理器執行鏈全部執行後。在doDispatch方法中,通過applyPreHandle、applyPostHandle和triggerAfterCompletion來調用處理器執行鏈上所有攔截器的對應方法。

applyPreHandle/applyPostHandle就是遍歷所有攔截器,觸發其preHandle/postHandle方法,比較有意思的是applyPreHandle,當preHandle返回false時,它會嘗試調用afterCompletion方法。閱讀HandlerInterceptor接口註釋後發現,preHandle方法如果返回true,表示應當由下一個攔截器或者處理器繼續處理,否則可以認爲response對象已經被該處理鏈處理過了,不需要重複處理,應當中斷:

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HandlerInterceptor[] interceptors = getInterceptors();
	if (!ObjectUtils.isEmpty(interceptors)) {
		for (int i = 0; i < interceptors.length; i++) {
			HandlerInterceptor interceptor = interceptors[i];
			if (!interceptor.preHandle(request, response, this.handler)) {
				triggerAfterCompletion(request, response, null);
				return false;
			}
			this.interceptorIndex = i;
		}
	}
	return true;
}

至於processDispatchResult,暫時先不介紹。

2.6 處理請求 —— handle方法、applyDefaultViewName方法

當執行鏈中所有的preHandle方法都觸發之後,就可以調用處理器來真正處理請求了。這裏調用了HandlerAdapter的handle方法處理,以RequestMappingHandlerAdapter爲例,它的handle方法又調用了handleInternal方法:

protected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
	ModelAndView mav;
	checkRequest(request);
	if (this.synchronizeOnSession) {
		HttpSession session = request.getSession(false);
		if (session != null) {
			Object mutex = WebUtils.getSessionMutex(session);
			synchronized (mutex) {
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}
	}
	else {
		mav = invokeHandlerMethod(request, response, handlerMethod);
	}
	...
}

synchronizedOnSession的作用是,讓同一個Session的請求串行執行,默認false。 

2.6.1 請求檢查 checkRequest

首先進行正式處理前的最後一次檢查,這裏檢查了請求類型是否被支持,以及是否包含必需的Session對象:

protected final void checkRequest(HttpServletRequest request) throws ServletException {
    String method = request.getMethod();
    if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {
        throw new HttpRequestMethodNotSupportedException(method, this.supportedMethods);
    }
    if (this.requireSession && request.getSession(false) == null) {
        throw new HttpSessionRequiredException("Pre-existing session required but none found");
    }
}

2.6.2 觸發處理器方法 invokeHandlerMethod

在該方法的第一段,對傳入的三個參數:request、response、handlerMethod進行了包裝和解析,創建了接下來觸發方法需要的組件:

ServletWebRequest webRequest = new ServletWebRequest(request, response);
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
    invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
    invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

getDataBinderFactory主要做了三件事:

1)取出handlerMethod所在類下被註解了@InitBinder的方法,以及@ControllerAdvice類下被註解了@InitBinder的方法

2)將這些方法封裝爲InvocableHandlerMethod,設置參數綁定解析器和參數綁定工廠,並添加到列表中

3)使用上一步得到的列表創建一個ServletRequestDataBinderFactory並返回

getModelFactory方法的主要流程和getDataBidnerFactory差不多,只不過第一步搜索的是@ModelAttribute註解的方法,第三步創建的是ModelFactory而已。

createInvocableHandlerMethod則是將傳入的HandlerMethod對象直接封裝爲ServletInvocableHandlerMethod。

接下來,將初始化HandlerAdapter時配置的各種解析器,以及剛剛創建的binderFactory對象綁定到處理器方法上。

方法的第二段主要創建和配置了邏輯視圖容器及異步管理器對象:

ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

中間還對處理器方法的ModelMap進行了處理,實際就是觸發剛剛找到的所有@ModelAttribute方法,將可以觸發的都觸發,把該添加到ModelMap的屬性都添加進來。

假如需要異步處理,則將ServletInvocableHandlerMethod對象進一步封裝爲ConcurrentResultHandlerMethod:

if (asyncManager.hasConcurrentResult()) {
    Object result = asyncManager.getConcurrentResult();
    mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
    asyncManager.clearConcurrentResult();
    invocableMethod = invocableMethod.wrapConcurrentResult(result);
}

第三段就是調用處理器方法,並取出處理結果(邏輯視圖),如果是異步處理,則先返回null,等異步處理完了再獲取:

invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
	return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);

invokeAndHandle邏輯很簡單:使用Method.invoke方法反射調用,然後調用初始化HandlerAdapter時配置的返回值處理器進行處理,中間還處理了一下無返回值或出現響應錯誤的情況:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    setResponseStatus(webRequest);
    if (returnValue == null) {
        if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            mavContainer.setRequestHandled(true);
            return;
        }
    }
    else if (StringUtils.hasText(getResponseStatusReason())) {
        mavContainer.setRequestHandled(true);
        return;
    }
    mavContainer.setRequestHandled(false);
    try {
        this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    }
    catch (Exception ex) {
        throw ex;
    }
}

可以看出,如果出現無返回值或者響應錯誤,則對邏輯視圖容器標記處理完成,而順利執行反而不做標記,這麼做的原因在getModelAndView方法裏:

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
    modelFactory.updateModel(webRequest, mavContainer);
    if (mavContainer.isRequestHandled()) {
        return null;
    }
    ModelMap model = mavContainer.getModel();
    ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
    if (!mavContainer.isViewReference()) {
        mav.setView((View) mavContainer.getView());
    }
    if (model instanceof RedirectAttributes) {
        Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        if (request != null) {
            RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
        }
    }
    return mav;
}

如果容器標記了請求已處理,則不會獲取處理結果。這裏將ModelMap和ViewName、Status等屬性封裝爲ModelAndView邏輯視圖對象,進行返回。對於視圖引用和重定向請求,還進行了特殊處理。值得一提的是重定向的處理,這裏獲取到了在本篇1.1節配置的outputFlashMap對象。

在整個方法調用完後,通過finally塊執行了ServletWebRequest的requestCompleted方法,該方法源碼如下:

public void requestCompleted() {
    executeRequestDestructionCallbacks();
    updateAccessedSessionAttributes();
    this.requestActive = false;
}

 這裏執行了request中設置的析構回調,並更新了會話中的屬性。

2.6.3 響應頭處理

在handle方法處理完畢後,handleInternal方法還對響應頭進行了處理:

if (!response.containsHeader("Cache-Control")) {
	if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
		applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
	}
	else {
		prepareResponse(response);
	}
}
return mav;

getSessionAttributesHandler返回了當前處理器類型對應的SessionAttributesHandler,如果沒有就新建一個,不過實際上在創建ModelFactory的時候就已經調用過這個方法了,也就是說,進行到響應頭處理這一步時,是肯定存在SessionAttributesHandler的,而且在上一步最後獲取處理結果時,調用的updateModel已經將會話屬性sessionAttributes保存進去了。也就是說,除非會話中不包含任何屬性,否則是肯定會進入applyCacheSeconds分支的。

applyCacheSeconds方法中,最外層的if語句判斷的兩個條件,默認情況下都不成立,即會進入else分支。cacheSeconds參數(即 RequestMappingHandlerAdapter 的 cacheSecondsForSessionAttributeHandlers 屬性)默認爲0,useCacheControlNoStore默認爲true,所以cControl在默認情況下是通過noStore方法創建的一個noStore屬性爲true的實例:

protected final void applyCacheSeconds(HttpServletResponse response, int cacheSeconds) {
    if (this.useExpiresHeader || !this.useCacheControlHeader) {
        if (cacheSeconds > 0) {
            cacheForSeconds(response, cacheSeconds);
        }
        else if (cacheSeconds == 0) {
            preventCaching(response);
        }
    }
    else {
        CacheControl cControl;
        if (cacheSeconds > 0) {
            cControl = CacheControl.maxAge(cacheSeconds, TimeUnit.SECONDS);
            if (this.alwaysMustRevalidate) {
                cControl = cControl.mustRevalidate();
            }
        }
        else if (cacheSeconds == 0) {
            cControl = (this.useCacheControlNoStore ? CacheControl.noStore() : CacheControl.noCache());
        }
        else {
            cControl = CacheControl.empty();
        }
        applyCacheControl(response, cControl);
    }
}

applyCacheControl就是根據傳入的CacheControl對象,對response進行修改:

protected final void applyCacheControl(HttpServletResponse response, CacheControl cacheControl) {
    String ccValue = cacheControl.getHeaderValue();
    if (ccValue != null) {
        response.setHeader("Cache-Control", ccValue);
		if (response.containsHeader("Pragma")) {
            response.setHeader("Pragma", "");
        }
        if (response.containsHeader("Expires")) {
            response.setHeader("Expires", "");
        }
    }
}

至於prepareResponse分支,實際也調用了applyCacheControl和applyCacheSeconds兩個方法:

protected final void prepareResponse(HttpServletResponse response) {
	if (this.cacheControl != null) {
		applyCacheControl(response, this.cacheControl);
	}
	else {
		applyCacheSeconds(response, this.cacheSeconds);
	}
	if (this.varyByRequestHeaders != null) {
		for (String value : getVaryRequestHeadersToAdd(response, this.varyByRequestHeaders)) {
			response.addHeader("Vary", value);
		}
	}
}

2.6.4 doDispatch的異步處理

在2.6.2中,如果請求需要異步處理,則不會獲取處理結果,而是直接返回,在doDispatch中也是這樣處理的:

if (asyncManager.isConcurrentHandlingStarted()) {
    return;
}

不過doDispatch在finally塊中又進行了一次處理:

finally {
	if (asyncManager.isConcurrentHandlingStarted()) {
		if (mappedHandler != null) {
			mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
		}
	}
	else {
		if (multipartRequestParsed) {
			cleanupMultipart(processedRequest);
		}
	}
}

 mappedHandler在applyAfterConcurrentHandlingStarted方法中,調用了AsyncHandlerInterceptor實例對請求和響應進行處理。

2.6.5 設置默認視圖名 —— applyDefaultViewName

這裏的邏輯很簡單,假如邏輯視圖不是視圖引用(即其view屬性是個String對象),則用RequestToViewNameTranslator生成一個默認名稱:

private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
    if (mv != null && !mv.hasView()) {
        String defaultViewName = getDefaultViewName(request);
        if (defaultViewName != null) {
            mv.setViewName(defaultViewName);
        }
    }
}

2.7 處理分派結果 —— processDispatchResult

處理器執行鏈對請求處理完成後,產生了ModelAndView邏輯視圖對象,但是:1)邏輯視圖還不是我們看到的網頁或數據,還需要進一步渲染;2)如果處理過程中出現了異常,也需要進行集中處理;3)afterCompletion方法還沒有觸發

因此Spring MVC提供了processDispatchResult對上述三個問題進行處理。

2.7.1 異常處理

在doDispatch方法中,如果捕獲了異常,就把異常賦給dispatchException對象,通過方法調用傳遞過來:

catch (Exception ex) {
	dispatchException = ex;
}
catch (Throwable err) {
	dispatchException = new NestedServletException("Handler dispatch failed", err);
}

在processDispatchResult方法最開始,就對異常進行了處理:

boolean errorView = false;
if (exception != null) {
	if (exception instanceof ModelAndViewDefiningException) {
		mv = ((ModelAndViewDefiningException) exception).getModelAndView();
	}
	else {
		Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
		mv = processHandlerException(request, response, handler, exception);
		errorView = (mv != null);
	}
}

這一過程是用異常視圖替換原始視圖的過程,核心邏輯在processHandlerException方法,實際就是調用所有註冊了的處理器異常解析器的resolveException方法產生異常視圖,並對請求屬性進行配置:

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) throws Exception {
    ModelAndView exMv = null;
    if (this.handlerExceptionResolvers != null) {
        for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
            exMv = resolver.resolveException(request, response, handler, ex);
            if (exMv != null) {
                break;
            }
        }
    }
    if (exMv != null) {
        if (exMv.isEmpty()) {
            request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
            return null;
        }
        if (!exMv.hasView()) {
            String defaultViewName = getDefaultViewName(request);
            if (defaultViewName != null) {
                exMv.setViewName(defaultViewName);
            }
        }
        WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
        return exMv;
    }
    throw ex;
}

2.7.2 頁面渲染 —— render方法

在方法的開頭,先使用本地化解析器決定了本地化屬性:

Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);

然後根據邏輯視圖中,view對象究竟是視圖引用還是名稱引用,採用不同的處理方式。首先是對名稱引用的處理:

view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
    throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'");
}

需要靠名稱解析出實際的視圖對象,實質就是調用合適的ViewResolver的resolveViewName方法:

protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {
    if (this.viewResolvers != null) {
        for (ViewResolver viewResolver : this.viewResolvers) {
            View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    return view;
                }
            }
        }
    return null;
}

resolveViewName的執行過程請見 Spring源碼學習(六):Spring MVC的初始化過程 3.8節。

如果邏輯視圖採用視圖引用,即內部已包含View對象,直接取出就可以。

接下來,爲response設置HTTP狀態碼,並調用View的render方法進行頁面渲染。以InternalResourceView爲例,其render方法實現在AbstractView中:

public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    prepareResponse(request, response);
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

prepareResponse方法雖然不是空方法,但是由於if條件永遠不滿足,所以實際不做任何處理。實際起作用的只有createMergedOutputModel和renderMergedOutputModel兩個方法。

首先看前者,該方法同樣實現在AbstractView類,作用是將靜態和動態變量添加到模型中:

protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) {
    Map<String, Object> pathVars = (this.exposePathVariables ?
        (Map<String, Object>) request.getAttribute(View.class.getName() + ".pathVariables") : null);
    int size = this.staticAttributes.size();
    size += (model != null ? model.size() : 0);
    size += (pathVars != null ? pathVars.size() : 0);
    Map<String, Object> mergedModel = new LinkedHashMap<>(size);
    mergedModel.putAll(this.staticAttributes);
    if (pathVars != null) {
        mergedModel.putAll(pathVars);
    }
    if (model != null) {
        mergedModel.putAll(model);
    }
    if (this.requestContextAttribute != null) {
        mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
    }
    return mergedModel;
}

exposePathVariables變量默認爲true。可以看到,添加屬性時,按照先靜態屬性再動態屬性的順序,即動態屬性優先級更高。

然後看後者,它的一個參數是getRequestToExpose的結果,它的兩個if判斷條件默認都不滿足,即不作處理直接返回:

protected HttpServletRequest getRequestToExpose(HttpServletRequest originalRequest) {
    if (this.exposeContextBeansAsAttributes || this.exposedContextBeanNames != null) {
        WebApplicationContext wac = getWebApplicationContext();
        Assert.state(wac != null, "No WebApplicationContext");
        return new ContextExposingHttpServletRequest(originalRequest, wac, this.exposedContextBeanNames);
    }
    return originalRequest;
}

renderMergedOutputModel方法實現在InternalResourceView類,它首先將模型數據綁定到request上:

exposeModelAsRequestAttributes(model, request);
exposeHelpers(request);

第二個方法是個擴展點,沒有默認實現。接下來解析View的位置:

protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response) throws Exception {
    String path = getUrl();
    Assert.state(path != null, "'url' not set");
    if (this.preventDispatchLoop) {
        String uri = request.getRequestURI();
        if (path.startsWith("/") ? uri.equals(path) : uri.equals(StringUtils.applyRelativePath(uri, path))) {
            throw new ServletException("Circular view path [" + path + "]: would dispatch back " + "to the current handler URL [" + uri + "] again. Check your ViewResolver setup! " + "(Hint: This may be the result of an unspecified view, due to default view name generation.)");
        }
    }
    return path;
}

這裏拋出的異常肯定很多人都遇到過,使用SpringBoot時,如果沒有配置themeleaf且有如下代碼,在訪問時就會拋出這個異常:

@Controller
public class TestController{
    @RequestMapping("/index")
    public String index(){
        return "index";
    }
}

不過在Spring MVC中,由於preventDispatchLoop默認爲false,所以在此處不會觸發迴環檢查。

接下來就是獲取一個RequestDispatcher(使用InternalResourceView時,一般對應JSP文件),由它將剛剛設置到request中的模型數據發送到實際頁面:

// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
    throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + "]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
    response.setContentType(getContentType());
    rd.include(request, response);
}
else {
    // Note: The forwarded resource is supposed to determine the content type itself.
    rd.forward(request, response);
}

RequestDispatcher是Servlet規範的一部分,關於它的使用和原理,可以參見:

Servlet規範

【Servlet】關於RequestDispatcher的原理

至此,頁面渲染部分完成。

在這之後,調用處理器執行鏈的afterCompletion方法,處理異步事件或調用cleanupMultipart清理資源。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章