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); }