Spring MVC的初始化邏輯分這麼幾步:
1、Tomcat掃描監聽,就是掃描web.xml中配置的Spring監聽;
<!--配置spring listener-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
2、Tomcat調用Spring實現的監聽接口方法 contextInitialized 進行初始化,初始化的目的就是得到Web應用上下文(WebApplicationContext),初始化的具體邏輯放在父類ContextLoader的方法中,即:initWebApplicationContext;同時Tomcat會把自己的Servlet容器上下文(servletContext)作爲參數傳給Spring;
3、Spring MVC初始化,得到Web應用上下文(WebApplicationContext),並且把它裝配到容器上下文中,細節是放到ServletContext的Map<String,Object> attributes屬性中去。
Tomcat: org.apache.catalina.core.ApplicationContext
/**
* The context attributes for this context.
*/
protected Map<String,Object> attributes = new ConcurrentHashMap<>();
@Override
public void setAttribute(String name, Object value) {
//。。。。
Object oldValue = attributes.put(name, value);
//。。。。
}
org.springframework.web.context.ContextLoader
// 這裏傳入的參數
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//。。。。。
}
Tomcat:org.apache.catalina.core.StandardContext
/**
* Configure the set of instantiated application event listeners
* for this Context.
* @return <code>true</code> if all listeners wre
* initialized successfully, or <code>false</code> otherwise.
*/
public boolean listenerStart() {
//....
// getServletContext()返回ApplicationContextFacade
ServletContextEvent event = new ServletContextEvent(getServletContext());
ServletContextEvent tldEvent = null;
if (noPluggabilityListeners.size() > 0) {
noPluggabilityServletContext = new NoPluggabilityServletContext(getServletContext());
tldEvent = new ServletContextEvent(noPluggabilityServletContext);
}
for (int i = 0; i < instances.length; i++) {
if (!(instances[i] instanceof ServletContextListener))
continue;
ServletContextListener listener =
(ServletContextListener) instances[i];
try {
fireContainerEvent("beforeContextInitialized", listener);
if (noPluggabilityListeners.contains(listener)) {
listener.contextInitialized(tldEvent);
} else {
// 從這裏進入Spring邏輯,傳的事件包含servletContext
listener.contextInitialized(event);
}
fireContainerEvent("afterContextInitialized", listener);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
fireContainerEvent("afterContextInitialized", listener);
getLogger().error
(sm.getString("standardContext.listenerStart",
instances[i].getClass().getName()), t);
ok = false;
}
}
return (ok);
}
/**
* @return the servlet context for which this Context is a facade.
*/
@Override
public ServletContext getServletContext() {
if (context == null) {
// 直接創建ApplicationContext對象
context = new ApplicationContext(this);
if (altDDName != null)
context.setAttribute(Globals.ALT_DD_ATTR,altDDName);
}
// 這裏是getFacade返回,所以上圖Spring ContextLoader.initWebApplicationContext方法參數【servletContext】對象的類型是ApplicationContextFacade
return (context.getFacade());
}
Spring MVC初始化:org.springframework.web.context.ContextLoader
/**
* Name of the class path resource (relative to the ContextLoader class)
* that defines ContextLoader's default strategy names.
*/
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
private static final Properties defaultStrategies;
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
/**
* 當前類中定義的Web應用上下文對象,有些文章稱之爲根應用上下文
* The root WebApplicationContext instance that this loader manages.
*/
private WebApplicationContext context;
// 父類實現初始化Web應用上下文邏輯
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!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
// 創建WebApplicationContext
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
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 ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 初始化核心方法,參照之前的Spring初始化文章
// 這個cwac是默認的XmlWebApplicationContext對象,它父類AbstractRefreshableApplicationContext中定義了:
// private DefaultListableBeanFactory beanFactory;
// 核心邏輯就是構造BeanFactory付給當前上下文的beanFactory,
// 實現方法在obtainFreshBeanFactory() -> AbstractRefreshableApplicationContext.refreshBeanFactory裏面,有興趣的可以去看看,我這裏就不貼了
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//把WebApplicationContext放到ServletContext屬性Map中去
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
// 創建web應用上下文
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);
}
// 決定使用哪種實現類
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 {
// 取默認配置的Web應用上下文初始化類,這個類是通過SPI方式提供
// 初始化就在上面的static代碼塊中
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);
}
}
}
# 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
綜上,項目初始化後的嵌套結構:
容器上下文門面(org.apache.catalina.core.ApplicationContextFacade)
- 容器上下文(org.apache.catalina.core.ApplicationContext)
- Web應用上下文(XmlWebApplicationContext)
- Bean工廠(DefaultListableBeanFactory)
其中:
- ApplicationContextFacade通過context屬性引用ApplicationContext,ApplicationContext通過facade引用ApplicationContextFacade;
- 容器上下文(org.apache.catalina.core.ApplicationContext)通過Map<String,Object> attributes存儲應用上下文(XmlWebApplicationContext);
- Web應用上下文又包含Bean工廠;
——由此,可以很明確地理解ServletContext,ApplicationContext,BeanFactory三者的關係了:
- ServletContext是容器上下文環境,它是WEB應用的基石或者叫宿主上下文;
- ApplicationContext是應用上下文,有人說它是由BeanFactory派生出來的,我覺得這話不是很恰當,因爲應用上下文完全可以不建立在BeanFactory基礎之上,只不過呢,它通常都會使用BeanFactory做Bean的維護(這麼好的東西幹嘛不用);
- BeanFactory則是Spring框架提供的最基礎的Bean管理器;如果是Java應用的話,這個BeanFactory的最終類型是:DefaultListableBeanFactory,而包裝這個BeanFactory的上下文就是ClassPathXmlApplicationContext,FileSystemXmlApplicationContext等等,這些Context與BeanFactory的引用關係在AbstractRefreshableApplicationContext抽象類中;
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
/** Bean factory for this context */
private DefaultListableBeanFactory beanFactory;
}
我們上面分析的Spring MVC就是其中的一個應用框架,它有自己的上下文環境,當然更多的框架沒有自己的上下文環境,而是選擇直接讓Spring來管理自己的Bean,比如:Mybatis,Durid等等。
這同時也解釋了Spring與Spring MVC的關係,Spring提供的BeanFactory,AOP,DI都是其他框架的底層基石,要麼在Spring的基礎之上增加自己的上下文環境,要麼直接使用Spring提供的上下文;