Spring的核心在於其IOC和AOP機制,而IOC機制的關鍵在於Spring中的容器,而Spring中的容器是並非單單指一個獨立的對象.根據Spring中組件的職能,做用的不一樣,咱們將不一樣的組件對象分別注入到兩類Context容器中,而且這兩類Context容器存在着繼承與被繼承的關係.前端
在閱讀Spring源碼或相關文獻時,會常常遇到這幾個名詞:
WebApplicationContext
---ApplicationContext
---ServletContext
---ServletConfig
.這些名詞很相近但適用範圍有所不一樣,容易形成spring
內部實現的理解混淆,因此首先大體解釋這幾個名詞.java
ServletContext
:這個是來自Servlet規範裏的概念,它是Servlet
用來與容器間進行交互的接口的組合.也就是,這個接口定義了一系列的方法,Servlet
經過這些方法能夠很方便地與所在的容器進行一些交互.從它的定義中也能夠看出在一個應用中(一個JVM
)只有一個ServletContext
.也就是說,容器中全部的Servlet
都共享一個ServletContext
.ServletConfig
:它與ServletContext
的區別在於,ServletConfig
是針對servlet
而言的,每一個servlet
都有它獨特的ServletConfig
信息,相互之間不共享.ApplicationContext
:這個類是Spring
容器功能的核心接口,它是Spring
實現IOC
功能最重要的接口.從它的名字能夠看出,它維護了整個程序運行期所須要的上下文信息,注意這裏的應用程序並不必定是web
程序.在Spring
中容許存在多個ApplicationContext
,這些ApplicationContext
相互之間造成父子,繼承與被繼承的關係,這也是一般咱們所說的:在Spring
中存在兩個context
,一個Root Application Context
,一個是Servlet Application Context
,這一點在後續會詳細闡述.WebApplicaitonContext
:這個接口只是ApplicationContext
接口的一個子接口,只不過它的應用形式是web
,它在ApplicaitonContext
的基礎上,添加了對ServletContext
的引用.
在SpringMVC
配置文件中,咱們一般會配置一個前端控制器DispatcherServlet
和監聽器ContextLoaderListener
來進行Spring
應用上下文的初始化
- 在以前的闡述中可知,
ServletContext
是容器中全部Servlet
共享的配置,它是應用於全局的.根據Servlet
規範的規定,根據以上監聽器的配置,其中context-param
指定了配置文件的未知.在容器啓動後初始化ServletContext
時,監聽器會自動加載配置文件,來初始化Spring
的根容器Root Application Context
.- 一樣
ServletConfig
是針對每一個Servlet
進步配置的,所以它的配置是在servlet
的配置中,根據以上DispatcherServlet
的配置,配置中init-param
一樣指定了在Servlet
初始化調用#init
方法時加載配置信息的xml
文件,並初始化Spring
應用容器Servlet Application Context
.
接下來咱們具體分析Spring容器初始化:web
- 關於
ApplicationContext
的配置,首先,在ServletContext
中配置context-param
參數.經過監聽器會生成所謂的Root Application Context
,而每一個DispatcherServlet
中指定的init-param
參數會生成Servlet Application Context
.並且它的parent
就是ServletContext
中生成的Root Application Context
.所以在ServletContext
中定義的全部配置都會繼承到DispatcherServlet
中,這在以後代碼中會有直觀的提現.
1. Root Application Contextspring
public class ContextLoaderListener extends ContextLoader implements ServletContextListener { //................... public void contextInitialized(ServletContextEvent event) { this.initWebApplicationContext(event.getServletContext()); } } -------------------------------------------------------------------------------------------------------------------------------------------------------- public class ContextLoader { //................... public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { if(servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!"); } else { //................... try { if(this.context == null) { this.context = this.createWebApplicationContext(servletContext); } //................... servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); ClassLoader ccl = Thread.currentThread().getContextClassLoader(); return this.context; } } } protected WebApplicationContext createWebApplicationContext(ServletContext sc) { Class<?> contextClass = this.determineContextClass(sc); if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); } else { return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); } } protected Class<?> determineContextClass(ServletContext servletContext) { String contextClassName = servletContext.getInitParameter("contextClass"); if(contextClassName != null) { try { return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } } else { contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); try { return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); } } } static { try { ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException var1) { throw new IllegalStateException("Could not load 'ContextLoader.properties': " + var1.getMessage()); } currentContextPerThread = new ConcurrentHashMap(1); } }
爲了方便閱讀,這裏把一些不是核心的代碼過濾segmentfault
- 根據配置文件,首先咱們經過
ContextLoaderListener
對象來監聽ServletContext
初始化,在初始化方法中會調用父類ContextLoader#initWebApplicationContext
方法來進行- 在
initWebApplication
中首先判斷是否存在Root Application Context
,若是存在則拋出異常.以後經過#createWebApplicationContext
方法來建立容器對象,並會將容器放入ServletContext
中.因此對於ApplicationContext
和ServletContext
的區別就是ApplicationContext
其實就是ServletContext
中的一個屬性值而已.這個屬性中存有程序運行的全部上下文信息,因爲這個ApplicationContext
是全局的應用上下文,因此在Spring
中稱它爲"Root Application Context"
.- 接下來咱們具體看一下容器是如何建立的:咱們進入到
#createWebApplicationContext
方法中能夠看到它是經過實例化容器的class類來建立容器的,而在#determineContextClass
方法中首先經過初始化參數來獲取全路徑類名,若不存在則經過配置類#defaultStrategies
來獲取容器名稱.- 咱們能夠從當前類的靜態代碼找到此配置類,它經過讀取
ContextLoader.properties
配置文件來獲取當前容器全路徑類名稱
2. Servlet Application Contextapp
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware { public final void init() throws ServletException { //遍歷獲取servletConfig的全部參數 PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties); //初始化servlet applicationContext this.initServletBean(); } } -------------------------------------------------------------------------------------------------------------------------------------------------------- public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware { public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class; public FrameworkServlet() { this.contextClass = DEFAULT_CONTEXT_CLASS; this.contextInitializers = new ArrayList(); ... } protected final void initServletBean() throws ServletException { //................... try { this.webApplicationContext = this.initWebApplicationContext(); } } protected WebApplicationContext initWebApplicationContext() { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext()); WebApplicationContext wac = null; if(wac == null) { wac = this.createWebApplicationContext(rootContext); } if(this.publishContext) { String attrName = this.getServletContextAttributeName(); this.getServletContext().setAttribute(attrName, wac); if(this.logger.isDebugEnabled()) { this.logger.debug("Published WebApplicationContext of servlet '" + this.getServletName() + "' as ServletContext attribute with name [" + attrName + "]"); } } return wac; } protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) { return this.createWebApplicationContext((ApplicationContext)parent); } protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) { Class<?> contextClass = this.getContextClass(); if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); } else { ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); wac.setEnvironment(this.getEnvironment()); wac.setParent(parent); wac.setConfigLocation(this.getContextConfigLocation()); this.configureAndRefreshWebApplicationContext(wac); return wac; } } protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) { return this.createWebApplicationContext((ApplicationContext)parent); } protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) { Class<?> contextClass = this.getContextClass(); if(this.logger.isDebugEnabled()) { this.logger.debug("Servlet with name '" + this.getServletName() + "' will try to create custom WebApplicationContext context of class '" + contextClass.getName() + "', using parent context [" + parent + "]"); } if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); } else { ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); wac.setEnvironment(this.getEnvironment()); wac.setParent(parent); wac.setConfigLocation(this.getContextConfigLocation()); this.configureAndRefreshWebApplicationContext(wac); return wac; } } }
接下來咱們再看
DispatcherServlet
ui
- 做爲
Servlet
,根據規範它的配置信息應該是在#init
方法中完成,咱們首先進入到DispatcherServlet#init
,其方法是繼承自父類HttpServletBean
中.在父類的#init
方法中,首先經過遍歷獲取ServletConfig
的全部參數,而後進行Servlet Application Context
的初始化- 容器初始化方法
#initServletBean
位於父類FrameworkServlet
中,在#initServletBean
方法中調用#initWebApplicationContex
t方法initWebApplicationContext
方法中首先經過ServletContext
獲取Root Application Context
,而後經過#createWebApplicationContext
開始初始化Servlet Application Context
,其中獲取的#createWebApplicationContext
中獲取的class對象在構造方法中進行初始化的.在建立容器的過程會傳入Root Application Context
做爲它的Parent
,也就是在這裏二者創建父子關係,造成以前所說的繼承關係,最後一樣將新建立的容器放入ContextServlet
中.
因此在
Spring
中存在兩類context
,一類Root Application Context
,一類是Servlet Application Context
,前者根容器是惟一的,是其餘全部Servlet應用容器的父類.
以上就是關於在Spring
中容器的大體分析,咱們會在項目啓動時將不一樣的組件實例注入到Spring
容器中,從而實現Spring IOC
機制.this
- 若想要了解如何經過
Spring
註解方式自定義DispatcherServlet
相關內容能夠參考:Spring中關於的WebApplicationInitializer及其實現的分析- 若想要了解關於
SpringMVC
自定義配置化相關知識能夠參考: