一、SpringMVC啟動過程 Spring的MVC是基於Servlet功能實現的,每個web工程中都有一個web.xml文件,web容器在啟動的時候會載入這個配置文件,當一個web應用載入到web容器中後,在web應用開始響應客戶端的請求之前,要按照順序執行下麵幾個步驟: 1、實例化部署描述符中的 ...
一、SpringMVC啟動過程
Spring的MVC是基於Servlet功能實現的,每個web工程中都有一個web.xml文件,web容器在啟動的時候會載入這個配置文件,當一個web應用載入到web容器中後,在web應用開始響應客戶端的請求之前,要按照順序執行下麵幾個步驟:
1、實例化部署描述符中的<listener>元素標識的時間監聽器的實例;
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
2、在實現了ServletContextListener介面的監聽器實例中,調用contextInitialized()方法。ContextLoaderListener實現了ServletContextListener介面,ServletContextListener作為Servlet的監聽者,會在ServletContext創建、銷毀過程中監聽ServletContextEvent事件,然後進行響應的處理。換句話說,就是在啟動web容器時,ContextLoaderListener類會自動裝配ApplicationContext的配置信息。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener { public ContextLoaderListener() { } public ContextLoaderListener(WebApplicationContext context) { super(context); } // 這裡調用其父類ContextLoader的initWebApplicationContext進行初始化工作 public void contextInitialized(ServletContextEvent event) { this.initWebApplicationContext(event.getServletContext()); } public void contextDestroyed(ServletContextEvent event) { this.closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); } }
ContextLoaderListener類繼承了ContextLoader,後者是實際的ServletContextListener介面實現者,這裡用到了代理模式,利用ContextLoader類創建Spring application context,並將其註冊到ServletContext中去。ContextLoaderListener就是調用ContextLoader類的initWebApplicationContext()方法和closeWebApplicationContext()方法來實現ServletContext的創建和銷毀工作的。
其中,initWebApplicationContext()方法主要完成了兩個部分的工作:一是查看是否已經指定父容器,如果不存在則設置其父容器。二是將Spring WebApplicationContext放置到線程Map中。
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { 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!"); } else { 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 { if (this.context == null) { this.context = this.createWebApplicationContext(servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context; if (!cwac.isActive()) { //查看是否指定父容器 if (cwac.getParent() == null) { ApplicationContext parent = this.loadParentContext(servletContext); cwac.setParent(parent); } this.configureAndRefreshWebApplicationContext(cwac, servletContext); } } servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); ClassLoader ccl = Thread.currentThread().getContextClassLoader(); //將WebApplicationContext放置到線程Map中 if (ccl == ContextLoader.class.getClassLoader()) { currentContext = this.context; } else if (ccl != null) { 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 var8) { logger.error("Context initialization failed", var8); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8); throw var8; } catch (Error var9) { logger.error("Context initialization failed", var9); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var9); throw var9; } } }
在初始化方法執行後,將WebApplicationContext存到了兩個地方,分別是ServletContext和currentContextPerThread中,我們可以通過這兩個地方來獲取WebApplicationContext:1.從servletContext中去拿;2.從當前的線程Map中去拿。
3、實例化部署符中<filter>元素標識的過濾器實例,並調用每個過濾器實例的init()方法。
4、實例化部署符中<servlet>元素標識的Servlet實例,按照該元素包含的load-on-startup順序,調用每個Servlet實例的init()方法。
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
在SpringMVC的架構中,DispatcherServlet主要負責客戶端請求的分發,起到控制器的作用。DispatcherServlet是實現Servlet介面的實現類,其結構如下圖所示。Servlet的生命周期由Servlet的容器來控制,主要分為初始化、運行和銷毀三個階段,分別對應三個方法:init()、service()和destory()。
在DispatcherServlet的父類HTTPServletBean中,重寫了init()方法,主要實現將當前的Servlet類實例轉換為BeanWrapper類型實例,以便使用Spring中提供的註入功能進行相應屬性的註入。
在DispatcherServlet的父類FameworkServelet中,重寫了service()方法,對於不同的方法,Spring統一將程式引導至processRequest(request,response)中,processRequest方法再對處理請求進行了一些準備工作後,又將細節實現轉移到了doService方法中。
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String method = request.getMethod(); if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) { this.processRequest(request, response); } else { super.service(request, response); } } protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null; LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); LocaleContext localeContext = this.buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor()); this.initContextHolders(request, localeContext, requestAttributes); try { //調用DispatcherServlet的doService()方法 this.doService(request, response); } catch (ServletException var17) { failureCause = var17; throw var17; } catch (IOException var18) { failureCause = var18; throw var18; } catch (Throwable var19) { failureCause = var19; throw new NestedServletException("Request processing failed", var19); } finally { this.resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { requestAttributes.requestCompleted(); } if (this.logger.isDebugEnabled()) { if (failureCause != null) { this.logger.debug("Could not complete request", (Throwable)failureCause); } else if (asyncManager.isConcurrentHandlingStarted()) { this.logger.debug("Leaving response open for concurrent processing"); } else { this.logger.debug("Successfully completed request"); } } this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause); } }
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { if (this.logger.isDebugEnabled()) { String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : ""; this.logger.debug("DispatcherServlet with name '" + this.getServletName() + "'" + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]"); } Map<String, Object> attributesSnapshot = null; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap(); Enumeration attrNames = request.getAttributeNames(); label108: while(true) { String attrName; do { if (!attrNames.hasMoreElements()) { break label108; } attrName = (String)attrNames.nextElement(); } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet")); attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.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 { // 最終的請求處理函數 this.doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) { this.restoreAttributesAfterInclude(request, attributesSnapshot); } } }
經過層層的準備工作,最終在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 { try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; //根據request信息尋找對應的處理器 mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { this.noHandlerFound(processedRequest, response); return; } //根據處理器尋找對應的處理器適配器 HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (this.logger.isDebugEnabled()) { this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } //返回視圖 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } this.applyDefaultViewName(request, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var19) { dispatchException = var19; } this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception var20) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20); } catch (Error var21) { this.triggerAfterCompletionWithError(processedRequest, response, mappedHandler, var21); } } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else if (multipartRequestParsed) { this.cleanupMultipart(processedRequest); } } }
二、ServletContext、ApplicationContext和WebApplicationContext區別?
1、ServletContext:是一個Web應用的上下文,是一個全局信息的存儲空間,代表當前的web應用。Servlet容器(如Tomcat)在啟動一個Webapp時,會為它創建一個ServletContext對象,即servlet上下文環境。每個webapp都有唯一的ServletContext對象。同一個webapp的所有servlet對象共用一個ServeltContext,servlet對象可以通過ServletContext來訪問容器中的各種資源。ServletContext在web應用啟動時創建,在web應用服務關閉時釋放。
2、ApplicationContext:它是建立在BeanFactory基礎之上的,除了包含BeanFactory的所有功能之外,在國際化支持、資源訪問、事件傳播等方面進行了良好的支持。其體繫結構如下圖。
ApplicationContext介面有三個常用的實現類,如下:
類名稱 | 功能描述 |
ClassPathXmlApplicationContext | 從類路徑ClassPath中尋找指定的XML配置文件,找到並裝載 完成ApplicationContext的實例化工作 |
FileSystemXmlApplicationContext | 從指定的文件系統路徑中尋找指定的XML配置文件,找到並裝載 完成ApplicationContext的實例化工作 |
XmlWebApplicationContext | 從Web應用中尋找指定的XML配置文件,找到並裝載完成ApplicationContext的實例化工作 |
與BeanFactory不同的是,ApplicationContext容器實例化後會自動對所有的單實例Bean進行實例化與依賴關係的裝配,使之處於待用狀態。而BeanFactory容器實例化後並不會自動實例化Bean,只有當Bean被使用時BeanFactory容器才會對該Bean進行實例化與依賴關係的裝配。
3、WebApplicationContext:它是專門為web應用提供的,它允許從相對於web根目錄路徑中裝載配置文件完成初始化;從WebApplicationContext中可以獲得ServletContext的引用,同時為了方便web應用訪問Spring應用上下文,WebApplicationContext也將作為一個屬性放到ServletContext中,可以通過WebApplicationContextUtils的getWebApplicationContext(ServletContext sc)方法獲取。
三、總結
ApplicationContext是Spring的核心,Context為上下文環境,在Web應用中,會用到WebApplicationContext,它繼承自ApplicationContext。在SpringMVC啟動過程中,ContextLoaderListener類起著至關重要的作用。它讀取web.xml中配置的context-param中的配置文件,提前在web容器初始化前準備業務對應的Application context,將創建好的WebApplicationContext放置於ServletContext屬性中,這樣我們只要得到Servlet就可以得到WebApplicationContext對象,並利用這個對象訪問spring容器管理的bean。
四、參考資料
1、https://www.cnblogs.com/RunForLove/p/5688731.html