Spring MVC源碼分析之——從Tomcat到Spring BeanFactory

 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提供的上下文;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章