javascript
SpringMVC源码剖析(三)- DispatcherServlet的初始化流程
我們啟動web服務器,在瀏覽器中輸入地址,就可以看到瀏覽器上輸出我們寫好的頁面。為了更好的理解上面這個過程,你需要學習關于Servlet生命周期的三個階段,就是所謂的“init-service-destroy”。
以上的知識,我覺得對于你理解SpringMVC的設計思想,已經(jīng)足夠了。SpringMVC當然可以稱得上是一個復雜的框架,但是同時它又遵循Servlet世界里最簡單的法則,那就是“init-service-destroy”。我們要分析SpringMVC的初始化流程,其實就是分析DispatcherServlet類的init()方法,讓我們帶著這種單純的觀點,打開DispatcherServlet的源碼一窺究竟吧。
1.<init-param>配置元素讀取
用Eclipse IDE打開DispatcherServlet類的源碼,ctrl+T看一下。
DispatcherServlet類的初始化入口方法init()定義在HttpServletBean這個父類中,HttpServletBean類作為一個直接繼承于HttpServlet類的類,覆寫了HttpServlet類的init()方法,實現(xiàn)了自己的初始化行為。
@Overridepublic final void init() throws ServletException {if (logger.isDebugEnabled()) {logger.debug("Initializing servlet '" + getServletName() + "'");}// Set bean properties from init parameters.try {PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));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.initServletBean();if (logger.isDebugEnabled()) {logger.debug("Servlet '" + getServletName() + "' configured successfully");}}這里的initServletBean()方法在HttpServletBean類中是一個沒有任何實現(xiàn)的空方法,它的目的就是留待子類實現(xiàn)自己的初始化邏輯,也就是我們常說的模板方法設計模式。SpringMVC在此生動的運用了這個模式,init()方法就是模版方法模式中的模板方法,SpringMVC真正的初始化過程,由子類FrameworkServlet中覆寫的initServletBean()方法觸發(fā)。
再看一下init()方法內(nèi)被try,catch塊包裹的代碼,里面涉及到BeanWrapper,PropertyValues,ResourceEditor這些Spring內(nèi)部非常底層的類。要深究具體代碼實現(xiàn)上面的細節(jié),需要對Spring框架源碼具有相當深入的了解。我們這里先避繁就簡,從代碼效果和設計思想上面來分析這段try,catch塊內(nèi)的代碼所做的事情:
- 注冊一個字符串到資源文件的編輯器,讓Servlet下面的<init-param>配置元素可以使用形如“classpath:”這種方式指定SpringMVC框架bean配置文件的來源。
- 將web.xml中在DispatcherServlet這個Servlet下面的<init-param>配置元素利用JavaBean的方式(即通過setter方法)讀取到DispatcherServlet中來。
這兩點,我想通過下面一個例子來說明一下。
我在web.xml中注冊的DispatcherServlet配置如下:
<!-- springMVC配置開始 --><servlet><servlet-name>appServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring/spring-servlet.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>appServlet</servlet-name><url-pattern>/</url-pattern></servlet-mapping><!-- springMVC配置結束 -->可以看到,我注冊了一個名為contextConfigLocation的<init-param>元素,其值為“classpath:spring/spring-servlet.xml”,這也是大家常常用來指定SpringMVC配置文件路徑的方法。上面那段try,catch塊包裹的代碼發(fā)揮的作用,一個是將“classpath:spring/spring-servlet.xml”這段字符串轉換成classpath路徑下的一個資源文件,供框架初始化讀取配置元素。在我的工程中是在spring文件夾下面的配置文件spring-servlet.xml。
另外一個作用,就是將contextConfigLocation的值讀取出來,然后通過setContextConfigLocation()方法設置到DispatcherServlet中,這個setContextConfigLocation()方法是在FrameworkServlet類中定義的,也就是上面繼承類圖中DispatcherServlet的直接父類。
我們在setContextConfigLocation()方法上面打上一個斷點,啟動web工程,可以看到下面的調(diào)試結果。
HttpServletBean類的作者是大名鼎鼎的Spring之父Rod Johnson。作為POJO編程哲學的大師,他在HttpServletBean這個類的設計中,運用了依賴注入思想完成了<init-param>配置元素的讀取。他抽離出HttpServletBean這個類的目的也在于此,就是“以依賴注入的方式來讀取Servlet類的<init-param>配置信息”,而且這里很明顯是一種setter注入。
明白了HttpServletBean類的設計思想,我們也就知道可以如何從中獲益。具體來說,我們繼承HttpServletBean類(就像DispatcherServlet做的那樣),在類中定義一個屬性,為這個屬性加上setter方法后,我們就可以在<init-param>元素中為其定義值。在類被初始化后,值就會被注入進來,我們可以直接使用它,避免了樣板式的getInitParameter()方法的使用,而且還免費享有Spring中資源編輯器的功能,可以在web.xml中,通過“classpath:”直接指定類路徑下的資源文件。
注意,雖然SpringMVC本身為了后面初始化上下文的方便,使用了字符串來聲明和設置contextConfigLocation參數(shù),但是將其聲明為Resource類型,同樣能夠成功獲取。鼓勵讀者們自己繼承HttpServletBean寫一個測試用的Servlet類,并設置一個參數(shù)來調(diào)試一下,這樣能夠幫助你更好的理解獲取配置參數(shù)的過程。
2.容器上下文的建立
上一篇文章中提到過,SpringMVC使用了Spring容器來容納自己的配置元素,擁有自己的bean容器上下文。在SpringMVC初始化的過程中,非常關鍵的一步就是要建立起這個容器上下文,而這個建立上下文的過程,發(fā)生在FrameworkServlet類中,由上面init()方法中的initServletBean()方法觸發(fā)。
?
@Overrideprotected final void initServletBean() throws ServletException {getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");if (this.logger.isInfoEnabled()) {this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");}long startTime = System.currentTimeMillis();try {this.webApplicationContext = initWebApplicationContext();initFrameworkServlet();}catch (ServletException ex) {this.logger.error("Context initialization failed", ex);throw ex;}catch (RuntimeException ex) {this.logger.error("Context initialization failed", ex);throw ex;}if (this.logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +elapsedTime + " ms");}}initFrameworkServlet()方法是一個沒有任何實現(xiàn)的空方法,除去一些樣板式的代碼,那么這個initServletBean()方法所做的事情已經(jīng)非常明白:
?
this.webApplicationContext = initWebApplicationContext();這一句簡單直白的代碼,道破了FrameworkServlet這個類,在SpringMVC類體系中的設計目的,它是?用來抽離出建立?WebApplicationContext?上下文這個過程的。
initWebApplicationContext()方法,封裝了建立Spring容器上下文的整個過程,方法內(nèi)的邏輯如下:
以上面6點跟蹤FrameworkServlet類中的代碼,可以比較清晰的了解到整個容器上下文的建立過程,也就能夠領會到FrameworkServlet類的設計目的,它是用來建立一個和Servlet關聯(lián)的Spring容器上下文,并將其注冊到ServletContext中的。跳脫開SpringMVC體系,我們也能通過繼承FrameworkServlet類,得到與Spring容器整合的好處,FrameworkServlet和HttpServletBean一樣,是一個可以獨立使用的類。整個SpringMVC設計中,處處體現(xiàn)開閉原則,這里顯然也是其中一點。
3.初始化SpringMVC默認實現(xiàn)類
初始化流程在FrameworkServlet類中流轉,建立了上下文后,通過onRefresh(ApplicationContext context)方法的回調(diào),進入到DispatcherServlet類中。
?
@Overrideprotected void onRefresh(ApplicationContext context) {initStrategies(context);}DispatcherServlet類覆寫了父類FrameworkServlet中的onRefresh(ApplicationContext context)方法,提供了SpringMVC各種編程元素的初始化。當然這些編程元素,都是作為容器上下文中一個個bean而存在的。具體的初始化策略,在initStrategies()方法中封裝。
protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}我們以其中initHandlerMappings(context)方法為例,分析一下這些SpringMVC編程元素的初始化策略,其他的方法,都是以類似的策略初始化的。
private void initHandlerMappings(ApplicationContext context) {this.handlerMappings = null;if (this.detectAllHandlerMappings) {// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.Map<String, HandlerMapping> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());// We keep HandlerMappings in sorted order.OrderComparator.sort(this.handlerMappings);}}else {try {HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);this.handlerMappings = Collections.singletonList(hm);}catch (NoSuchBeanDefinitionException ex) {// Ignore, we'll add a default HandlerMapping later.}}// Ensure we have at least one HandlerMapping, by registering// a default HandlerMapping if no other mappings are found.if (this.handlerMappings == null) {this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);if (logger.isDebugEnabled()) {logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");}}}detectAllHandlerMappings變量默認為true,所以在初始化HandlerMapping接口默認實現(xiàn)類的時候,會把上下文中所有HandlerMapping類型的Bean都注冊在handlerMappings這個List變量中。如果你手工將其設置為false,那么將嘗試獲取名為handlerMapping的Bean,新建一個只有一個元素的List,將其賦給handlerMappings。如果經(jīng)過上面的過程,handlerMappings變量仍為空,那么說明你沒有在上下文中提供自己HandlerMapping類型的Bean定義。此時,SpringMVC將采用默認初始化策略來初始化handlerMappings。
點進去getDefaultStrategies看一下。
@SuppressWarnings("unchecked")protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {String key = strategyInterface.getName();String value = defaultStrategies.getProperty(key);if (value != null) {String[] classNames = StringUtils.commaDelimitedListToStringArray(value);List<T> strategies = new ArrayList<T>(classNames.length);for (String className : classNames) {try {Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());Object strategy = createDefaultStrategy(context, clazz);strategies.add((T) strategy);}catch (ClassNotFoundException ex) {throw new BeanInitializationException("Could not find DispatcherServlet's default strategy class [" + className +"] for interface [" + key + "]", ex);}catch (LinkageError err) {throw new BeanInitializationException("Error loading DispatcherServlet's default strategy class [" + className +"] for interface [" + key + "]: problem with class file or dependent class", err);}}return strategies;}else {return new LinkedList<T>();}}它是一個范型的方法,承擔所有SpringMVC編程元素的默認初始化策略。方法的內(nèi)容比較直白,就是以傳遞類的名稱為鍵,從defaultStrategies這個Properties變量中獲取實現(xiàn)類,然后反射初始化。
需要說明一下的是defaultStrategies變量的初始化,它是在DispatcherServlet的靜態(tài)初始化代碼塊中加載的。
private static final Properties defaultStrategies;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());}} private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";這個DispatcherServlet.properties里面,以鍵值對的方式,記錄了SpringMVC默認實現(xiàn)類,它在spring-webmvc-3.1.3.RELEASE.jar這個jar包內(nèi),在org.springframework.web.servlet包里面。
# 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.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolverorg.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMappingorg.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager至此,我們分析完了initHandlerMappings(context)方法的執(zhí)行過程,其他的初始化過程與這個方法非常類似。所有初始化方法執(zhí)行完后,SpringMVC正式完成初始化,靜靜等待Web請求的到來。
4.總結
回顧整個SpringMVC的初始化流程,我們看到,通過HttpServletBean、FrameworkServlet、DispatcherServlet三個不同的類層次,SpringMVC的設計者將三種不同的職責分別抽象,運用模版方法設計模式分別固定在三個類層次中。其中HttpServletBean完成的是<init-param>配置元素的依賴注入,FrameworkServlet完成的是容器上下文的建立,DispatcherServlet完成的是SpringMVC具體編程元素的初始化策略。
轉載于:https://my.oschina.net/wugong/blog/1618116
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的SpringMVC源码剖析(三)- DispatcherServlet的初始化流程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用Docker for Windows
- 下一篇: Deploying Kubernetes