前言
轉(zhuǎn)載請注明來源
? ? ? ? 上一次寫博客已經(jīng)是5月份的事了,其實是蠻想抽時間寫寫,沉淀一下自己,無奈,天天加班加點,人都傻了,心累.真心覺得,對于一個搞技術(shù)的人來說,不能只是工作工作,雖然這是必要的(畢竟大多數(shù)人還是要吃飯的),但是經(jīng)常的周期性的沉淀下自己,總結(jié)自己一段時間的收獲是分重要的.
? ? ? ? 話不多說了,本篇主要是針對一下springMVC的處理流程及實現(xiàn)原理通過debug方式層層深入理解.
? ? ? ? 整個篇幅過長,偏向于個人的一個debug過程的展示,以及個人理解的筆記,感興趣的可以參照我的流程進(jìn)行debug,加上一些我個人的理解與提示,多debug幾遍流程,相信看的人都能得到自己的理解.
服務(wù)器原理簡介
springMVC作為現(xiàn)在Java這塊非常流行的MVC框架,基本上只要會spring就可以無縫銜接springMVC,
那么springMVC到底是如何工作的呢,這里不得不提的是服務(wù)器,相信在Java Web這塊的程序員們都非常的熟悉.
正好最近擼了點tomcat源碼的皮毛,了解了類tomcat等的web服務(wù)器的工作原理,有興趣的可以吃一吃我的安利
<<how tomcat works>> 這本書.
那么為什么需要服務(wù)器呢?
簡單來講,服務(wù)器通過ServerSocket獲取到Http請求然后對其解析并封裝成Request和Response對象,
然后將其交給Servlet容器進(jìn)行處理,選擇對應(yīng)的Servlet處理請求,返回結(jié)果(實際上是很復(fù)雜,作為一個web
程序員,這個真的是應(yīng)該好好了解研究的).
那么為什么tomcat和springmvc可以結(jié)合起來呢,最最核心的原因是他們都是基于Servlet規(guī)范的,
由于Servlet規(guī)范,他們可以互相通信(服務(wù)器和SpringMVC的結(jié)合在debug時將會簡單體現(xiàn)).
SpringMVC
開始詳解SpringMVC了.
1、web.xml
web.xml中配置了最重要的ContextLoaderListener以及DispatchServlet.
ContextLoaderListener用于啟動web容器時,自動裝ApplicationContext的配置信息,
由于 ContextLoaderListener實現(xiàn)了ServletContextListener,所以在web容器啟動應(yīng)用時,
創(chuàng)建ServletContext對象,每個應(yīng)用都有一個對應(yīng)的ServletContext對象,ServletContext在應(yīng)用關(guān)閉
時將會銷毀,在啟動時,可以向ServletContext中添加WebApplicationContext,這個在ServletContext
整個運行期間都是可見的.
DispatchServlet是SpringMVC最重要的一個類,它實現(xiàn)了Servlet,用于對請求做邏輯處理,相應(yīng)的
ContextLoaderListener實際上只是為了創(chuàng)建WebApplicationContext,DispatchServlet則是負(fù)責(zé)了
SpringMVC中對客戶端請求的邏輯處理,我們的每個請求都是經(jīng)過DispatchServlet進(jìn)行處理,調(diào)用對應(yīng)的
邏輯實現(xiàn)(Controller中對應(yīng)的請求的方法),返回視圖,所以說DispatchServlet是SpringMVC中最重要的一個
類一點不為過.
2、啟動
終于要接觸代碼了哲银,下面就對springMVC啟動過程的流程與邏輯進(jìn)行debug。
本文采用的是Jetty服務(wù)器.
如下所示,web.xml中配置的監(jiān)聽器類:
可以看到,該類實現(xiàn)了ServletContextListener,ServletContextListener接口,當(dāng)系統(tǒng)啟動時,將會調(diào)用ServletContextListener的實現(xiàn)類的contextInitialized方法,用于在初始化時加入一些屬性,這樣就可以在全局范圍內(nèi)調(diào)用這些屬性.
debug啟動web工程,直入主題,在contextInitialized方法出打斷點:
繼續(xù)跳入父類ContextLoader中的initWebApplicationContext方法中進(jìn)行WebApplicationContext的創(chuàng)建,WebApplicationContext是繼承自ApplicationContext,在ApplicationContext的基礎(chǔ)上增加了一些特定于Web的操作及屬性,詳細(xì)可以自行查看.
下面是整個initWebApplicationContext方法的詳細(xì)代碼,加入了一點個人理解的注釋:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//驗證context是否已經(jīng)存在,
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
//初始化 WebApplicationContext
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
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 ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//刷新上下文環(huán)境
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//將WebApplicationContext記錄在servletContext中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
//映射當(dāng)前的類加載器與context實例到全局變量currentContextPerThread中
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
debug到下圖280行,創(chuàng)建WebApplicationContext,
debug 跳入createWebApplicationContext(servletContext)方法中,
determineContextClass方法返回一個WebApplicationContext 接口的實現(xiàn)類,否則默認(rèn)返回XmlWebApplicationContext 或者一個指定的context
此處有一個defaultStrategies,可以看下圖,ContextLoader有一個static代碼塊,
通過以上我們可以得知,在ContextLoader類加載的時候就先讀取了ContextLoader同級目錄下的ContextLoader.properties配置文件,在初始化WebApplicationContext時,根據(jù)其中的配置提取WebApplicationContext接口的實現(xiàn)類,并根據(jù)這個實現(xiàn)類通過反射的方式進(jìn)行實例的創(chuàng)建.
接著debug走,將WebApplicationContext記錄在servletContext中
映射當(dāng)前的類加載器與context實例到全局變量currentContextPerThread中
初始化servlet
SpringMVC通過DispatcherServlet對請求進(jìn)行處理,而DispatcherServlet是實現(xiàn)了Servlet接口的,而servlet接口是基于servlet規(guī)范編寫的一個Java類,實際上一個servlet會經(jīng)歷三個階段:初始化階段麻诀、運行階段、銷毀階段,也就是我們熟知的servlet 的init、doGet/doPost颂砸、destroy這三個方法的執(zhí)行,而dispatchservlet是實現(xiàn)了servlet接口的疑务,那么必然也會經(jīng)歷這三個階段,下面是DispatchServlet類的父類結(jié)構(gòu)圖:
可以看到dispatServlet的父類FrameworkServlet梗醇,F(xiàn)rameworkServlet又繼承了HttpServletBean知允,實際上整個servlet的三個階段都在這三個類中做了具體的實現(xiàn)叙谨。
初始化階段在HttpServletBean中可以找到相應(yīng)的方法,
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
try {
//解析init parameters 并封裝到PropertyValues中
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
//將當(dāng)前這個Servlet類轉(zhuǎn)化為BeanWrapper,從而能以spring的方式對init parameters的值進(jìn)行注入
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
//注冊自定義屬性編輯器,一旦遇到resource類型的屬性將會使用ResourceEditor進(jìn)行解析
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//空實現(xiàn),留給子類覆蓋
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
// Let subclasses do whatever initialization they like.
//在FrameworkServlet中覆蓋了該方法
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
debug進(jìn)入init方法中,跳入FrameworkServlet的initServletBean方法中,如下圖
可以看到,最重要的就是this.webApplicationContext = initWebApplicationContext();
這段代碼,這個方法的作用是創(chuàng)建或刷新WebApplicationContext實例,并對servlet功能所使用的變量進(jìn)行初始化:
可以看到上圖這段代碼將不會執(zhí)行,直接到if(wac == null)中去了,
跳入findWebApplicationContext方法中,這個方法是用于根據(jù)ContextAttribute屬性加載WebApplicationContext,但這里可以看到ContextAttribute為空,所以這段代碼最終返回的還是null
接著走下一個if,
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
//獲取servlet初始化參數(shù),如果沒有則默認(rèn)為XMLWebApplicationContext.class
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
//通過反射的方式實例化contextClass
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
//設(shè)置servlet環(huán)境
wac.setEnvironment(getEnvironment());
//這個parent使用的就是ContextLoaderListener初始化時創(chuàng)建的那個root WebApplicationContext
wac.setParent(parent);
//獲取ContextConfigLocation屬性,配置在servlet初始化參數(shù)中
wac.setConfigLocation(getContextConfigLocation());
//初始化spring環(huán)境包括加載配置文件等
configureAndRefreshWebApplicationContext(wac);
return wac;
}
上面是對createWebApplicationContext方法的一個詳細(xì)介紹,下面debug一步一步看這個方法的邏輯:
接著創(chuàng)建wac并配置了servlet初始化的一些參數(shù)后,初始化整個spring的環(huán)境:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
ServletContext sc = getServletContext();
if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
// Servlet <= 2.4: resort to name specified in web.xml, if any.
String servletContextName = sc.getServletContextName();
if (servletContextName != null) {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName +
"." + getServletName());
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getServletName());
}
}
else {
// Servlet 2.5's getContextPath available!
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()) + "/" + getServletName());
}
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// the wac environment's #initPropertySources will be called in any case when
// the context is refreshed; do it eagerly here to ensure servlet property sources
// are in place for use in any post-processing or initialization that occurs
// below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
//加載配置文件及整合parent到wac中
wac.refresh();
}
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
上述代碼每一步都有自帶的注釋,相信很容易就能理解,其中的onRefresh方法是FrameworkServlet類中提供的模板方法,在子類dispatchservlet中進(jìn)行了重寫,其主要作用就是為了刷新spring在web功能實現(xiàn)中必須使用的全局變量,這些變量在接下來的處理請求響應(yīng)中將會用到,如下,就不詳細(xì)介紹了
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
/**
* 初始化MultipartResolver,主要用于處理文件上傳,默認(rèn)情況下,spring是沒有Multipart處理的
* 需要用戶自己去配置,常用配置如下:
* <bean id="multipartResolver"
* class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
* <property name="defaultEncoding" value="utf-8"></property>
* <property name="maxUploadSize" value="10485760000"></property>
* <property name="maxInMemorySize" value="40960"></property>
* </bean>
*/
initMultipartResolver(context);
/**
* 初始化LocaleResolver,用于國際化配置
*/
initLocaleResolver(context);
/**
* 初始化ThemeResolver,主題theme用于控制網(wǎng)頁風(fēng)格
*/
initThemeResolver(context);
/**
* 初始化HandlerMappings,dispatchservlet會將Request請求提交給HandlerMapping然后HandlerMapping
* 根據(jù)webapplicationcontext的配置來返回給dispatchservlet 相應(yīng)的controller
* 默認(rèn)情況下,springMVC會加載當(dāng)前系統(tǒng)中所有實現(xiàn)了HandlerMapping接口的bean
*/
initHandlerMappings(context);
/**
* 初始化HandlerAdapters,適配器設(shè)計模式,HandlerAdapter適配當(dāng)前請求到對應(yīng)的控制器,
*
*/
initHandlerAdapters(context);
/**
* 初始化HandlerExceptionResolvers,
*/
initHandlerExceptionResolvers(context);
/**
* 初始化RequestToViewNameTranslator,當(dāng)controller處理方法沒有返回一個view或者邏輯視圖名稱時,并且
* 沒有在該方法中直接往response的輸出流中寫數(shù)據(jù)時,就會通過RequestToViewNameTranslator接口的實現(xiàn)類
* 來提供一個約定好的邏輯視圖名稱供使用,spring中提供了一個默認(rèn)的實現(xiàn)類
*/
initRequestToViewNameTranslator(context);
/**
* 初始化ViewResolvers,當(dāng)controller將請求處理結(jié)果放入到modelandview中后,dispatchservlet會根據(jù)
* modelandview選擇合適的視圖進(jìn)行渲染,springMVC通過ViewResolver接口定義的resolverViewName方法
* 根據(jù)合適的viewname創(chuàng)建對應(yīng)的view.
* 配置如下:
* <bean
* class="org.springframework.web.servlet.view.InternalResourceViewResolver">
* <property name="prefix" value="/WEB-INF/views/" />
* <property name="suffix" value=".jsp" />
* </bean>
*/
initViewResolvers(context);
/**
* 初始化FlashMapManager用于管理,FlashMapManager用于管理FlashMap,FlashMap用于保持flash attributes,
* flash attributes提供了一個請求存儲屬性,在使用請求重定向時非常重要,flash attributes在重定向之前暫存
* 以便重定向之后還能使用,并立即刪除.
*/
initFlashMapManager(context);
}
創(chuàng)建完WebApplicationContext 并刷新成功后,接著走下一步
發(fā)布wac
到此,dispatchservlet初始化完成,整個web工程才算啟動完成.
處理請求響應(yīng)
完成了servlet的初始化過程后,現(xiàn)在可以進(jìn)行對請求的處理響應(yīng)過程了,打開瀏覽器地址欄輸入url;
http://localhost:8080/demo-idle-web/index.do
這個時候其實可以通過debug的信息簡略看下服務(wù)器是如何處理請求并與springMVC交互的:
上圖可以看到,從最下面的信息看起,可以看到j(luò)etty服務(wù)器先解析http請求,解析成HTTPServletRequest以及HTTPServletResponse后經(jīng)過一系列邏輯處理后將request 與response傳遞給servlet容器,然后容器選擇對應(yīng)的servlet進(jìn)行處理request 與 response,這個時候其實就傳遞到了springMVC中的DispatchServlet中去了.
接下來繼續(xù) debug:
繼續(xù),跳入了doGet方法中,
deGet/doPost都沒有直接對請求進(jìn)行處理,都是在processRequest方法中對請求進(jìn)行處理的:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
/**
* 為了保證當(dāng)前線程的LocaleContext屬性,RequestAttributes屬性可以再當(dāng)前請求完成后還能恢復(fù),
*/
//提取當(dāng)前線程LocaleContext屬性
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//根據(jù)當(dāng)前request創(chuàng)建對應(yīng)的localeContext
LocaleContext localeContext = buildLocaleContext(request);
//提取當(dāng)前線程對應(yīng)的RequestAttributes屬性
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//根據(jù)當(dāng)前request創(chuàng)建對應(yīng)的RequestAttributes
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
//將上述的根據(jù)request創(chuàng)建后的兩個屬性綁定到當(dāng)前線程
initContextHolders(request, localeContext, requestAttributes);
try {
//準(zhǔn)備工作做完后,具體的處理邏輯委托給了子類dispatchServlet中的doService方法進(jìn)行處理
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 {
//請求處理結(jié)束后恢復(fù)線程到原始狀態(tài)
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");
}
}
}
//請求處理結(jié)束后無論成功與失敗都發(fā)布事件通知
publishRequestHandledEvent(request, startTime, failureCause);
}
}
看了上面這段帶有注釋的代碼,相信對processRequest的處理邏輯應(yīng)該是比較清楚了,這里額外講一點東西(僅僅是個人所了解的一些知識,可能不完全對):
針對每個request請求,服務(wù)器都會分配一個線程進(jìn)行處理,線程也不是無限的,頻繁的創(chuàng)建銷毀線程,
進(jìn)行線程上下文切換是非常消耗資源的,所以針對這些請求進(jìn)行線程分配,一般來說都是通過線程池完成的,
所以, 在請求處理完成后,是需要恢復(fù)線程到原始狀態(tài)的,刪除掉前一個request請求遺留的信息
接著debug進(jìn)入doService方法中:
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
" processing " + request.getMethod() + " request for [" + requestUri + "]");
}
// 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)) {
logger.debug("Taking snapshot of request attributes before include");
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));
}
}
}
// 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()) {
return;
}
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
doService方法也是講具體的邏輯處理放入了doDispatch中,
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 {
//如果是MultipartContent類型的request則轉(zhuǎn)換成MultipartHTTPServletRequest類型的Request
processedRequest = checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// Determine handler for the current request.
//根據(jù)request信息尋找對應(yīng)的handler
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
//如果沒找到對應(yīng)的handler則通過response返回錯誤信息
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
//根據(jù)當(dāng)前的handler尋找對應(yīng)的handlerAdapter
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;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
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);
}
}
}
繼續(xù)跳入getHandler方法中,getHandler會通過request的信息從handlerMappings中提取對應(yīng)的handler,其實就是提取了當(dāng)前實例中的Controller的相關(guān)的信息,debug可以看到相關(guān)的信息:
{{[/index],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}
=
public java.lang.String com.axa.idle.controller.IdleController.heartBeatCode() throws java.lang.Exception}
可以看到 /index 與 controller中的方法IdleController.heartBeatCode() 有了一個映射關(guān)系,繼續(xù)debug,hm.getHandler這個方法中,這里就不花篇幅詳細(xì)解讀了,這一塊的邏輯處理挺長,但是都還挺簡單,容易理解,且源碼的注釋也是寫的比較詳細(xì),這里簡單介紹下這一過程,
通過Request中的url等信息去匹配對應(yīng)的controller,這里分一個直接匹配和通配符匹配的處理方式,
匹配完成后,將handler封裝成HandlerExecutionChain執(zhí)行鏈,然后往執(zhí)行鏈中加入攔截器,
以保證攔截器可以作用到目標(biāo)對象中.
看到這個返回的handler的信息:
接著debug:
看名字就知道是個適配器設(shè)計模式,看下具體的邏輯,簡單易懂,遍歷所有的handlerAdapters,選擇適配的適配器:
接著debug,處理last-modified 請求頭緩存,客戶端第一次訪問url時會添加一個last-modified 的響應(yīng)頭,客戶端第二次訪問url時,客戶端會向服務(wù)器發(fā)送請求頭"if-modified-since",詢問服務(wù)器該時間之后當(dāng)前請求的內(nèi)容是否修改過,如果無變化,則自動返回 304 狀態(tài)碼(只要響應(yīng)頭,內(nèi)容為空,節(jié)省服務(wù)器網(wǎng)絡(luò)帶寬).
繼續(xù)debug,攔截器攔截請求前置處理:
接著debug,處理邏輯:
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
debug step into 方法可以看到,跳入的是具體的哪個實現(xiàn)類(AbstractHandlerMethodAdapter):
看下該方法收到的具體的參數(shù)信息:
protected final ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
// Always prevent caching in case of session attribute management.
//
checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
}
else {
// Uses configured default cacheSeconds setting.
checkAndPrepare(request, response, true);
}
// Execute invokeHandlerMethod in synchronized block if required.
//需要在session內(nèi)的同步執(zhí)行
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
return invokeHandleMethod(request, response, handlerMethod);
}
}
}
//調(diào)用用戶邏輯
return invokeHandleMethod(request, response, handlerMethod);
}
上面這段代碼重點在最后一句invokeHandleMethod(request, response, handlerMethod),這里就是執(zhí)行具體的controller中的方法的邏輯了,該方法返回的是一個ModelAndView,這里的具體實現(xiàn)是通過將request解析以及提供的參數(shù)組合成controller中映射的方法所需要的參數(shù),利用反射的方式調(diào)用該方法邏輯,計算執(zhí)行結(jié)果,將返回結(jié)果再封裝到ModelAndView中,如下圖可以看到調(diào)用invokeHandleMethod方法會跳入controller中/index 映射的方法中去執(zhí)行邏輯
返回結(jié)果封裝到ModelAndView中,由于heartBeatCode方法并沒有將任何執(zhí)行結(jié)果放入model中,所以可以看到mv中view為index,model is {}:
接著debug:
applyDefaultViewName方法則是當(dāng)mv中沒有view的值時,采用之前初始化時這個方法中提供的信息:
/**
* 初始化RequestToViewNameTranslator,當(dāng)controller處理方法沒有返回一個view或者邏輯視圖名稱時,并且
* 沒有在該方法中直接往response的輸出流中寫數(shù)據(jù)時,就會通過RequestToViewNameTranslator接口的實現(xiàn)類
* 來提供一個約定好的邏輯視圖名稱供使用,spring中提供了一個默認(rèn)的實現(xiàn)類
*/
initRequestToViewNameTranslator(context);
這個時候mv已經(jīng)封裝好了,那么就是要做渲染視圖的事情了:
這段代碼邏輯篇幅有點長,這里就總結(jié)下resolveViewName實現(xiàn)了什么邏輯:
采用之前初始化時的ViewResolvers對視圖進(jìn)行解析:
/**
* 初始化ViewResolvers,當(dāng)controller將請求處理結(jié)果放入到modelandview中后,dispatchservlet會根據(jù)
* modelandview選擇合適的視圖進(jìn)行渲染,springMVC通過ViewResolver接口定義的resolverViewName方法
* 根據(jù)合適的viewname創(chuàng)建對應(yīng)的view.
* 配置如下:
* <bean
* class="org.springframework.web.servlet.view.InternalResourceViewResolver">
* <property name="prefix" value="/WEB-INF/views/" />
* <property name="suffix" value=".jsp" />
* </bean>
*/
initViewResolvers(context);
然后解析視圖名時看當(dāng)前的這個viewName是否在緩存中,在則直接從緩存中提取,提高效率,不在則直接創(chuàng)建該視圖,并且提供了對 redirect:xx 和 forward:xx 前綴的支持,最后向view中添加前綴以及后綴,并向view中添加了必要的屬性設(shè)置,view渲染完成后,接著是頁面跳轉(zhuǎn)了,
在renderMergedOutputModel方法中,主要就是完成了將model中的信息放入到Request中,這樣我們就可以在頁面中使用JSTL語法或者Request信息直接獲取的方式渲染頁面,這樣到達(dá)了我們通常在使用jsp頁面時采用JSTL的語法的方式獲取后臺返回過來的值渲染到頁面上.這一步最主要的就是通過將model中的值放入到Request中,這樣我們就可以在別的地方調(diào)用到這些值.看下頁面結(jié)果:
???????到此為止,我們就完成了整個springMVC處理Request請求響應(yīng)的過程,整個過程中略過了一些東西,像異常視圖處理,url錯誤處理等等.
總結(jié)
總結(jié)一下整個springMVC的處理流程:
ContextLoaderListener初始化WebApplicationContext ROOT,接著初始化servlet,
初始化WebApplicationContext以及一些web應(yīng)用中必須用到的屬性,初始化servlet完成后,
整個web應(yīng)用算是啟動成功了,接著開始處理請求,所有的請求都是通過dispatchservlet進(jìn)行處理的,
通過解析Request信息從handlermappings中找到對應(yīng)handler(通常來說就是controller),封裝成一個
包含攔截器的執(zhí)行器鏈HandlerExecutionChain,然后找到對應(yīng)的handlerAdapter適配器通過反射的方式
調(diào)用controller中的方法進(jìn)行邏輯處理,返回的結(jié)果封裝成ModelAndView,然后通過viewReslover對view
進(jìn)行試圖渲染,將model的值注入到Request中,最后返回response響應(yīng).