1. 程式人生 > >spring MVC的實現原理

spring MVC的實現原理

1、典型配置

Spring MVC的一個典型配置如下:

<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:spring-context.xml</param-value>
</context-param>
<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
		<servlet-name>test</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:config/test-servlet.xml</param-value>
        </init-param>
		<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
		<servlet-name>test</servlet-name>
		<url-pattern>/</url-pattern>
</servlet-mapping>
從這個配置裡面可以看到,一個是配置上下文的監聽器,這個監聽器負責整個ioc容器在web環境中的啟動工作;另外一個就是配置spring MVC的一個分發請求器。他們共同構成了spring和web容器的介面操作,他們與web容器的耦合是通過ServletContext來實現的。而ServletContext就為spring的IOC容器提供了一個宿主環境,這個宿主環境裡面的ioc容器是通過.ContextLoader初始化的,而DispatcherServlet就負責web請求轉發的建立,完成http的請求響應。

2、上下文的啟動及初始化過程


在這個啟動過程中看到系統載入是依賴web容器的servletContextListener來觸發的,這個listener的觸發會在如下時候:
當Servlet 容器啟動或終止Web 應用時,會觸發ServletContextEvent 事件,該事件由 ServletContextListener 來處理。在 ServletContextListener 介面中定義了處理ServletContextEvent 事件的兩個方法。
1) contextInitialized(ServletContextEvent sce) :當Servlet 容器啟動Web 應用時呼叫該方法。在呼叫完該方法之後,容器再對Filter 初始化,並且對那些在Web 應用啟動時就需要被初始化的Servlet 進行初始化。
2)contextDestroyed(ServletContextEvent sce) :當Servlet 容器終止Web 應用時呼叫該方法。在呼叫該方法之前,容器會先銷燬所有的Servlet 和Filter 過濾器。
所以spring會在web容器啟動的時候藉助這個監聽器來建立它的上下文,而這個上下文的建立其實就是一個ico容器的初始化過程。而這個上下文初始化的過程同時也會把這個ServletContext給設定上,以供後面的WebApplicationContext來獲取web容器級別的全域性屬性。
先看下這個建立的XmlWebApplicationContext上下文的類繼承關係:

它的refresh過程其實是在AbstractApplicationContext完成的,如下程式碼:
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;
			}
		}
	}
這個就是之前spring ioc中已經講到過的容器啟動過程,在XmlWebApplicationContext它重寫了beanDefinition的載入,如下程式碼:
@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}
對於這個實現類它主要添加了對web環境和xml配置類的定義處理,例如:
/** Default config location for the root context */
	public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";


	/** Default prefix for building a config location for a namespace */
	public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
這兩個靜態變數的定義就是專門為web環境來設定的,相對於配置資源的獲取從以下這段重寫的程式碼也可以看出來。
@Override
	protected String[] getDefaultConfigLocations() {
		if (getNamespace() != null) {
			return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
		}
		else {
			return new String[] {DEFAULT_CONFIG_LOCATION};
		}
	}
在上面時序圖中有一個initWebApplicationContext的過程,這個過程裡面有如下一段程式碼可以簡單的說明一下:
if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
			}
也就是context的建立過程,這個createWebApplicationContext的實現如下:
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
		Class<?> contextClass = determineContextClass(sc);
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
		}
		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	}
在建立webApplicationXontext的時候這裡面有一個選擇上下文class的方法:
protected Class<?> determineContextClass(ServletContext servletContext) {
		String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
		if (contextClassName != null) {
			try {
				return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load custom context class [" + contextClassName + "]", ex);
			}
		}
		else {
			contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
			try {
				return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load default context class [" + contextClassName + "]", ex);
			}
		}
	}
首先會判斷在web容器的上下文全域性屬性裡面有沒有做設定,如果做了設定就使用這個類。如果沒有設定就從一個預設的策略properties裡面去獲取這個配置。這個配置的預設檔案內容如下:
# Default WebApplicationContext implementation class for ContextLoader.  
# Used as fallback when no explicit context implementation has been specified as context-param.  
# Not meant to be customized by application developers.  
  
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext  
也就是說spring的mvc預設上下文就是XmlWebApplicationContext ,驗證了我們上面對這個類的分析。