SpringMVC的工作流程和源碼分析

SpringMVC框架是一個基於請求驅動的Web框架,並且使用了‘前端控制器’模型來進行設計,再根據‘請求映射規則’分發給相應的頁面控制器進行處理。

整體流程

具體步驟:

1、  首先用戶發送請求到前端控制器,前端控制器根據請求信息(如 URL)來決定選擇哪一個頁面控制器進行處理並把請求委託給它,即以前的控制器的控制邏輯部分;圖中的 1、2 步驟;

2、  頁面控制器接收到請求後,進行功能處理,首先需要收集和綁定請求參數到一個對象,這個對象在 Spring Web MVC 中叫命令對象,並進行驗證,然後將命令對象委託給業務對象進行處理;處理完畢後返回一個 ModelAndView(模型數據和邏輯視圖名);圖中的 3、4、5 步驟;

3、  前端控制器收回控制權,然後根據返回的邏輯視圖名,選擇相應的視圖進行渲染,並把模型數據傳入以便視圖渲染;圖中的步驟 6、7;

4、  前端控制器再次收回控制權,將響應返回給用戶,圖中的步驟 8;至此整個結束。

 

核心流程

具體步驟:

第一步:發起請求到前端控制器(DispatcherServlet)

第二步:前端控制器請求HandlerMapping查找 Handler (可以根據xml配置、註解進行查找)

第三步:處理器映射器HandlerMapping向前端控制器返回Handler,HandlerMapping會把請求映射爲HandlerExecutionChain對象(包含一個Handler處理器(頁面控制器)對象,多個HandlerInterceptor攔截器對象),通過這種策略模式,很容易添加新的映射策略

第四步:前端控制器調用處理器適配器去執行Handler

第五步:處理器適配器HandlerAdapter將會根據適配的結果去執行Handler

第六步:Handler執行完成給適配器返回ModelAndView

第七步:處理器適配器向前端控制器返回ModelAndView (ModelAndView是springmvc框架的一個底層對象,包括 Model和view)

第八步:前端控制器請求視圖解析器去進行視圖解析 (根據邏輯視圖名解析成真正的視圖(jsp)),通過這種策略很容易更換其他視圖技術,只需要更改視圖解析器即可

第九步:視圖解析器向前端控制器返回View

第十步:前端控制器進行視圖渲染 (視圖渲染將模型數據(在ModelAndView對象中)填充到request域)

第十一步:前端控制器向用戶響應結果

 

總結 核心開發步驟

1、  DispatcherServlet 在 web.xml 中的部署描述,從而攔截請求到 Spring Web MVC

2、  HandlerMapping 的配置,從而將請求映射到處理器

3、  HandlerAdapter 的配置,從而支持多種類型的處理器

注:處理器映射求和適配器使用紓解的話包含在了註解驅動中,不需要在單獨配置

4、  ViewResolver 的配置,從而將邏輯視圖名解析爲具體視圖技術

5、  處理器(頁面控制器)的配置,從而進行功能處理 

View是一個接口,實現類支持不同的View類型(jsp、freemarker、pdf...)

 

相關組件說明

DispatcherServlet:前端控制器

用戶請求到達前端控制器,它就相當於 mvc 模式中的 cdispatcherServlet 是整個流程控制的中心,由它調用其它組件處理用戶的請求, dispatcherServlet 的存在降低了組件之間的耦合性。

HandlerMapping:處理器映射器(三大組件之一)

HandlerMapping 負責根據用戶請求找到 Handler 即處理器, SpringMVC 提供了不同的映射器實現不同的映射方式,例如:配置文件方式,實現接口方式,註解方式等

Handler:處理器

它就是我們開發中要編寫的具體業務控制器。由 DispatcherServlet 把用戶請求轉發到 Handler。由Handler 對具體的用戶請求進行處理。

​​​​​​​HandlAdapter:處理器適配器(三大組件之一)

調用:處理器映射器。

通過 HandlerAdapter 對處理器進行執行,這是適配器模式的應用,通過擴展適配器可以對更多類型的處理器進行執行。

​​​​​​​ViewResolver:視圖解析器(三大組件之一)

View Resolver 負責將處理結果生成 View 視圖, View Resolver 首先根據邏輯視圖名解析成物理視圖名即具體的頁面地址,再生成 View 視圖對象,最後對 View 進行渲染將處理結果通過頁面展示給用戶。

​​​​​​​View:視圖

SpringMVC 框架提供了很多的 View 視圖類型的支持,包括: jstlViewfreemarkerViewpdfView等。我們最常用的視圖就是 jsp。一般情況下需要通過頁面標籤或頁面模版技術將模型數據通過頁面展示給用戶,需要由程序員根據業務需求開發具體的頁面

​​​​​​​<mvc:annotation-driven>說明

SpringMVC 的各個組件中,處理器映射器、處理器適配器、視圖解析器稱爲 SpringMVC 的三大組件。使 用 <mvc:annotation-driven> 自 動 加 載 RequestMappingHandlerMapping ( 處 理 映 射 器 ) 和RequestMappingHandlerAdapter ( 處 理 適 配 器 ) , 可 用 在 SpringMVC.xml 配 置 文 件 中 使 用<mvc:annotation-driven>替代註解處理器和適配器的配置。

 

源碼分析:請求參數綁定流程

 1、我們現在web.xml中找到DispatcherServlet的配置

2、在類DispatcherServlet找到doDispatch方法

// 下面這段代碼,就是 處理器適配器執行處理器,也就是真正執行處理器。

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

3、然後找到HandlerAdapter接口中的handle方法

然後找到HandlerAdapter接口的實現類AbstractHandlerMethodAdapter(也就是RequestMappingHandlerAdapter父類) 

適配器內部執行處理方法(在實現類RequestMappingHandlerAdapter中實現)

4、接着進去AbstractHandlerMethodAdapter方法handle的返回值handleInternal方法,發現handleInternal是一個抽象方法。

AbstractHandlerMethodAdapter的子類RequestMappingHandlerAdapter對handleInternal方法進行了重寫。

RequestMappingHandlerAdapter對handleInternal方法中調用了invokeAndHandle

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,

HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

......

invocableMethod.invokeAndHandle(webRequest, mavContainer);

     ......

}

5、接着來到類ServletInvocableHandlerMethod中的方法invokeAndHandle

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,

Object... providedArgs) throws Exception {

// 處理請求參數(將請求的參數數據,對應轉換到方法形參上)

Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

          ......

6、接着來到類InvocableHandlerMethod類的invokeForRequest方法

public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,

Object... providedArgs) throws Exception {

// 處理請求參數轉換方法

Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

......

}

 

7、最後來到類InvocableHandlerMethod的方法getMethodArgumentValues

private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,

Object... providedArgs) throws Exception {

//獲取當前執行方法的形參個數

MethodParameter[] parameters = getMethodParameters();

    // 定義返回結果對象數組(參數對象)

Object[] args = new Object[parameters.length];

for (int i = 0; i < parameters.length; i++) {

// 當前循環處理的第i個參數(主要有形參類型信息)

MethodParameter parameter = parameters[i];

parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);

// 給形參參數賦值(providedArgs參數是list)

// Attempt to resolve a method parameter from the list of provided argument values.

args[i] = resolveProvidedArgument(parameter, providedArgs);

if (args[i] != null) {

continue;

}

if (this.argumentResolvers.supportsParameter(parameter)) {

try {

// 給形參參數賦值(簡單類型、pojo類型,數組)

// 1.根據形參的類型,創建對象

// 2.將request中的參數數據,對應設置到形參上

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;

 

源碼分析:模型數據響應到jsp/html頁面流程

1、我們繼續來到類DispatcherServlet(doDispatch方法中),也就是處理執行結果

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

 

2、接着繼續來到類DispatcherServlet的processDispatchResult方法,這裏面進行視圖渲染。

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
		......
		// Did the handler return a view to render?
		if (mv != null && !mv.wasCleared()) {
// 視圖渲染
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
		......

 

3、緊接着來到類DispatcherServlet

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
		// Determine locale for request and apply it to the response.
		Locale locale = this.localeResolver.resolveLocale(request);
		response.setLocale(locale);
		......
		try {
			// 視圖渲染
			view.render(mv.getModelInternal(), request, response);
		}
		catch (Exception ex) {
			......
		}

 

4、接着來到類AbstractView類中的render的方法,將模型數據mergedModel設置到request中

public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
		if (logger.isTraceEnabled()) {
			logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
				" and static attributes " + this.staticAttributes);
		}

		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
		prepareResponse(request, response);
// 將模型數據mergedModel,設置到request中
		renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

 

5、接着,來到類InternalResourceView

protected void renderMergedOutputModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

		// Expose the model object as request attributes.
		exposeModelAsRequestAttributes(model, request);
		......
}

 

6、最後來到類AbstractView,設置模型數據到request中

protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
// 循環拿到模型map中所有的key/value
		for (Map.Entry<String, Object> entry : model.entrySet()) {
			String modelName = entry.getKey();
			Object modelValue = entry.getValue();
			if (modelValue != null) {
// 設置模型數據到request對象中
				request.setAttribute(modelName, modelValue);
				if (logger.isDebugEnabled()) {
					logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
							"] to request in view with name '" + getBeanName() + "'");
				}
			}
			else {
				request.removeAttribute(modelName);
				if (logger.isDebugEnabled()) {
					logger.debug("Removed model object '" + modelName +
							"' from request in view with name '" + getBeanName() + "'");
				}
			}
		}

 

參考鏈接:

https://www.cnblogs.com/leskang/p/6101368.html

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