1. 程式人生 > >spring原始碼(一) springmvc啟動過程,springmvc初始化過程

spring原始碼(一) springmvc啟動過程,springmvc初始化過程

spring mvc配置

我們知道要想使用springmvc,一般需要配置如下

web.xml中配置ContextLoaderListener來載入spring根配置檔案。

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

再加一個spring的根配置檔案application-context.xml,可以什麼內容都不寫。大家有沒有想過ContextLoaderListener的作用是什麼,具體做了什麼?如果先思考一下,帶著問題再去看原始碼,更有益處!

當然為了能正常接收請求,還需要配置DispatcherServlet及對應的mvc配置檔案,這裡暫不配置,以專注springmvc初始化。

分析

很明顯ContextLoaderListener是入口,首先看類繼承層次圖(intellij idea快捷鍵 ctrl+alt+shift+u
這裡寫圖片描述
ContextLoaderListener實現了ServletContextListener,這個介面的contextInitialized方法會在容器初始化時被呼叫。

ContextLoaderListener.java
/**
     * Initialize the root web application context.
     */
    @Override
    public void contextInitialized
(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); }

contextInitialized 方法很簡單,呼叫了ContextLoaderinitWebApplicationContext

ContextLoader.java
/**
     * Initialize Spring's web application context for the given servlet context,
     * using the application context provided at construction time, or creating a new one
     * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
     * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
     * @param servletContext current servlet context
     * @return the new WebApplicationContext
     * @see #ContextLoader(WebApplicationContext)
     * @see #CONTEXT_CLASS_PARAM
     * @see #CONFIG_LOCATION_PARAM
     */
    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) {
                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);
                    }
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
            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;
        }
    }

首先看註釋,這個方法就是為給定的servlet context初始化spring application context,spring application context有兩個來源,要麼是使用構造方法傳入的,要麼通過web.xml中context-param指定contextClass、contextConfigLocation新建立的 ,一句話概括,這個方法是用來初始化spring application context的。方法返回值是WebApplicationContext,類繼承層次圖
這裡寫圖片描述

initWebApplicationContext()方法內部,首先判斷ServletContext是否已經存在spring application context,如果存在則丟擲錯誤。否則建立上下文this.context = createWebApplicationContext(servletContext);, 然後**配置並重新整理**WebApplicationContextconfigureAndRefreshWebApplicationContext,然後將spring application context設定到servletcontext。簡單來說

這裡最重要的兩個方法是createWebApplicationContextconfigureAndRefreshWebApplicationContext,坦白的說,createWebApplicationContext是建立了WebApplicationContext這個物件,在此基礎上,configureAndRefreshWebApplicationContext修改了WebApplicationContext的屬性。

/**
     * Instantiate the root WebApplicationContext for this loader, either the
     * default context class or a custom context class if specified.
     * <p>This implementation expects custom contexts to implement the
     * {@link ConfigurableWebApplicationContext} interface.
     * Can be overridden in subclasses.
     * <p>In addition, {@link #customizeContext} gets called prior to refreshing the
     * context, allowing subclasses to perform custom modifications to the context.
     * @param sc current servlet context
     * @return the root WebApplicationContext
     * @see ConfigurableWebApplicationContext
     */
    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);
    }

看方法註釋,使用預設的context class或者 自定義的context class例項化root webapplicationcontext。如果是自定義context class,那麼它要實現ConfigurableWebApplicationContext介面。
看程式碼,通過determineContextClass()返回context class,如果不是ConfigurableWebApplicationContext,則丟擲異常,也就是說,這個方法只能返回ConfigurableWebApplicationContext型別的ApplicaContext,為了一窺究竟,我們看determineContextClass()

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

web.xml中沒有配置context_class_param,所以只能是contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
返回context class,繼續跟蹤defaultStrategies

ContextLoader.java
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());
        }
    }
ContextLoader.properties
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

defaultStrategies返回的是XmlWebApplicationContext這是通過ContextLoader.properties指定的,通過檢視類層次圖,XmlWebApplicationContextConfigurableWebApplicationContext的子類
這裡寫圖片描述

這裡就清楚了,我們再繼續看ContextLoader.configureAndRefreshWebApplicationContext(),對ApplicationContext做了哪些修改。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {

        wac.setServletContext(sc);
        String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
        if (configLocationParam != null) {
            wac.setConfigLocation(configLocationParam);
        }

        // The wac environment's #initPropertySources will be called in any case when the context
        // is refreshed; do it eagerly here to ensure servlet property sources are in place for
        // use in any post-processing or initialization that occurs below prior to #refresh
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
        }

        customizeContext(sc, wac);
        wac.refresh();
    }

程式碼裡將contextConfigLocation設定到ConfigurableWebApplicationContext((ConfigurableWebEnvironment) env).initPropertySources(sc, null);初始化servlet context的property source,執行customizeContext()來執行自定義邏輯,最後執行refresh() 因為此時的wac是XmlWebApplicationContextrefresh()方法在父類AbstractApplicationContext中定義,所以執行AbstractApplicationContext.refresh()

AbstractApplicationContext.java
@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

大體上就是準備資源,然後建立BeanFactory、然後後處理BeanFactory(postProcessBeanFactory())、然後執行BeanFactory的後置處理器invokeBeanFactoryPostProcessors、然後註冊BeanPostProcessor……,這裡不要犯迷糊,是先建立BeanFactory、然後執行BeanFactory的後置處理器,再註冊BeanPostProcessor,然後在建立bean時,後置處理Bean。如果這個過程中失敗,則確保已經建立的bean都毀滅。

總結

  • spring mvc初始化,建立的ApplicationContext是XmlWebApplicationContext,BeanFactory是DefaultListableBeanFactory,最重要的兩個方法是createWebApplicationContext()AbstractApplicationContext.refresh() ,尤其是refresh()方法,下一步將此方法中的每個步驟搞明白。
  • spring原始碼很優秀,整個springmvc初始化過程,邏輯很清晰;除此之外,在類層次結構設計上很龐大;
  • 再說一點看原始碼的心得,看原始碼先大概流程,後細節,別一頭扎進某個方法,看大概幹了什麼,梳理流程,然後再挖下去,這個過程中輔助類繼承層次圖(ctrl+alt+shift+u)至關重要,還有方法呼叫鏈使用ctrl+alt+h(intellij idea)也很實用。