SpringMVC源代碼深度分析DispatcherServlet核心的控制器(初始化)

      SpringMVC是很是優秀的MVC框架,每個框架都是爲了咱們提升開發效率,咱們試圖經過對SpringMVC的源碼去了解這個框架,瞭解整個設計思想,框架要有擴展性,這裏用的比較可能是接口和抽象,是框架的主力,咱們經過了解源碼能對SpringMVC框架更瞭解,也能對咱們開發思想有很是大的啓示。html

    SpringMVC由幾個核心類和接口組成的。咱們今天要的一個是DispatcherServlet核心的前置控制器。配置在Web.xml中。因此請求都通過它來統一分發的。SpringMVC幾個核心類和接口都會出現在DispatcherServlet的源代碼中,我這裏大概介紹一個。今天重點是介紹DispatcherServlet核心的前置控制器。後面咱們在具體分析其餘的幾個核心類和接口分析。java

         

   DispatcherServlet的繼承關係圖,能清晰的瞭解整個層次。web

  當Web項目啓動時。作初始化工做。因此咱們大部分是配置在Web.xml裏面,這樣項目一啓動,就會運行相關的初始化工做,如下是Web.xml代碼:spring

   

<servlet>
		<servlet-name>SpringMVCDispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>
				classpath:spring-mvc.xml
			</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
        <servlet-name>SpringMVCDispatcher</servlet-name>
        <url-pattern>*.jhtml</url-pattern>
    </servlet-mapping>
<servlet>
		<servlet-name>HessianDispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>
				classpath:hessian-service.xml
			</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
        <servlet-name>HessianDispatcher</servlet-name>
        <url-pattern>/service/*</url-pattern>
    </servlet-mapping>
這裏配置了兩個 DispatcherServlet,後面會介紹到。怎麼各自處理,有各自的上下文容器。


load-on-startup是啓動的優先級,spring-mvc.xml是咱們配置bean的一些信息編程

   最先咱們開始學習MVC結構時。就是學servlet,都是繼 承了HttpServlet 類,也是又一次了initdoGetdoPostdestroy方法,我這邊就不介紹HttpServlet 類,DispatcherServlet也是間接最高繼承了HttpServlet,如圖所看到的:spring-mvc

    


  咱們先了解項目啓動,DispatcherServlet和父類都作了什麼事情呢?這是咱們今天的重點。緩存

  第一步:DispatcherServlet繼承了FrameworkServlet,FrameworkServlet繼承了HttpServletBeanHttpServletBean繼承了HttpServlet 類,而HttpServletBean類有一個入口點就是重寫了init方法。如圖所看到的:mvc

  

   

    init方法作了什麼事情呢?接下來咱們來詳細分析:app

    init方法裏有涉及到了BeanWrapperPropertyValuesResourceLoader。我這裏大概介紹一下。框架

     1PropertyValues:獲取Web.xml裏面的servletinit-param(web.xml)

            

  /**
		 * Create new ServletConfigPropertyValues.
		 * @param config ServletConfig we'll use to take PropertyValues from
		 * @param requiredProperties set of property names we need, where
		 * we can't accept default values
		 * @throws ServletException if any required properties are missing
		 */
		public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
			throws ServletException {
			Enumeration en = config.getInitParameterNames();
			while (en.hasMoreElements()) {
				String property = (String) en.nextElement();
				Object value = config.getInitParameter(property);
				addPropertyValue(new PropertyValue(property, value));
			}
		}
   

說明:

   Enumeration en = config.getInitParameterNames();

獲取了init-paramparam-nameparam-value值,並設置配置參數到PropertyValue,如圖所看到的:

  

   

  2BeanWrapper:封裝了bean的行爲,提供了設置和獲取屬性值。它有相應的BeanWrapperImpl如圖所看到的:

         

3)ResourceLoader:接口僅有一個getResource(String location)的方法。可以依據一個資源地址載入文件資源。classpath:這樣的方式指定SpringMVC框架bean配置文件的來源。

     ResourcePatternResolver擴展了ResourceLoader接口。獲取資源

         ResourcePatternResolver resolver =new PathMatchingResourcePatternResolver();

         resolver.getResources("classpath:spring-mvc.xml");


  總結:

     先經過PropertyValues獲取web.xml文件init-param的參數值,而後經過ResourceLoader讀取.xml配置信息,BeanWrapper對配置的標籤進行解析和將系統默認的bean的各類屬性設置到相應的bean屬性。


   在init方法裏還調用了initServletBean();這裏面又實現了什麼。HttpServletBean在爲子類提供模版、讓子類依據本身的需求實現不一樣的ServletBean的初始化工做。這邊是由HttpServletBean的子類FrameworkServlet來實現的,如圖所看到的:

     

  this.webApplicationContext = initWebApplicationContext();初始化SpringMVC 上下文容器。servlet的上下文容器是ServletContext。對initWebApplicationContext()。進行跟蹤,查看這種方法作了什麼事情?

 
protected WebApplicationContext initWebApplicationContext() {
       //根節點上下文,是經過ContextLoaderListener載入的,server啓動時,最早載入的
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		if (this.webApplicationContext != null) {
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                 //要對上下文設置父上下文和ID等
				if (!cwac.isActive()) {
  					if (cwac.getParent() == null) {
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
         //Servlet不是由編程式註冊到容器中,查找servletContext中已經註冊的WebApplicationContext做爲上下文
		if (wac == null) {
			wac = findWebApplicationContext();
		}
          //假設都沒找到時,就用根上下文就建立一個上下文有ID
		if (wac == null) {
  			   wac = createWebApplicationContext(rootContext);
		}
        //在上下文關閉的狀況下調用refesh可啓動應用上下文,在已經啓動的狀態下。調用refresh則清除緩存並又一次裝載配置信息
		if (!this.refreshEventReceived) {
			onRefresh(wac);
		}
       //對不一樣的請求相應的DispatherServlet有不一樣的WebApplicationContext、並且都存放在ServletContext中
		if (this.publishContext) {
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
						"' as ServletContext attribute with name [" + attrName + "]");
			}
		}

		return wac;
	}
   

  總結:

       initWebApplicationContext初始化上下文。並做爲值放到了ServletContext裏,因爲不一樣的DispatherServlet有相應的各自的上下文,而且上下文有設置父上下文和id屬性等。上下文項目啓動時會調用createWebApplicationContext()方法,如圖所看到的:

    

   而後會初始化,設置設置父上下文和id屬性等。如圖所看到的:

    



 1)獲取ContextLoaderListener載入的上下文並標示爲跟上下文,假設是編程式傳入。沒初始化,以根節點爲父上文。並設置ID等信息。而後初始化。

 2)假設上下文是爲空的,Servlet不是由編程式註冊到容器中,查找servletContext中已經註冊的WebApplicationContext做爲上下文,假設都沒找到時,就用根上下文就建立一個上下文有ID。在上下文關閉的狀況下調用refesh可啓動應用上下文,在已經啓動的狀態下,調用refresh則清除緩存並又一次裝載配置信息

 3)對不一樣的請求相應的DispatherServlet有不一樣的WebApplicationContext、並且都存放在ServletContext中。以servlet-name爲key保存在severtContext。前面有配置了兩個DispatherServlet,都有各自的上下文容器。如圖所看到的:

    


回調函數onRefresh還作了一些提供了SpringMVC各類編程元素的初始化工做。 onRefresh在爲子類提供模版、讓子類依據本身的需求實現不一樣的onRefresh的初始化工做。這邊是由FrameworkServlet的子類DispatcherServlet來實現的,如圖所看到的:

   

咱們現在來分析SpringMVC組件進行初始化。並封裝到DispatcherServlet

        //初始化上傳文件解析器

        initMultipartResolver(context);

        //初始化本地解析器

       initLocaleResolver(context);

        //初始化主題解析器

        initThemeResolver(context);

        //初始化映射處理器

        initHandlerMappings(context);

         //初始化適配器處理器

        initHandlerAdapters(context);

        //初始化異常處理器

        initHandlerExceptionResolvers(context);

        //初始化請求到視圖名翻譯器

        initRequestToViewNameTranslator(context);

        //初始化視圖解析器

        initViewResolvers(context);

 咱們這邊拿幾個比較基本的分析一下詳細實現了什麼。

 第一:initHandlerMappings初始化映射處理器

 private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;
		if (this.detectAllHandlerMappings) {
			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) {
				}
		}
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
			}
		}
	}

說明: 

  1detectAllHandlerMappings默認是true,依據類型匹配機制查找上下文及父容器上下文中所有類型爲HandlerMappingbean,將它們做爲該類型組件。並放到ArrayList<HandlerMapping>中。

  2detectAllHandlerMappings假設是false時。查找keyhandlerMappingHandlerMapping類型的bean爲該類組件,而且 Collections.singletonList僅僅有一個元素的集合。

  3List<HandlerMapping> 是爲空的話,使用BeanNameUrlHandleMapping實現類建立該類的組件。

第二:initHandlerAdapters適配器處理器

  
private void initHandlerAdapters(ApplicationContext context) {
		this.handlerAdapters = null;

		if (this.detectAllHandlerAdapters) {
			Map<String, HandlerAdapter> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
				// We keep HandlerAdapters in sorted order.
				OrderComparator.sort(this.handlerAdapters);
			}
		}
		else {
			try {
				HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
				this.handlerAdapters = Collections.singletonList(ha);
			}
			catch (NoSuchBeanDefinitionException ex) {
							}
		}
		if (this.handlerAdapters == null) {
			this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
			}
		}
	}


initHandlerAdapters適配器處理器初始化原理跟initHandlerMappings初始化映射處理器同樣

 

這裏就不在說明了。

總結:

1initHandlerMapping會初始化了handlerMethods請求方法的映射。HandlerMapping處理請求的映射的如圖所看到的:

 


   今天先講SpringMVC的初始化。當DispatcherServlet初始化後,就會本身主動掃描上下文的bean,依據名稱或者類型匹配的機制查找本身定義的組件,找不到則使用DispatcherServlet。Properties定義默認的組件

 

總結:

     HttpServletBeanFrameworkServletDispatcherServlet三個不一樣的類層次,SpringMVC對三個以抽象和繼承來實現不用的功能。分工合做。實現瞭解耦的設計原則。

咱們在回想一下。各自作了什麼事情。HttpServletBean是實現了獲取web.xml中的<init-param>配置元素的值。FrameworkServlet實現了SpringMVC上下文並依據不一樣的DispatcherServlet放在以servlet-namekeysevertContext中。DispatcherServlet主要實現了初始化SpringMVC組件元素。

相關文章
相關標籤/搜索