總結前端控制器DispatcherServlet(下)-程序環境加載

上一篇日誌總結了Servlet規範,對於所有處理用戶請求的服務器組件,都要去實現Servlet接口。GenericServlet保留了Servlet配置,提供無參數的init()方法供子類重寫,還提供了通用協議的service()方法,該方法子類必須重寫,來實現不同協議的Servlet。最後HttpServlet重寫GenericServlet的service()方法,實現支持HTTP協議的Servlet,負責分發HTTP請求到不同的方法中。由DipsatcherServlet的多級繼承圖可以看到,往下走,到了HttpServletBean。

 

HttpServletBean

HttpServletBean的作用是把Servlet配置參數作爲Bean屬性,對我們自己的Servlet(如Spring MVC中的DispatcherServlet)做初始化。具體是在web.xml文件中的<servlet>標籤對裏,使用<init-param>標籤對來配置該servlet的參數,其中參數包含有:contextConfigLocation、contextClass、contextInitializerClass,contexAttribute等。

<servlet>
	<!-- 定義servlet配置,實現類爲DispatcherServlet,即前端控制器類 -->
		<servlet-name>springmvc</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<!-- DispatcherServlet初始化參數,加載的配置文件爲編譯目錄classpath -->
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:springmvc.xml</param-value>
		</init-param>
	</servlet>

初始化Servlet成員變量

實現方式通過重寫GenericServlet中的init()方法:

@Override
public final void init() throws ServletException {
	if (logger.isDebugEnabled()) {
		// 從Servlet配置中獲取名字,這個Servlet配置來自GenericServlet
		Logger.debug("Initializing servlet '" + getServletName() + "'");
	}
		
	// 設置Servlet的初始化參數作爲Servlet Bean的屬性
	try {
		// PropertyValues是一個容器類,存放從web.xml配置文件中配置的參數
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), 
				this.requiredProperties);
		
		// 把當前的Servlet作爲一個Bean,把Bean的屬性和屬性的存取方法信息放入BeanWrapper中
		BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
		
		// 註冊一個可以在資源和路徑之間進行轉化的用戶化編輯器,這些資源是這個Web應用的內部資源,例如一個文件或圖片等
		ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
		bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader));
		
		// 提供一個模板方法initServletBean(),供子類去實現更多的初始化
		initBeanWrapper(bw);
		
		// 把初始化指定的參數都賦值到Serlvet的屬性中,第二個參數true表示忽略位置屬性
		bw.setPropertyValues(pvs, true);
	} catch (BeansException ex) {
		logger.error("Failed to set bean properties on servlet '" + getServletName() + 
				"'", ex);
	}
	
	// 提供一個模板方法initServletBean(),供子類去初始化其他資源
	initServletBean();
	
	if (logger.isDebugEnabled()) {
		logger.debug("Servlet '" + getServletName() + "' configured successfully");
	}
}

來看看代碼,第3-6行是日誌輸出,關鍵從第9行開始,首先實例化一個ServletConfigPropertyValues容器對象,用來存放web.xml配置文件中配置的參數,即<init-param>標籤對中的配置。到第15行,實例化一個Bean包裹器BeanWrapper(BeanWrapper是Spring中的一個重要組件,作用是爲Bean完成屬性設置),後面forBeanPropertyAccess()方法中的參數this,指的就是當前這個類HttpServletBean的實例,也就是我們的DispatcherServlet(因爲我們在web.xml中配置了它)。第17行,實例化一個ServletContextResourceLoader來獲取當前Servlet中的一些資源文件,例如classpath資源(Spring提供的Resource接口可以讓我們對一些底層資源做一致的訪問操作,如URL資源,File資源和classpath資源)。第19行在BeanWrapper中註冊這個用戶化編輯器,到這一步,默認的BeanWrapper初始化就完成了,第22行HttpServletBean提供了一個initBeanWrapper()供子類去做更多的初始化,如增加用戶化編輯器等。

      最後,第25行,調用BeanWrapper的setPropertyValues()方法,把前面的初始化參數,即ServletConfigPropertyValues中存放的配置參數,都設置到被BeanWrapper包裹的實例裏對應的成員變量中去,完成對Servlet的初始化。在第32行還提供了一個模板方法initServletBean(),供子類對Servlet做更多的初始化。

      總結下HttpServletBean,它繼承自HttpServlet,功能是把web.xml中的Servlet配置參數(<init-param>標籤對中的配置),初始化到Servlet的成員變量中,對於Servlet中的成員變量,都提供了getter()和setter()方法,我們可以很方便地去獲取這些參數值。在Spring MVC中,HttpServletBean就是通過BeanWrapper包裹實例DispatcherServlet,然後將web.xml中的Servlet配置參數初始化到DispatcherServlet中。

 

FrameworkServlet

再往下到了FrameworkServlet,FrameworkServlet就是Spring Web的一個基本Servlet實現類了。它的作用是初始化,加載WebApplicationContext(Web應用程序環境),FrameworkServlet實現了HttpServlet中的所有接口實現,所以可以將HTTP支持的GET、PUT、POST,DELETE等方法分發到Spring MVC的控制器方法中進行處理,,子類必須實現其doService()方法來處理接收到的請求。

加載Web應用程序環境

FrameworkServlet通過繼承HttpServletBean,實現了ApplicationContextAware接口,接口中定義了一個setApplicationContext()方法,以此來在初始化時加載Web應用程序環境。FrameworkServlet加載WebApplicationContext應用程序環境過程:

可以看到,FrameworkServlet初始化Servlet時,首先會去查找一個專用的應用程序跟環境,即查找我們在web.xml中servlet標籤對裏是否有配置contextClass,如果有,就會去加載我們配置的專用的contextClass,如果不存在專用的根環境,就會加載默認的共享根環境。

監聽與動態刷新

      在FrameworkServlet父類HttpServletBean中的init()方法裏,最後調用了一個模板方法initServletBean(),這個方法就是HttpServletBean供子類去重寫,完成Servlet框架的初始化,來看看FrameworkServlet重寫的這個方法:

@Override
public final void initServletBean() throws ServletException {
	// 輸出初始化信息到日誌中
	getServletContext().log("Initializing Spring FrameworkServlet '" + 
			getServletName() + "'");
	
	if (this.logger.isInfoEnabled()) {
		this.logger.info("FrameworkServlet '" + getServletName() + 
				"': initializationstarted");
	}
	
	// 初始化環境開始時間
	long startTime = System.currentTimeMillis();
	try {
		// 初始化Servlet環境
		this.webApplicationContext = initWebApplicationContext();
		
		// 調用模板方法,供子類實現,初始化指定的資源(例如具體實現在DispatcherServlet中)
		initFrameworkServlet();
	} catch (ServletException ex) {
		this.logger.error("Context initialization failed", ex);
		throws ex;
	} catch (RuntimeException ex) {
		this.logger.error("Context initialization failed", ex);
		throws ex;
	}
	
	if (this.logger.isInfoEnabled()) {
		// 初始化環境用時
		long elapsedTime = System.currentTimeMillis() - startTime;
		// 輸出到日誌信息中
		this.logger.info("FrameworkServlet '" + getServletName() + 
				"': initializationcomplete in " + elapsedTime + "ms");
	}
	
}

可以看到,initServletBean()方法關鍵在於第16行,調用initWebApplicationContext()方法,完成Web應用程序環境的加載(加載過程在上面已經貼出了圖示)。接下來就看看,initWebApplicationContext()方法是如何實現的:

protected WebApplicationContext initWebApplicationContext() {
	// 首先查找這個Servlet(例如DispatcherServlet)是否配置有專用的根環境
	WebApplicationContext wac = findWebApplicationContext();
	
	// 如果沒有,就加載默認的共享根環境
	if (wac == null) {
		// 加載默認的共享根環境,這個根環境通過關鍵字ROOT_WEB_APPLICATION
		// _CONTEXT_ATTRIBUTE保存在Servlet環境裏
		WebApplicationContext parent = 
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		
		// 創建Servlet的子環境,並引用已經得到的著環境
		wac = createWebApplicationContext(parent);
	}
	
	// Servlet框架在初始化時爲子類提供了初始化模板方法initFrameworkServlet()和onRefresh()
	if (!this.refreshEventReceived) {
		onRefresh(wac);
	}
	
	// 如果設置了發佈環境屬性,則把這個Web應用程序環境以ServletContextAttributeName的值
	// 作爲關鍵字保存到Servlet環境裏,以此來讓其他Servlet共享這個Web應用程序環境
	if (this.publishContext) {
		// 將這個環境發佈並存儲到Servlet Context中
		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;
}

按照加載Web應用程序環境的過程,第3行首先判斷在web.xml中是否有配置servlet的專用根環境,如果你沒有在裏面配置ContextLoaderListener,那麼wac的值就爲null。接下來第9行就會去加載一個默認的共享根環境,並把我們的Servlet子環境引用得到這個主環境。

      在Web應用環境初始化後,這個Servlet類就會作爲Web應用程序環境的事件處理監聽器,然後通過onRefresh()方法動態刷新來實現各種組件的初始化,例如DispatcherServlet就是通過onRefresh()方法來動態加載Spring MVC需要的組件。第17行的refreshEventReceived標識onRefresh()方法是否已經被調用過,如果沒有,就調用onRefresh()。

最後,第23-31行,根據publishContext標識是否設置了發佈環境屬性,如果設置了,就把當前初始化的Web應用程序環境設置到Servlet環境中,供其他Servlet共享這個Web應用程序環境。

總的來說,FrameworkServlet的作用就是初始化WebApplicationContext,我們的Web應用程序環境(如Spring MVC環境)。

 

DispatcherServlet

最後來到我們的DispatcherServlet,由前面經過多級繼承自HttpServlet,每一級都完成特定的初始化。Servlet提供初始化模板方法,GenericServlet保存了config配置,並實現了Servlet接口的通用協議的service()方法,下一級HttpServlet重寫了service()方法,實現了支持HTTP協議的Servlet,完成對用戶HTTP請求的分發。HttpServletBean根據<init-param>的配置,完成Servlet中成員變量的賦值,如contextConfigLocation、contextClass等。FrameworkServlet加載專用或默認的共享的應用程序環境,並提供onRefresh()方法供子類(如DispatcherServlet)完成組件的動態加載。來到DispatcherServlet,我們的前端控制器,作爲整個繼承鏈的最後一個類,它會在Web應用程序環境中查找Spring MVC的需要的組件,在接收到HTTP請求後,將其分發到響應的組件中進行處理。

根據其父類FrameworkServlet,當Web應用程序環境初始化完成之後,該類就會變成當前應用程序進行環境事件處理的監聽器,所以當DispatcherServlet監聽事件知道Web應用程序環境刷新後(調用了onRefresh()方法),便會在主環境和子環境中查找是否已經註冊了Spring MVC需要的組件。Spring MVC的組件按照數量來劃分,分爲可選組件、單值組件和多值組件。在Web應用程序環境初始化和刷新時,首先調用initStrategies()方法,該方法完成組件的加載:

protected void initStrategies(ApplicationContext context) {
	// 初始化多部(multipart)請求解析器,沒有默認的實現
	initMultipartResolver(context);
	
	// 初始化地域解析器,默認的實現是AcceptHeaderLocaleResolver
	initLocaleResolver(context);
	
	// 初始化主題解析器,默認的實現是FixedThemeResolver
	initThemeResolver(context);
	
	// 初始化處理器映射器集合,默認的實現是BeanNameUrlHandlerMapping和
	// DefaultAnnotationHandlerMapping
	initHandlerMappings(context);
	
	// 初始化處理器適配器集合,默認的實現是HttpRequestHandlerAdapter,
	// SimpleControllerHandlerAdapter和AnnotationMethodHandlerAdapter
	initHandlerAdapters(context);
	
	// 初始化處理器異常解析器集合,默認的實現是AnnotationMethodHandlerExceptionResolver、
	// ResponseStatusExceptionResolver和DefaultHandlerExceptionResolver
	initHandlerExceptionResolvers(context);
	
	// 初始化請求視圖名解析器,默認的實現是DefaultRequestToViewNameTranslator
	initRequestToViewNameTranslator(context);
	
	// 初始化視圖解析器集合,默認的實現是InternalResourceViewResolver
	initViewResolvers(context);
}

接下來看看裏面的幾個具體方法。

可選組件MultipartResolver

可選組件指的是在工作流中可能需要也可能不需要的組件,例如MultipartResolver,它負責處理文件上傳:

private void initMultipartResolver(ApplicationContext context) {
	try {
		// 從配置的Web應用程序環境中查找多部請求解析器
		this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, 
				MultipartResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
		}
	} catch (NoSuchBeanDefinitionException ex) {
		// 如果沒有查找到多部請求解析器,則忽略它,因爲不是所有的應用程序都需要它,多部請求
		// 通常用在文件上傳中
		this.multipartResolver = null;
		if (logger.isDebugEnabled()) {
			logger.debug("Unable to locate MultipartResolver with name '" + 
					MULTIPART_RESOLVER_BEAN_NAME + "': no multipart request handling provided");
		}
	}
}

它的初始化很簡單,之間在Web應用程序環境中查找,如果沒有找到,則會忽略這個組件。

單值組件LocalResolver

單值組件顧名思義就是整個工作流中只需要一個的組件,例如主題解析器ThemeResolver,地域解析器LocaleResolver和請求視圖名解析器RequestToViewNameTranslator。與可選組件的初始化一樣,都是直接在Web應用程序環境中查找,不同的是,如果查找失敗,則會去查找Spring MVC的默認配置,並根據其默認配置來加載地域解析器:

private void initLocaleResolver(ApplicationContext context) {
	try {
		// 從配置的Web應用程序環境中查找地域請求解析器
		this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, 
				LocaleResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
		}
	} catch (NoSuchBeanDefinitionException ex) {
		// 如果沒有查找到低於請求解析器,則查找默認的配置,並根據默認配置初始化地域解析器
		this.localResolver = getDefaultStrategy(context, LocaleResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Unable to locate LocaleResolver with name '" + 
					LOCALE_RESOLVER_BEAN_NAME + "': using default [" + this.localResolver + 
					"]");
		}
	}
}

多值組件HandlerMappings

多值組件也就是整個工作流中可以有多個實現組件,很容易想到,肯定有我們的處理器映射器,因爲DispatcherServlet需要輪詢查找出能夠處理當前HTTP請求的處理器映射器,讓其返回一個執行鏈。來看看多值組件的初始化代碼:

private void initHandlerMappings(ApplicationContext context) {
	this.initHandlerMappings = null;
	
	if (this.detectAllhandlerMappings) {
		// 如果配置爲自動檢測所有的處理器映射器,則在加載的Web應用程序環境中查找
		// 所有實現處理器映射器接口的Bean
		Map<String, HandlerMapping> matchingBeans = 
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, 
						HandlerMapping.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerMappings = new 
					ArratList<HandlerMapping>(matchingBeans.values());
					
			// 根據這些Bean所實現的Order接口進行排序
			orderComparator.sort(this.handlerMappings);
		}
	} else {
		// 如果沒有配置爲自動檢測所有的處理器映射器,則在Web應用程序環境中查找名爲
		// "handlerMapping"的Bean作爲處理器映射器
		try {
			HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, 
					HandlerMapping.class);
			
			// 構造單個Bean集合
			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");
		}
	}
}

第4行首先判斷是否配置爲檢測所有的處理器映射器,如果是,就在Web應用程序環境中查找所有的處理器映射器。若不是配置檢測所有的處理器映射器,第17行則去加載一個名爲”handlerMapping”的處理器映射器。第31行,若以上方式都沒有查找到註冊的處理器映射器,那麼Spring MVC就會使用默認的配置,加載默認的處理器映射器。

      以上,就是DispatcherServlet的初始化過程,經過多級繼承,逐層初始化,最後完成Spring MVC的組件註冊後,DispatcherServlet就可以開始攔截用戶的HTTP請求,將其分發到Spring MVC的工作流中進行響應處理了。

 

以上源碼已上傳GitHub:

https://github.com/justinzengtm/SSM-Framework/tree/master/SpringMVC_Project/source%20code

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章