代碼分析Spring MVC的工作原理
遺留問(wèn)題
在關(guān)于利用maven搭建ssm的博客,我們一起來(lái)探討下問(wèn)的最多的問(wèn)題中,我遺留了一個(gè)問(wèn)題:Spring mvc是何時(shí)、何地、如何將Model中的屬性綁定到哪個(gè)作用域,這里的作用域指的是Servlet的四大作用域;不了解問(wèn)題背景的可以回過(guò)頭去看看我的上篇博文。
明確的解答我會(huì)放到最后,在解答問(wèn)題之前,我先和大家一起來(lái)捋一捋Spring mvc的工作原理。廢話不多說(shuō),開(kāi)始我們神秘的探險(xiǎn)之旅!
應(yīng)用示例
在講工作原理之前,我們先看一個(gè)簡(jiǎn)單的spring mvc(ssm)示例,以及實(shí)現(xiàn)的效果
工程代碼地址:ssm-web

工程結(jié)構(gòu)與效果如上所示,我們不做過(guò)多的探究,我們打起精神往下看本篇的重點(diǎn)
工作原理
準(zhǔn)備 - 資源的加載與初始化
1、DispatcherServlet 靜態(tài)初始化
DispatcherServlet中有如下靜態(tài)塊
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
}
}
這里會(huì)將DispatcherServlet.properties中的內(nèi)容讀取到DispatcherServlet的屬性:private static final Properties defaultStrategies中,DispatcherServlet.properties內(nèi)容如下
# Default implementation classes for DispatcherServlet's strategy interfaces. # Used as fallback when no matching beans are found in the DispatcherServlet context. # Not meant to be customized by application developers. org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
指定了DispatcherServlet策略接口的默認(rèn)實(shí)現(xiàn),后續(xù)DispatcherServlet初始化策略的時(shí)候會(huì)用到
2、interceptor定義的加載
spring啟動(dòng)過(guò)程中會(huì)調(diào)用InterceptorsBeanDefinitionParser的parse方法來(lái)解析出我們自定義的interceptor定義,封裝成MappedInterceptor類型的bean定義,并放到spring容器中;我們可以簡(jiǎn)單的認(rèn)為spring容器中已經(jīng)存在了我們自定義的interceptor的bean定義
3、DispatcherServlet初始化策略:initStrategies
DispatcherServlet的繼承圖如下

DispatcherServlet是一個(gè)Servlet,tomcat啟動(dòng)過(guò)程中會(huì)調(diào)用其init方法,一串的調(diào)用后,會(huì)調(diào)用DispatcherServlet的initStrategies方法
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
實(shí)例化步驟1中的默認(rèn)實(shí)現(xiàn),并填充到DispatcherServlet各個(gè)屬性值中
4、DefaultAnnotationHandlerMapping的攔截器初始化
DispatcherServlet.properties種指定了兩個(gè)默認(rèn)的HandlerMapping:BeanNameUrlHandlerMapping、DefaultAnnotationHandlerMapping,這兩者的類繼承圖如下(我們暫時(shí)只關(guān)注DefaultAnnotationHandlerMapping)

DefaultAnnotationHandlerMapping間接實(shí)現(xiàn)了ApplicationContextAware,那么在DefaultAnnotationHandlerMapping實(shí)例初始化過(guò)程中,會(huì)調(diào)用setApplicationContext(ApplicationContext applicationContext)方法,一串調(diào)用后,會(huì)來(lái)到AbstractUrlHandlerMapping的initApplicationContext()
@Override
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.mappedInterceptors);
initInterceptors();
}
初始化了DefaultAnnotationHandlerMapping的攔截器:interceptor
我們來(lái)看下具體的初始化過(guò)程,看看上面的順序是否只是我個(gè)人的臆想?

可以看到,初始化順序就是我們上面說(shuō)的,不是我個(gè)人的意淫;此時(shí)的DefaultAnnotationHandlerMapping中有我們自定義的MyInterceptor。初始化過(guò)程我們需要關(guān)注的就是上述這些,下面我們一起看看具體請(qǐng)求的過(guò)程
請(qǐng)求的處理
請(qǐng)求從servlet的service開(kāi)始,一路到DispatcherServlet的doDispatch,如下圖

doDispatch
/**
* Process the actual dispatching to the handler. 將請(qǐng)求分發(fā)到具體的handler,也就是我們的controller
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
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. 決定哪個(gè)handler來(lái)處理當(dāng)前的請(qǐng)求
// mappedHandler是由handler和interceptor集合組成的一個(gè)執(zhí)行鏈,有點(diǎn)類似FilterChain
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request. 決定哪個(gè)adapter來(lái)處理當(dāng)前的請(qǐng)求
// handlerMapping是找出適配的handler,而真正回調(diào)handler的是adapter
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()) {
String requestUri = urlPathHelper.getRequestUri(request);
logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// handler的前置處理,也就是調(diào)用適配當(dāng)前url的interceptor的preHandler方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// Actually invoke the handler. 真正調(diào)用handler
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
applyDefaultViewName(request, mv);
// handler的后置處理,也就是調(diào)用適配當(dāng)前url的interceptor的postHandler方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
// 處理handler返回的結(jié)果,會(huì)調(diào)用適配當(dāng)前url的interceptor的afterCompletion方法
// 這里會(huì)將響應(yīng)結(jié)果返回給請(qǐng)求者
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
return;
}
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
handlerMapping具體如何找到匹配當(dāng)前url的handler(一般而言就是我們的controller)、handlerAdapter具體如何回調(diào)真正的handler,有興趣的可以自行去跟下,我就不跟了。我們具體看下processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 這個(gè)與我們最初的疑問(wèn)有關(guān)
processDispatchResult

可以看到model中的persons會(huì)被設(shè)置到request的attributes中,然后轉(zhuǎn)發(fā)請(qǐng)求到show_person.jsp,轉(zhuǎn)發(fā)過(guò)程中request作用域的變量仍然有效,所以show_person.jsp中的jstl標(biāo)簽和el表達(dá)式能夠取到persons變量,最后將show_person.jsp中的內(nèi)容填充好之后的靜態(tài)內(nèi)容返回給請(qǐng)求者;至此就完成了一次請(qǐng)求的響應(yīng)
問(wèn)題解答
回到我們開(kāi)篇的疑問(wèn):Spring mvc是何時(shí)、何地、如何將Model中的屬性綁定到哪個(gè)作用域?想必大家已經(jīng)知道答案了
Controller中的model、ModelMap的注入由spring mvc完成,這個(gè)不是請(qǐng)求傳入的參數(shù),用于綁定變量到Servlet作用域;默認(rèn)情況下,在DispatcherServlet調(diào)用了真正的handler之后,將結(jié)果返回給請(qǐng)求者的過(guò)程中,將model、modelMap中的變量設(shè)置到了request的attributes中,轉(zhuǎn)發(fā)的過(guò)程中,request中的變量仍然有效,所以show_person.jsp中能取到persons這個(gè)變量,自此疑問(wèn)得到解答
總結(jié)
1、Spring MVC工作原理圖
圖是用的別人的,具體是誰(shuí)的我也不記得了(捂臉)

2、DefaultAnnotationHandlerMapping在spring3.2中被廢棄,替換成了RequestMappingHandlerMapping
相關(guān)文章
Java?Stream流的常見(jiàn)生成和操作方法總結(jié)
從Java1.8開(kāi)始提出了Stream流的概念,本文將通過(guò)示例為大家詳細(xì)講解一下Stream流的常見(jiàn)生成和操作方法,感興趣的小伙伴可以了解一下2022-09-09
SpringBoot+MySQL+Jpa實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)的增刪改查和分頁(yè)詳解
這篇文章主要介紹了SpringBoot+MySQL+Jpa實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)的增刪改查和分頁(yè)詳解,需要的朋友可以參考下2020-02-02
關(guān)于swagger配置及踩坑@Api參數(shù)postion無(wú)效解決接口排序問(wèn)題
這篇文章主要介紹了關(guān)于swagger配置及踩坑@Api參數(shù)postion無(wú)效解決接口排序問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06
從最基本的Java工程搭建SpringMVC+SpringDataJPA+Hibernate
本文會(huì)介紹從一個(gè)最基本的java工程,到Web工程,到集成Spring、SpringMVC、SpringDataJPA+Hibernate,本文介紹的非常詳細(xì),具有參考借鑒價(jià)值,感興趣的朋友一起學(xué)習(xí)吧2016-05-05
Spring Cloud Feign組成配置過(guò)程解析
這篇文章主要介紹了Spring Cloud Feign組成配置過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
淺談java字符串比較到底應(yīng)該用==還是equals
這篇文章主要介紹了淺談java字符串比較到底應(yīng)該用==還是equals,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
springboot表單提交之validator校驗(yàn)
在前臺(tái)表單驗(yàn)證的時(shí)候,通常會(huì)校驗(yàn)一些數(shù)據(jù)的可行性,比如是否為空,長(zhǎng)度,身份證,郵箱等等,這篇文章主要給大家介紹了關(guān)于springboot表單提交之validator校驗(yàn)的相關(guān)資料,需要的朋友可以參考下2021-05-05

