章節(jié)目錄
- Spring MVC DispatcherServlet 與 HttpServlet 關(guān)系類圖
- Spring MVC 源碼分析Request 請(qǐng)求映射驱负、執(zhí)行、視圖解析流程
- 總結(jié)-Spring MVC 運(yùn)行流程圖
1.Spring MVC DispatcherServlet 與 HttpServlet 關(guān)系類圖
1.1 什么是DispatcherServlet
源碼注釋如下所示:
Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers or HTTP-based remote service exporters. Dispatches to registered handlers for processing a web request, providing convenient mapping and exception handling facilities.
譯文如下:
DispatcherServlet 是HTTP請(qǐng)求處理程序/控制器的中央調(diào)度程序(將請(qǐng)求映射到具體處理器(handler)上 )娱局,例如用于Web UI控制器或基于HTTP的遠(yuǎn)程服務(wù)導(dǎo)出器(webService)耻涛,調(diào)度器會(huì)將請(qǐng)求路由至已經(jīng)注冊(cè)好的具體的hadler,使得handler可以處理執(zhí)行相關(guān)的web請(qǐng)求奏赘,提供了請(qǐng)求與處理器之間的映射關(guān)系功能梁只,其實(shí)就是路由映射功能监嗜。
1.2 什么是HttpServlet
HttpServlet 是處理相關(guān)基于Http請(qǐng)求的處理程序音五,請(qǐng)求的相關(guān)信息被封裝成
HttpServletRequest
對(duì)象苍蔬,其中Service() 方法通過獲取 HttpServletRequest 中的方法名 如GET
惭蟋、POST
、PUT
等 request-method信息的獲取我碟,去invoke具體的doGet()
放案、doPost()
、doPut()
方法矫俺,最終將執(zhí)行完業(yè)務(wù)邏輯獲取到的處理數(shù)據(jù)通過HttpServletResponse
對(duì)象返回給客戶端吱殉。所以最終request請(qǐng)求結(jié)果還是從HttpServlet中的service()返回的
那么這兩者之間有什么關(guān)系呢?
如下圖所示DispatcherServlet與HttpServlet之間的類圖關(guān)系:
其中最重要的是FrameworkServlet厘托。
源碼注釋如下:
Base servlet for Spring's web framework. Provides integration with
a Spring application context, in a JavaBean-based overall solution.
譯文如下:
Spring web 框架中的基礎(chǔ)Servlet友雳,將Spring 相關(guān)的ApplicationContext 集成進(jìn)來。方便我們?cè)诤笃谑褂肧pring IOC 容器中注冊(cè)的各種屬性的類對(duì)象铅匹。
FrameworkServlet 整合Spring WebApplicationContext 對(duì)象源碼如下:
/**
* Initialize and publish the WebApplicationContext for this servlet.
* <p>Delegates to {@link #createWebApplicationContext} for actual creation
* of the context. Can be overridden in subclasses.
* @return the WebApplicationContext instance
* @see #FrameworkServlet(WebApplicationContext)
* @see #setContextClass
* @see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
...
return wac;
}
其中獲取web應(yīng)用程序上下文的代碼段為:
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
FrameworkServlet 對(duì) extends 自 HttpServlet 的service()方法進(jìn)行了override()
super.service()
即調(diào)用HttpServlet中的Service()方法
可以看到Service()方法根據(jù)request.method 去調(diào)用具體的doxxx()
方法押赊,這里FrameworkServlet 對(duì) doxxx()
方法也進(jìn)行了override()。
如下為FrameworkServlet 中doGet()
方法源碼
其中的processRequest()
方法源碼如下所示:
/**
* Process this request, publishing an event regardless of the outcome.
* <p>The actual event handling is performed by the abstract
* {@link #doService} template method.
*/
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);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
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);
}
}
- 其中最重要的是doService()方法伊群,這個(gè)doService()方法被聲明為抽象方法考杉,在DispatcherServlet 做具體實(shí)現(xiàn)。
源碼實(shí)現(xiàn)如下:
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;
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("org.springframework.web.servlet")) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
//為請(qǐng)求設(shè)置具體的屬性舰始。
// 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 {
//調(diào)用doDispatch()崇棠,將請(qǐng)求分配給具體的handler去處理。實(shí)際上第二節(jié)會(huì)具體分析doDispatch()方法
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);
}
}
}
}
1.3 Spring Web 對(duì)Request的執(zhí)行流程
所以請(qǐng)求的整個(gè)執(zhí)行流程依據(jù)之前的HttpServlet知識(shí)積累(未debug)可以大致總結(jié)如下(第二節(jié)會(huì)debug源碼丸卷,用來驗(yàn)證我們總結(jié)的這個(gè)流程)
即:
1.請(qǐng)求到達(dá)dispatcherServlet枕稀,(非初次請(qǐng)求,初次請(qǐng)求會(huì)涉及dispatcherServlet初始化,調(diào)用init()方法)谜嫉。
2.dispatcherServlet 執(zhí)行service()方法萎坷,因Dispatcher類繼承FrameworkServlet,所以調(diào)用父類的service()方法沐兰。
3.service()調(diào)用FrameworkServlet 中具體的doxxx()方法
4.FrameworkServlet 具體的 doxxx()方法調(diào)用processRequest()方法哆档。
5.processRequest()方法調(diào)用dispatcherServlet 的 doService()方法。
6.dispatcherServlet 的 doService()方法調(diào)用doDispatch()方法住闯。
注意:上述根方法-service()方法被servlet容器 Servlet container顯示調(diào)用瓜浸。
2.Spring MVC 源碼分析Request 請(qǐng)求映射、執(zhí)行比原、視圖解析流程
簡(jiǎn)單的helloword級(jí)別的web項(xiàng)目,搭建方式可以略過插佛。主要是debug開始的地方我們需要確定,因?yàn)橛蠬ttpServlet源碼分析的積累量窘,那么我們直接在DispatcherServlet中的Service方法中打斷點(diǎn)就可以了雇寇,因?yàn)镈ispatcherServlet繼承了FrameworkServlet,F(xiàn)rameworkServlet對(duì)HttpServlet中的service()方法進(jìn)行了override,所以程序入口斷點(diǎn)應(yīng)該打在FrameworkServlet 中的service() 方法锨侯,接下來就是實(shí)操演示:
注意:本源碼分析的是Spring 4.1版本
2.0 debug的目的
了解 request 到具體 handler 的執(zhí)行流程嫩海。
2.1 FrameworkServlet 中 service() 打斷點(diǎn)
2.2 開啟debug模式
2.3 開始debug
執(zhí)行父類service()方法
執(zhí)行doGet()方法
執(zhí)行processRequest()方法
執(zhí)行doService()方法
執(zhí)行doDispatch方法
獲取請(qǐng)求對(duì)應(yīng)的handler
繼續(xù)debug hm.getHandler(request)
看看這其中發(fā)生了什么?
通過SimpleUrlHandlerMapping, 發(fā)現(xiàn)并不能獲取到 對(duì)應(yīng)的 handler(HandlerExcutionChain對(duì)象)识腿,
繼續(xù)foreach
通過EndpointHandlerMapping, 發(fā)現(xiàn)并不能獲取到 對(duì)應(yīng)的 handler(HandlerExcutionChain對(duì)象)出革,
繼續(xù)foreach
最終我們通過RequestMappingHandlerMapping對(duì)象獲取到了對(duì)應(yīng)的handler對(duì)象。
可以看下handler對(duì)象是什么東東渡讼?
所以handlerExcutionChain 對(duì)象 包含有handler對(duì)象骂束、interceptor對(duì)象。
到此我們通過requestMappingHandlerMapping 獲取到了請(qǐng)求對(duì)應(yīng)的handler成箫。
接下來需要以handler為參數(shù)獲取真正處理請(qǐng)求的handlerAdaptor
接下來執(zhí)行 handlerAdaptor 中 handler()
方法
返回mv展箱,需要注意的是,返回mv 其實(shí)是對(duì)Controller 中業(yè)務(wù)方法的調(diào)用其實(shí)使用到了反射蹬昌。
注意在返回mv之前 通過handlerExcutionChain對(duì)象可以調(diào)用applyPreHandler 方法混驰,可以在返回mv之前做預(yù)先處理工作。
返回mv之后皂贩,可以通過handlerExcutionChain對(duì)象可以調(diào)用applyPreHandler 方法對(duì)返回的mv做修改栖榨。我們只需要實(shí)現(xiàn) handlerInterceptor類并實(shí)現(xiàn)配置就可以了。
最后一步執(zhí)行視圖渲染的工作明刷,這一步是在dispatcherServlet中完成的婴栽。
最終請(qǐng)求結(jié)果
注意:由于返回結(jié)果為String 類型的value,不涉及視圖解析,所以render 方法并沒有執(zhí)行辈末。
3.總結(jié)-Spring MVC 運(yùn)行流程圖
對(duì)上述流程圖的解釋:
- 用戶發(fā)起請(qǐng)求到前端控制器(Controller)
- 前端控制器沒有處理業(yè)務(wù)邏輯的能力愚争,需要找到具體的模型對(duì)象處理(Handler),到處理器映射器(HandlerMapping)中查找Handler對(duì)象(Model)挤聘。
- HandlerMapping返回執(zhí)行鏈轰枝,包含了2部分內(nèi)容: ① Handler對(duì)象、② 攔截器數(shù)組
- 前端處理器通過處理器適配器包裝后執(zhí)行Handler對(duì)象组去。
- 處理業(yè)務(wù)邏輯鞍陨。
- Handler處理完業(yè)務(wù)邏輯,返回ModelAndView對(duì)象从隆,其中view是視圖名稱诚撵,不是真正的視圖對(duì)象。
- 將ModelAndView返回給前端控制器广料。
- 視圖解析器(ViewResolver)返回真正的視圖對(duì)象(View)。
- (此時(shí)前端控制器中既有視圖又有Model對(duì)象數(shù)據(jù))前端控制器根據(jù)模型數(shù)據(jù)和視圖對(duì)象幼驶,進(jìn)行視圖渲染艾杏。
- 返回渲染后的視圖(html/json/xml)返回。
- 給用戶產(chǎn)生響應(yīng)盅藻。