1. 程式人生 > >ServletContext與ApplicationContext的區別

ServletContext與ApplicationContext的區別

Spring中的概念

在閱讀Spring原始碼或相關的文獻時,經常會遇到WebApplicationContext, ApplicationContext, ServletContext以及ServletConfig等名詞,這些名詞都很相近,但適用範圍又有所不同,對理解原始碼及spring內部實現造成混淆,因此有必要對這些概念進行一些比較.

為了後續比較的方便,首先我們先來澄清這幾個名詞的概念

  • ServletContext: 這個是來自於servlet規範裡的概念,它是servlet用來與容器間進行互動的介面的組合,也就是說,這個介面定義了一系列的方法,servlet通過這些方法可以很方便地與自己所在的容器進行一些互動,比如通過getMajorVersion與getMinorVersion來獲取容器的版本資訊等. 從它的定義中也可以看出,在一個應用中(一個JVM)只有一個ServletContext, 換句話說,容器中所有的servlet都共享同一個ServletContext.

  • ServletConfig: 它與ServletContext的區別在於,servletConfig是針對servlet而言的,每個servlet都有它獨有的serveltConfig資訊,相互之間不共享.

  • ApplicationContext: 這個類是Spring實現容器功能的核心介面,它也是Spring實現IoC功能中最重要的介面,從它的名字中可以看出,它維護了整個程式執行期間所需要的上下文資訊, 注意這裡的應用程式並不一定是web程式,也可能是其它型別的應用. 在Spring中允許存在多個applicationContext,這些context相互之間還形成了父與子,繼承與被繼承的關係,這也是通常我們所說的,在spring中存在兩個context,一個是root context,一個是servlet applicationContext的意思. 這點後面會進一步闡述.

  • WebApplicationContext: 其實這個介面不過是applicationContext介面的一個子介面罷了,只不過說它的應用形式是web罷了. 它在ApplicationContext的基礎上,添加了對ServletContext的引用,即getServletContext方法.

如何配置

ServletContext

從前面的論述中可以知道, ServletContext是容器中所有servlet共享的配置,它在應用中是全域性的

根據servlet規範的規定,可以通過以下配置來進行配置,其中Context-Param指定了配置檔案的位置,ContextLoaderListener定義了context載入時的監聽器,因此,在容器啟動時,監聽器會自動載入配置檔案,執行servletContext的初始化操作.

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:conf/applicationContext.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

 

ServletConfig

ServletConfig是針對每個Servlet進行配置的,因此它的配置是在servlet的配置中,如下所示, 配置使用的是init-param, 它的作用就是在servlet初始化的時候,載入配置資訊,完成servlet的初始化操作

<servlet>
    <servlet-name>mvc-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    <async-supported>true</async-supported>
</servlet>


原始碼分析

關於applicationContext的配置,簡單來講,在servletContext中配置的context-param引數, 會生成所謂的root application context, 而每個servlet中指定的init-param引數中指定的物件會生成servlet application context, 而且它的parent就是servletContext中生成的root application context, 因此在servletContext中定義的所有配置都會被繼承到servlet中, 這點在後續的原始碼闡述中會有更直觀的體現.

首先先來看ServletContext中的配置檔案的載入過程. 這個過程是由ContextLoaderListener物件來完成的,因此我們找到相應的原始碼,去掉一些日誌及不相關的原始碼後如下:

  • 第一步是判斷是否存在rootApplicationContext,如果存在直接丟擲異常結束

  • 第二步是建立context物件,並在servletContext中把這個context設定為名稱為ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的屬性. 到這裡其實已經解釋了ApplicationContext與servletContext的區別,它不過是servletContext中的一個屬性值罷了,這個屬性值中存有程式執行的所有上下文資訊 由於這個applicationContext是全域性的應用上下文資訊,在spring中就把它取名為’root application context’.

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
	if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
	}
	try {
		// Store context in local instance variable, to guarantee that
		// it is available on ServletContext shutdown.
		if (this.context == null) {
			this.context = createWebApplicationContext(servletContext);
		}
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
		return this.context;
	}
}

接著再來看DispatcherServlet的原始碼,作為servlet,根據規範它的配置資訊應該是在Init方法中完成,因此我們找到這個方法的原始碼即可知道servletConfig以及servlet application context的初始化過程:
        第一步是從servletConfig中獲取所有的配置引數, ServletConfigPropertyValues的建構函式中會遍歷servletConfig物件的所有初始化引數,並把它們一一儲存在pvs中接著再來看DispatcherServlet的原始碼,作為servlet,根據規範它的配置資訊應該是在Init方法中完成,因此我們找到這個方法的原始碼即可知道servletConfig以及servlet application context的初始化過程:

  • 第二步就是開始初始servlet,由於dispatcherServlet是繼承自FrameworkServlet,因此這個方法在FrameworkServlet中找到,可以看到,在initServletBean中又呼叫了initWebApplicationContext方法,
    在這個方法中,首先獲取到rootContext, 接著就開始初始化wac這個物件,在建立這個wac物件的方法中,傳入了rootContext作為它的parent,也就是在這裡,兩者之間的父子關係建立,也就形成了我們平時常說的繼承關係.

@Override
public final void init() throws ServletException {
	// Set bean properties from init parameters.
	PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
	// Let subclasses do whatever initialization they like.
	initServletBean();
}
//遍歷獲取servletConfig的所有引數
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
	throws ServletException {
	while (en.hasMoreElements()) {
		String property = (String) en.nextElement();
		Object value = config.getInitParameter(property);
		addPropertyValue(new PropertyValue(property, value));
		if (missingProps != null) {
			missingProps.remove(property);
		}
	}
}
//初始化webApplicationContext
protected final void initServletBean() throws ServletException {
	try {
		this.webApplicationContext = initWebApplicationContext();
	}
}
//具體的初始化操作實現
protected WebApplicationContext initWebApplicationContext() {
	WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	WebApplicationContext wac = null;
	if (this.webApplicationContext != null) {
		// A context instance was injected at construction time -> use it
		wac = this.webApplicationContext;
		if (wac instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
			if (!cwac.isActive()) {
				// The context has not yet been refreshed -> provide services such as
				// setting the parent context, setting the application context id, etc
				if (cwac.getParent() == null) {
					// The context instance was injected without an explicit parent -> set
					// the root application context (if any; may be null) as the parent
					cwac.setParent(rootContext);
				}
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	if (wac == null) {
		// No context instance was injected at construction time -> see if one
		// has been registered in the servlet context. If one exists, it is assumed
		// that the parent context (if any) has already been set and that the
		// user has performed any initialization such as setting the context id
		wac = findWebApplicationContext();
	}
	if (wac == null) {
		// No context instance is defined for this servlet -> create a local one
		//就是在這個方法中,servlet application context與root application context的繼承關係正式建立
		wac = createWebApplicationContext(rootContext);
	}
	if (this.publishContext) {
		// Publish the context as a servlet context attribute.
		String attrName = getServletContextAttributeName();
		getServletContext().setAttribute(attrName, wac);
	}
	return wac;
}
//就是在這個方法中,servlet application context與root application context的繼承關係正式建立
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
	return createWebApplicationContext((ApplicationContext) parent);
}


Servlet與JSP: Head first to Servlet and JSP參考文獻