天天看點

面試之Spring的啟動原理引入Spring啟動過程總結

引入

為什麼突然說一下Spring啟動原理呢,因為之前面試的時候,回答的那可謂是坑坑窪窪,前前後後,補補貼貼。。。

總而言之就是不行,再次看一下源碼發掘一下。。。

在Spring Boot還沒有廣泛到家家在用的時候,我們都還在書寫繁瑣的配置,什麼web.xml、spring.xml、bean.xml等等。雖然現在很少,可以說幾乎沒有企業在去使用Spring的老一套,而會去使用Spring Boot約定大于配置來進行快速開發,但是,Spring的也要去學習,去挖掘,畢竟是我們Java程式員的基礎呀。

spring的啟動是建築在servlet容器之上的,所有web工程的初始位置就是web.xml,它配置了servlet的上下文(context)和監聽器(Listener)

web.xml

<!--上下文監聽器,用于監聽servlet的啟動過程-->
    <listener>
        <description>ServletContextListener</description>
        <!--這裡是自定義監聽器,個性化定制項目啟動提示-->
        <listener-class>com.trace.app.framework.listeners.ApplicationListener</listener-class>
    </listener>
    <!--dispatcherServlet的配置,這個servlet主要用于前端控制,這是springMVC的基礎-->
    <servlet>
        <servlet-name>service_dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/services/service_dispatcher-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!--spring資源上下文定義,在指定位址找到spring的xml配置檔案-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/application_context.xml</param-value>
    </context-param>
    <!--spring的上下文監聽器-->
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
    <!--Session監聽器,Session作為公共資源存在上下文資源當中,這裡也是自定義監聽器-->
    <listener>
        <listener-class>
            com.trace.app.framework.listeners.MySessionListener
        </listener-class>
    </listener>           

複制

Spring啟動過程

spring的上下文監聽器

<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/application_context.xml</param-value>
</context-param>

<listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
 </listener>           

複制

spring的啟動其實就是IOC容器的啟動過程,通過上述的第一段配置

<context-param>

是初始化上下文,然後通過後一段的的

<listener>

來加載配置檔案,其中調用的spring包中的

ContextLoaderListener

這個上下文監聽器,

ContextLoaderListener

是一個實作了ServletContextListener接口的監聽器,他的父類是 ContextLoader,在啟動項目時會觸發contextInitialized上下文初始化方法。

public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
}           

複制

調用了父類ContextLoader的initWebApplicationContext(event.getServletContext());方法,很顯然,這是對ApplicationContext的初始化方法,也就是到這裡正是進入了springIOC的初始化。

接下來看一下initWebApplicationContext(event.getServletContext())的工作:

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

複制

總結:

  • 建立WebApplicationContext。
  • 加載對應的spring配置檔案中的Bean。
  • 将WebApplicationContext放入ServletContext(Java Web的全局變量)中。

接下來,來到了

configureAndRefreshWebApplicationContext()

方法

作用:

就是用來加載spring配置檔案中的Bean執行個體的。這個方法于封裝ApplicationContext資料并且初始化所有相關Bean對象。它會從

web.xml

中讀取名為 contextConfigLocation的配置,這就是spring xml資料源設定,然後放到ApplicationContext中,最後調用傳說中的

refresh

方法執行所有Java對象的建立。

總結:

面試之Spring的啟動原理引入Spring啟動過程總結

總結

  1. 首先對于一個web應用,需要部署到web容器中,web容器提供了一個全局的上下文環境,ServletContext,SpringIOC的宿主環境。
  2. 其次,在web容器啟動時,觸發容器初始化,web.xml中提供的有ContextLoaderListener監聽器會監聽這個事件,初始化方法contextInitialized被調用,初始化spring上下文

    WebApplicationContext接口,實作類時XmlWebApplicationContext即SpringIOC容器,對應的Bean定義是有context-param标簽定義指定,然後存儲到ServletContext中,友善擷取。

  3. ContextLoaderListener監聽初始化完成後,開始初始化web.xml中配置的Servlet,指DisapatchServlet前端控制器,用來比對,轉發,處理每個Servlet請求,DisaptchServlet初始化時會建立自己的IOC上下文,用來持有Spring MVC的相關bean。

    首先會從之前初始化存儲在ServletContext中的上下文左右parent上下文,再初始化自己的上下文,大概的工作就是初始化處理器映射、視圖解析等。這個servlet自己持有的上下文預設實作類也是xmlWebApplicationContext。然後存儲到ServletContext。每個Servlet擁有自己的上下文,也會共享parent的上下文。

下期講解refresh()做了什麼,以及Spring Boot的啟動原理,敬請觀看,謝謝。。。

下期講解refresh()做了什麼,以及Spring Boot的啟動原理,敬請觀看,謝謝。。。

下期講解refresh()做了什麼,以及Spring Boot的啟動原理,敬請觀看,謝謝。。。