SpringMVC(十七):Web.xml初始化流程源碼分析


上一篇講解了web.xml如何使用編碼的方式替換掉,但是一直沒有寫web.xml是如何被加載的相關細節,覺得十分有必要寫一篇文章來梳理下。

Web應用部署初始化

當一個web應用被部署到容器(tomcat等),tomcat系統啟動過程中會執行以下處理:

1)部署描述文件(tomcat對應web.xml)中定義的(由<listener>元素標記的)事件監聽器會被創建和初始化;
2)對於所有事件監聽器,如果實現了ServletContextListener接口,將會執行#contextInitialized()方法;
3)部署描述文件中定義的(由<filter>元素標記的)過濾器會被創建和初始化,並調用#init()方法;
4)部署描述文件中定義的(由<servlet>元素標記的)servlet會根據<load-on-startup>的權重按順序創建和初始化,並調用#init()方法。

備注:

1)<load-on-startup>權值如果設置為負整數,則不會在啟動服務器時執行;包含>=0時,會在啟動服務器時執行。

2)具體參考:《Java Servlet(十二):Servlet、Listener、Filter之間的執行流程分析》測試程序部署啟動日志。

Web的初始化流程如下:

Tomcat初始化與處理Http請求

Tomcat初始化

Tomcat初始化解析Web.xml文件

要理清web.xml如何被加載、解析,需要從tomcat源碼入手:

一個Tomcat中只有一個Server,一個Server可以包含多個Service,一個Service只有一個Container,但是可以有多個Connectors,這是因為一個服務可以有多個連接,如同時提供Http和Https鏈接,也可以提供向相同協議不同端口的連接。

在Container用於封裝和管理Servlet,以及具體的處理Request請求,在Conatiner內部包含4個子容器:

1)Engine:引擎,用來管理多個站點,一個Servcie最多只能有一個Engine;
2)Host:代表一個站點,也可以叫虛擬主機,通過配置host就可以添加站點;
3)Context:代表一個應用程序,對應着平時開發的一套程序,或者一個WEB-INF目錄以及下面的web.xml文件;
4)Wrapper:每一個Wrapper封裝這一個Servlet。

Tomca的心臟是兩個組件:Connector和Container(Engine,Host,Context,Wrapper)。一個Container可以選擇多個Connecter,多個Connector和一個Container就形成了一個Service。Service可以對外提供服務,而Server服務器控制整個Tomcat的生命周期。 

從上邊分析可以知道Context是代表一個應程序,或者一個WEB-INF目錄以及下邊的web.xml文件,可以從它的作用上可以明白Context對web.xml的加載解析啟動了特別重要的作用。Tomcat的啟動流程:

從上圖可以知道Tomcat中啟動過程中會調用Context的唯一實現類StandardContext的#init()和#start(),在這里Tomcat采用了模板設計模式,StandardContext中實際實現方法對應的是initInternal()和startInternal()。

StandardContext#startInternal()

    @Override
    protected synchronized void startInternal() throws LifecycleException { // 此處省略代碼... try { if (ok) { // 此處省略代碼... // 1)加載web.xml // Notify our interested LifecycleListeners fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null); // 此處省略代碼...  } // 此處省略代碼... // 2)合並參數 // Set up the context init params  mergeParameters(); // 此處省略代碼... // 3)listener啟動 // Configure and call application event listeners if (ok) { if (!listenerStart()) { log.error(sm.getString("standardContext.listenerFail")); ok = false; } } // 此處省略代碼... // 4)filter啟動 // Configure and call application filters if (ok) { if (!filterStart()) { log.error(sm.getString("standardContext.filterFail")); ok = false; } } // 此處省略代碼... // 5)加載初始化servlet // Load and initialize all "load on startup" servlets if (ok) { if (!loadOnStartup(findChildren())){ log.error(sm.getString("standardContext.servletFail")); ok = false; } } // 此處省略代碼... } finally { // Unbinding thread  unbindThread(oldCCL); } // 此處省略代碼... }

1)調用fireLifecycleEvent()
發布一個"configure_start" 事件,在眾多監聽器中有一個ContextConfig監聽器,在ContextConfig監聽到"configure_start" 事件后, 會執行configureStart()方法;在configureStart()方法中執行webConfig()開始web.xml解析;值得一提的是webConfig中調用的兩個方法:
ContextConfig#parseWebXml(contextWebXml, webXml, false)方法:
這個方法中有一個Digester工具,在Tomcat加載server.xml配置文件的時候就是使用了此工具,解析原理異曲同工。 此處使用WebRuleSet規則,將web.xml文件中的配置讀取出來設置到webXml對象中去。
ContextConfig#configureContext(StandardContext context)方法:
將web.xml文件解析出來的各個組件設置到標准servlet上下文StandardContext中去。 其中就包括我們的filter ,servlet,listener。

2)mergeParameters
步驟1)中會將web.xml中的context-param元素設置到context的parameters里,此處則是把parameters設置到servletContext里。

3)啟動listener
步驟1)中會將web.xml中的listener元素設置到context的applicationListeners里,此處則取出listener類名,創建實例,並將listener分為兩類
eventlistener:ServletRequestAttributeListener、ServletRequestListener、HttpSessionIdListener、HttpSessionAttributeListener
lifecyclelistener:ServletContextListener、HttpSessionListener
對於ServletContextListener,會調用listener.contextInitialized(event),並觸發實現了ServletContextListener接口實現類的beforeContextInitialized,afterContextInitialized方法。

4)啟動filter
步驟1)中會將web.xml中的filter元素設置到filter的filterdef里,此處則會實例化filter設置到filterConfigs里。

5)啟動servlet
步驟1)中會將web.xml中的servlet元素封裝成wrapper並調用addChild方法設置到Context里,此處則會檢查是否需要loadonstartup,如果需要則load。

更詳細介紹請參考:《Tomcat(一):Tomcat啟動時加載web.xml

Tomcat處理Http請求

Tomcat處理Http請求過程流程圖:

假設來自客戶的請求為:http://localhost:8080/test/index.jsp 請求被發送到本機端口8080

1、用戶點擊網頁內容,請求被發送到本機端口8080,被在那里監聽的Coyote HTTP/1.1 Connector獲得。 
2、Connector把該請求交給它所在的Service的Engine來處理,並等待Engine的回應。 
3、Engine獲得請求localhost/test/index.jsp,匹配所有的虛擬主機Host。 
4、Engine匹配到名為localhost的Host(即使匹配不到也把請求交給該Host處理,因為該Host被定義為該Engine的默認主機),名為localhost的Host獲得請求/test/index.jsp,匹配它所擁有的所有的Context。Host匹配到路徑為/test的Context(如果匹配不到就把該請求交給路徑名為“ ”的Context去處理)。 
5、path=“/test”的Context獲得請求/index.jsp,在它的mapping table中尋找出對應的Servlet。Context匹配到URL PATTERN為*.jsp的Servlet,對應於JspServlet類。 
6、構造HttpServletRequest對象和HttpServletResponse對象,作為參數調用JspServlet的doGet()或doPost().執行業務邏輯、數據存儲等程序。 
7、Context把執行完之后的HttpServletResponse對象返回給Host。 
8、Host把HttpServletResponse對象返回給Engine。 
9、Engine把HttpServletResponse對象返回Connector。 
10、Connector把HttpServletResponse對象返回給客戶Browser。

Tomcat哪些組件參與了Http請求處理

Connector相關組件

注意:不同的協議、不同的通信方式,ProtocolHandler會有不同的實現。在Tomcat8.5中,ProtocolHandler類的集成層級如下圖所示:

ProtocolHandler包含三個部件:Endpoint、Processor、Adapter。

1)Endpoint:用來處理底層Socket網絡連接,因此Endpoint是用來實現TCP/IP協議的;Endpoint的抽象實現類AbstractEndpoint里面定義了Acceptor和AsyncTimeout兩個內部類和一個Handler接口。

1.1)Acceptor:用於監聽請求;

1.2)AsyncTimeout:用於檢查異步Request的超時;

1.3)Handler:用於處理接收到的Socket,在內部調用Processor進行處理。

2)Processor:用來將Endpoint接收到Socket封裝成Request,Precessor用來實現Http協議的;

3)Adapter:用來將Request交給Container進行具體處理,Adapter想請求適配到Servlet容器進行具體的處理。

CoyoteAdapter#service()

Adapter用於連接ConnectorContainer,起到承上啟下的作用。Processor會調用Adapter.service()方法。

    @Override
    public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception {
        Request request = (Request) req.getNote(ADAPTER_NOTES);
        Response response = (Response) res.getNote(ADAPTER_NOTES);

        // 此處忽略代碼

        try {
            // Parse and set Catalina and configuration specific request parameters
            postParseSuccess = postParseRequest(req, request, res, response);
            if (postParseSuccess) {
                //check valves if we support async
                request.setAsyncSupported(
                        connector.getService().getContainer().getPipeline().isAsyncSupported());
                // Calling the container
                connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);
            }
            // 此處忽略代碼
        } catch (IOException e) {
            // Ignore
        } finally {
            // 此處忽略代碼
        }
    }

如果請求可以被傳給容器的Pipeline即當postParseRequest方法返回true時,則由容器繼續處理,在service方法中有connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)這一行:

  • Connector調用getService返回StandardService;
  • StandardService調用getContainer返回StandardEngine;
  • StandardEngine調用getPipeline返回與其關聯的StandardPipeline;

接下來將會調用EnginePipeline#StandardEngineValve->HostPipeline#StandardHostValve->ContextPipeline#StandardContextValve->WrapperPipeline#StandardWrapperValve。

StandardWrapperValve#invoker(...)

    /**
     * Invoke the servlet we are managing, respecting the rules regarding
     * servlet lifecycle and SingleThreadModel support.
     *
     * @param request Request to be processed
     * @param response Response to be produced
     *
     * @exception IOException if an input/output error occurred
     * @exception ServletException if a servlet error occurred
     */
    @Override
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {
        // 此處省略代碼
        StandardWrapper wrapper = (StandardWrapper) getContainer();
        Servlet servlet = null;
        Context context = (Context) wrapper.getParent();

        // 此處省略代碼
        // 分配一個servlet實例用來處理請求
        // Allocate a servlet instance to process this request
        try {
            if (!unavailable) {
                // 調用StandardWrapper#allocate()方法,獲取到servlet實例
                servlet = wrapper.allocate();
            }
        } catch (UnavailableException e) {
            // 此處省略代碼
        } catch (ServletException e) {
            // 此處省略代碼
        } catch (Throwable e) {
            // 此處省略代碼
        }

        // 此處省略代碼
        // 為當前請求創建一個過濾器鏈
        // Create the filter chain for this request
        ApplicationFilterChain filterChain =
                ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

        // 為當前請求調用過濾器鏈,注意:這也會調用servlet實例的service()方法
        // Call the filter chain for this request
        // NOTE: This also calls the servlet's service() method
        try {
            if ((servlet != null) && (filterChain != null)) {
                // Swallow output if needed
                if (context.getSwallowOutput()) {
                    try {
                        SystemLogHandler.startCapture();
                        if (request.isAsyncDispatching()) {
                            request.getAsyncContextInternal().doInternalDispatch();
                        } else {
                            filterChain.doFilter(request.getRequest(),
                                    response.getResponse());
                        }
                    } finally {
                        String log = SystemLogHandler.stopCapture();
                        if (log != null && log.length() > 0) {
                            context.getLogger().info(log);
                        }
                    }
                } else {
                    if (request.isAsyncDispatching()) {
                        request.getAsyncContextInternal().doInternalDispatch();
                    } else {
                        filterChain.doFilter
                            (request.getRequest(), response.getResponse());
                    }
                }
            }
        } catch (ClientAbortException | CloseNowException e) {
            // 此處省略代碼
        } catch (IOException e) {
            // 此處省略代碼
        } catch (UnavailableException e) {
            // 此處省略代碼
        } catch (ServletException e) {
            // 此處省略代碼
        } catch (Throwable e) {
            // 此處省略代碼
        } finally {
            // 釋放資源
            // Release the filter chain (if any) for this request
            if (filterChain != null) {
                filterChain.release();
            }

            // Deallocate the allocated servlet instance
            try {
                if (servlet != null) {
                    wrapper.deallocate(servlet);
                }
            } catch (Throwable e) {
                // 此處省略代碼
            }

            // If this servlet has been marked permanently unavailable,
            // unload it and release this instance
            try {
                if ((servlet != null) &&
                    (wrapper.getAvailable() == Long.MAX_VALUE)) {
                    wrapper.unload();
                }
            } catch (Throwable e) {
                // 此處省略代碼
            }
            // 此處省略代碼
        }
    }

該StandardWrapperValve正是將Tomcat在處理Http請求過程中,真正調用filterChain(內部執行filter.doFilter())、調用servlet.service(...)代碼的地方。

ApplicationFilterChain#doFilter(...)

    /**
     * Invoke the next filter in this chain, passing the specified request
     * and response.  If there are no more filters in this chain, invoke
     * the <code>service()</code> method of the servlet itself.
     *
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     *
     * @exception IOException if an input/output error occurs
     * @exception ServletException if a servlet exception occurs
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
        if( Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            try {
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedExceptionAction<Void>() {
                        @Override
                        public Void run()
                            throws ServletException, IOException {
                            internalDoFilter(req,res);
                            return null;
                        }
                    }
                );
            } catch( PrivilegedActionException pe) {
                // 此處省略代碼;
            }
        } else {
            internalDoFilter(request,response);
        }
    }

    /**
     * 職責鏈模式調用filter#doFilter,並在調用最底層調用servlet.service(request,response)。
     */
    private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {

        // Call the next filter if there is one
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            try {
                Filter filter = filterConfig.getFilter();

                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
                }
                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
                } else {
                    filter.doFilter(request, response, this);
                }
            } catch (IOException | ServletException | RuntimeException e) {
                throw e;
            } catch (Throwable e) {
                // 此處省略代碼
            }
            return;
        }

        // We fell off the end of the chain -- call the servlet instance
        try {
            // 此處省略代碼
            // Use potentially wrapped request from this point
            if ((request instanceof HttpServletRequest) &&
                    (response instanceof HttpServletResponse) &&
                    Globals.IS_SECURITY_ENABLED ) {
                final ServletRequest req = request;
                final ServletResponse res = response;
                Principal principal =
                    ((HttpServletRequest) req).getUserPrincipal();
                Object[] args = new Object[]{req, res};
                SecurityUtil.doAsPrivilege("service",
                                           servlet,
                                           classTypeUsedInService,
                                           args,
                                           principal);
            } else {
                servlet.service(request, response);
            }
        } catch (IOException | ServletException | RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            // 此處省略代碼
        } finally {
            // 此處省略代碼
        }
    }

采用職責鏈設計模式,在filter最深層調用servlet.service(request,response)。

在Spring+SpringMvc項目中,Tomcat處理Http請求時的流程圖如下:

圖片來自《Tomcat原理系列之二:由點到線,請求主干

SpringMVC初始化流程

啟動時需要加載部署描述文件(web.xml),我們本章的給出一個比較常見的web.xml為例,用它來分析SpringMVC的啟動流程。

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
    <display-name>Archetype Created Web Application</display-name>

    <welcome-file-list>
        <welcome-file>/index</welcome-file>
    </welcome-file-list>

    <!--加載dao/service/一些共享組件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:applicationContext-base.xml,
            classpath:applicationContext-security.xml
        </param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
    </listener>

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <!-- 默認是false -->
            <param-value>false</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>multipartFilter</filter-name>
        <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
        <init-param>
            <param-name>multipartResolverBeanName</param-name>
            <param-value>multipartResolver</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>multipartFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
        <init-param>
            <param-name>methodParam</param-name>
            <param-value>_method</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!--加載springmvc controller viewsolver 等-->
    <servlet>
        <servlet-name>spring-security-01</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-security-01-servlet.xml</param-value>
        </init-param>
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>org.springframework.web.context.support.XmlWebApplicationContext</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring-security-01</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

在Spring+SpringMVC項目被部署到tomcat容器,啟動項目時會去加載web.xml配置文件,會根據web.xml配置內容去進行初始化。

Listener初始化

ContextLoaderListener實現了ServletContextListener,它本身就是一個listener,在Tomcat啟動過程中web.xml加載后,StandardContext#startInternal()方法中調用的StandardContext#listenerStart()中執行的。

下邊查看StandardContext#listenerStart()源碼:

    public boolean listenerStart() {
             ...
            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 {
                    //執行listener的初始。傳遞ServletContextEvent參數
                    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;
            }
            }
             ...
     }

因為ContextLoaderListener實現ServletContextListener接口,因此上邊調用ServletContextListener#contextInitialized(...)實現代碼就是ContextLoaderListener#contextInitialized(ServletContextEvent event)方法:

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

initWebApplicationContext(...)是ContextLoader的方法(ContextLoaderListener不但實現了ServletContextListener接口,還繼承了ContextLoader類),接下來查看ContextLoader#initWebApplicationContext(...)源碼:

    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!");
        }

        servletContext.log("Initializing Spring root WebApplicationContext");
        Log logger = LogFactory.getLog(ContextLoader.class);
        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.isInfoEnabled()) {
                long elapsedTime = System.currentTimeMillis() - startTime;
                logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
            }

            return this.context;
        }
        catch (RuntimeException | Error ex) {
            logger.error("Context initialization failed", ex);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
            throw ex;
        }
    }

上邊代碼核心方法包含兩個:

1)ContextLoader#createWebApplicationContext(ServletContext sc):

createWebApplicationContext(ServletContext sc)方法用來創建WebApplicationContext實例:

    /**
     * 返回WebApplicationContext的實現類,否則返回默認XmlWebApplicationContext或者通過開發者自定義.
     * 此實現要求自定義上下文實現ConfigurableWebApplicationContext接口。可以在子類中重寫。
     * 此外,{#customizeContext}在刷新上下文之前被調用,從而允許子類對上下文執行自定義修改。
     * @param sc 當前servlet上下文
     * @return 根WebApplicationContext
     */
    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);
    }

ContextLoader#determineContextClass()方法:

    /**
     * 返回WebApplicationContext的實現類,否則返回默認XmlWebApplicationContext或者通過開發者自定義.
     * @param servletContext 當前servlet上下文
     * @return  WebApplicationContext的實現類
     */
    protected Class<?> determineContextClass(ServletContext servletContext) {
        String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);//"contextClass"
        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 { // 否則,讀取ContextLoader.properties中配置的key=WebApplicationContetxt.class.getName()的value,這里返回XmlWebApplicationContext.class.getName()
            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);
            }
        }
    }

ContextLoader#defaultStrategies屬性:

    /**
     * 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());
        }
    }

ContextLoader.properties是spring-web-5.2.0.RELEASE.jar中內的配置文件/org/springframework/web/context/ContextLoader.properties

# 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

結合ContextLoader.properties中配置,可以得知ContextLoader#defaultStrategies屬性值配置的值就是:{key=WebApplicationContext.class,value=XmlWebApplicationContext.class}。

2)ContextLoader#configureAndRefreshWebApplicationContext(cwac, servletContext):

configureAndRefreshWebApplicationContext(cwac, servletContext)方法作用是‘配置’和‘刷新’WebApplicationContext:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
   if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
      // The application context id is still set to its original default value
      // -> assign a more useful id based on available information
      String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
      if (idParam != null) {
         wac.setId(idParam);
      }
      else {
         // Generate default id...
         wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
               ObjectUtils.getDisplayString(sc.getContextPath()));
      }
   }

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

wac.setConfigLocation(configLocationParam): CONFIG_LOCATION_PARAM = "contextConfigLocation" 獲取web.xml中<context-param>標簽配置的全局變量,其中key為CONFIG_LOCATION_PARAM,也就是我們配置的相應Bean的xml文件名,並將其放入到WebApplicationContext中

wac是一個接口類:我們知道ac的真正實現類是XmlWebApplicaitonContext,因此wac.refresh()調用就是XmlWebApplicationContext#refresh(),注意XmlWebApplcationContext的refresh()是定義在它的抽象父類接口類AbstractApplicationContext.java中。

下邊看下AbstractApplicationContext#refresh()源碼:

@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();
            }
        }
    }

上邊refresh()方法初始化bean,就是spring的著名的初始化方法refresh()。在Tomcat啟動時,加載Spring+SpringMVC項目中web.xml定義的ContextLoaderListener,ContextLoaderListener實現ServletContextListener接口,繼承ContextLoader加載器,進而把Tomcat與Spring連接到了一起。

Filter初始化

在監聽器listener初始化完成后,按照文章開始的講解,接下來會進行filter的初始化操作,filter的創建和初始化中沒有涉及IoC容器的相關操作,因此不是本文講解的重點,本文舉例的filter是一個用於編碼用戶請求和響應的過濾器,采用utf-8編碼用於適配中文。

關於Filter的在請求過程中與Servlet調用原理,請參考上邊Tomcat處理Http請求過程。

Servlet(DispatcherServlet)初始化

上邊在介紹Tomcat啟動過程中解析web.xml時,講到Servlet的初始化與調用核心代碼,這里將主要針對啟動這部分進一步介紹,關於調用部分不再闡述。

在Spring+SpringMVC項目中web.xml中servlet配置了DispatcherServlet,Tomcat在啟動過程中DispatcherServlet就是一個普通的Servlet(DispatcherServlet extends FrameworkServlet,FrameworkServlet extends HttpServletBean,HttpServletBean extends HttpServlet,HttpServlet extends GenericServlet,GenericServlet extends Servlet),從上邊結合Tomcat加載啟動web.xml過程可以知道StandardContext#startInternal()調用時,加載並初始化Servlet,調用的方法是StandardContext#loadOnStartup(...)。

StandardContext#loadOnStartup(...)

    /**
     * Load and initialize all servlets marked "load on startup" in the
     * web application deployment descriptor.
     *
     * @param children Array of wrappers for all currently defined
     *  servlets (including those not declared load on startup)
     * @return <code>true</code> if load on startup was considered successful
     */
    public boolean loadOnStartup(Container children[]) {
        // 1)收集到配置<load-on-startup>值大於等於0的servlet,后邊回去初始化它們。
        // Collect "load on startup" servlets that need to be initialized
        TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
        for (int i = 0; i < children.length; i++) {
            Wrapper wrapper = (Wrapper) children[i];
            int loadOnStartup = wrapper.getLoadOnStartup();
            if (loadOnStartup < 0)
                continue;
            Integer key = Integer.valueOf(loadOnStartup);
            ArrayList<Wrapper> list = map.get(key);
            if (list == null) {
                list = new ArrayList<>();
                map.put(key, list);
            }
            list.add(wrapper);
        }
      
        // 2)初始化<load-on-startup>值大於等於0的servlet,通過調用servlet.load()方法。
        // Load the collected "load on startup" servlets
        for (ArrayList<Wrapper> list : map.values()) {
            for (Wrapper wrapper : list) {
                try {
                    wrapper.load();
                } catch (ServletException e) {
                    getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
                          getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
                    // NOTE: load errors (including a servlet that throws
                    // UnavailableException from the init() method) are NOT
                    // fatal to application startup
                    // unless failCtxIfServletStartFails="true" is specified
                    if(getComputedFailCtxIfServletStartFails()) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

Wrapper的實現類結構:

 

其中上邊StandardContext#loadOnStartup(...)中調用的wrapper.load(),load()方法的實現就是StandardWrapper#load()。

StandardWrapper#load()

    @Override
    public synchronized void load() throws ServletException {
        instance = loadServlet();

        if (!instanceInitialized) {
            initServlet(instance);
        }

        if (isJspServlet) {
            StringBuilder oname = new StringBuilder(getDomain());

            oname.append(":type=JspMonitor");

            oname.append(getWebModuleKeyProperties());

            oname.append(",name=");
            oname.append(getName());

            oname.append(getJ2EEKeyProperties());

            try {
                jspMonitorON = new ObjectName(oname.toString());
                Registry.getRegistry(null, null).registerComponent(instance, jspMonitorON, null);
            } catch (Exception ex) {
                log.warn("Error registering JSP monitoring with jmx " + instance);
            }
        }
    }

主要代碼:

1)loadServlet():加載Servlet類,並返回其實例對象;

2)initServlet():如果Servlet實例未初始化,則調用Servlet#init()方法初始化servlet。

StandardWrapper#loadServlet()

    public synchronized Servlet loadServlet() throws ServletException {
        // Nothing to do if we already have an instance or an instance pool
        if (!singleThreadModel && (instance != null))
            return instance;

        PrintStream out = System.out;
        if (swallowOutput) {
            SystemLogHandler.startCapture();
        }

        Servlet servlet;
        try {
            long t1=System.currentTimeMillis();
            // Complain if no servlet class has been specified
            if (servletClass == null) {
                unavailable(null);
                throw new ServletException
                    (sm.getString("standardWrapper.notClass", getName()));
            }

            InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
            try {
                servlet = (Servlet) instanceManager.newInstance(servletClass);
            } catch (ClassCastException e) {
                // 此處省略代碼
            } catch (Throwable e) {
                // 此處省略代碼
            }

            if (multipartConfigElement == null) {
                MultipartConfig annotation =
                        servlet.getClass().getAnnotation(MultipartConfig.class);
                if (annotation != null) {
                    multipartConfigElement =
                            new MultipartConfigElement(annotation);
                }
            }

            // Special handling for ContainerServlet instances
            // Note: The InstanceManager checks if the application is permitted
            //       to load ContainerServlets
            if (servlet instanceof ContainerServlet) {
                ((ContainerServlet) servlet).setWrapper(this);
            }

            classLoadTime=(int) (System.currentTimeMillis() -t1);

            if (servlet instanceof SingleThreadModel) {
                if (instancePool == null) {
                    instancePool = new Stack<>();
                }
                singleThreadModel = true;
            }

            initServlet(servlet);

            fireContainerEvent("load", this);

            loadTime=System.currentTimeMillis() -t1;
        } finally {
            // 此處省略代碼
        }
        return servlet;
    }

創建Servlet實例的方法是從Wrapper.loadServlet開始:

1)loadServlet方法要完成的就是獲取servletClass,然后把它交給InstanceManager去創建一個基於servletClass.class的對象。如果這個Servlet配置了jsp-file,那么這個servletClass就是conf/web.xml中定義的JspServlet了。

2)調用initServlet(servlet),在initServlet(...)方法內部調用Servlet#init(),對servlet進行初始化。

3)觸發Container load事件。

StandardWrapper#initServlet(...)

    private synchronized void initServlet(Servlet servlet)
            throws ServletException {
        if (instanceInitialized && !singleThreadModel) return;

        // Call the initialization method of this servlet
        try {
            if( Globals.IS_SECURITY_ENABLED) {
                boolean success = false;
                try {
                    Object[] args = new Object[] { facade };
                    SecurityUtil.doAsPrivilege("init",
                                               servlet,
                                               classType,
                                               args);
                    success = true;
                } finally {
                    if (!success) {
                        // destroy() will not be called, thus clear the reference now
                        SecurityUtil.remove(servlet);
                    }
                }
            } else {
                servlet.init(facade);
            }

            instanceInitialized = true;
        } catch (UnavailableException f) {
            // 此處省略代碼
        } catch (ServletException f) {
            // 此處省略代碼
        } catch (Throwable f) {
            // 此處省略代碼
        }
    }

初始化Servlet在StandardWrapper的initServlet方法中,調用Servlet的init()方法,同時把包裝了StandardWrapper對象的StandardWrapperFacade作為ServletConfig傳給Servlet。

DispatcherServlet初始化

DispatcherServlet也是一個Servlet:ispatcherServlet extends FrameworkServlet,FrameworkServlet extends HttpServletBean,HttpServletBean extends HttpServlet,HttpServlet extends GenericServlet,GenericServlet extends Servlet。

Servlet的初始化過程可以通過一張圖來總結,如下所示:

因此DispatcherServlet調用init時,實際上執行的是GenericServlet#init(ServletConfig config)

GenericServlet#init(ServletConfig config)

package javax.servlet;
...
public abstract class GenericServlet 
    implements Servlet, ServletConfig, java.io.Serializable
{
    /**
     * Called by the servlet container to indicate to a servlet that the servlet is being placed into service.  See {@link Servlet#init}.
     * <p>This implementation stores the {@link ServletConfig} object it receives from the servlet container for later use.
     * When overriding this form of the method, call <code>super.init(config)</code>.
     * @param config             the <code>ServletConfig</code> object that contains configutation information for this servlet
     * @exception ServletException     if an exception occurs that interrupts the servlet's normal operation
     * @see                 UnavailableException
     */
    public void init(ServletConfig config) throws ServletException {
    this.config = config;
    this.init();
    }
    
    public void init() throws ServletException {

    }
}    

從GenericServlet#init(...)方法的實現上來看,它並未真正執行任何操作,因此我們繼續查看GenericServlet的子類是如何實現servlet初始化操作。

在GernericServlet的子類中HttpServletBase中重寫了GenericServlet#init()方法,接下來我們來查看下是重寫init方法內部是如何實現的。這一應用是采用模板方法設計模式。

HttpServletBase#init()

package org.springframework.web.servlet;
...
@SuppressWarnings("serial")
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
    /**
     * Map config parameters onto bean properties of this servlet, and invoke subclass initialization.
     * @throws ServletException if bean properties are invalid (or required properties are missing), or if subclass initialization fails.
     */
    @Override
    public final void init() throws ServletException {
        // Set bean properties from init parameters.
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            }
            catch (BeansException ex) {
                if (logger.isErrorEnabled()) {
                    logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                }
                throw ex;
            }
        }

        // Let subclasses do whatever initialization they like.
        initServletBean();
    }

    /**
     * Initialize the BeanWrapper for this HttpServletBean, possibly with custom editors.
     * <p>This default implementation is empty.
     * @param bw the BeanWrapper to initialize
     * @throws BeansException if thrown by BeanWrapper methods
     * @see org.springframework.beans.BeanWrapper#registerCustomEditor
     */
    protected void initBeanWrapper(BeanWrapper bw) throws BeansException {
    }

    /**
     * Subclasses may override this to perform custom initialization.
     * All bean properties of this servlet will have been set before this method is invoked.
     * <p>This default implementation is empty.
     * @throws ServletException if subclass initialization fails
     */
    protected void initServletBean() throws ServletException {
    }
}

從HttpServletBase#init()內部實現方法來看:

1)它將init-param等配置參數封裝到BeanWrapper參數內,並調用HttpServletBase#initBeanWrapper(BeanWrapper bw)來進行實現Bean的包裝初始化;

2)調用HttpServletBase#initServletBean()方法進進一步初始化Servlet bean。

但從initBeanWrapper(...)和initServletBean()方法定義不難發現,這里采用了模板方法設計模式,把真正的實現交給了子類去實現,那么我們不得不區分析它的子類如何去試下了。

在子類FrameworkServlet中對initServletBean()方法進行了重寫,接下來我們來查看FrameworkServlet中如何重寫initServletBean()方法。

FrameworkServlet#initServletBean()

package org.springframework.web.servlet;
...
@SuppressWarnings("serial")
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    /**
     * Overridden method of {@link HttpServletBean}, invoked after any bean properties have been set. Creates this servlet's WebApplicationContext.
     */
    @Override
    protected final void initServletBean() throws ServletException {
        getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
        if (logger.isInfoEnabled()) {
            logger.info("Initializing Servlet '" + getServletName() + "'");
        }
        long startTime = System.currentTimeMillis();

        try {
      // 這里是重點,用於初始化子ApplicationContext對象,主要是用來加載<servlet/>對應的servletName-servlet.xml文件如:classpath:spring-security-01-servlet.xml
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }
        catch (ServletException | RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            throw ex;
        }

        if (logger.isDebugEnabled()) {
            String value = this.enableLoggingRequestDetails ?
                    "shown which may lead to unsafe logging of potentially sensitive data" :
                    "masked to prevent unsafe logging of potentially sensitive data";
            logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
                    "': request parameters and headers will be " + value);
        }

        if (logger.isInfoEnabled()) {
            logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
        }
    }    
  
  /**
     * This method will be invoked after any bean properties have been set and
     * the WebApplicationContext has been loaded. The default implementation is empty;
     * subclasses may override this method to perform any initialization they require.
     * @throws ServletException in case of an initialization exception
     */
    protected void initFrameworkServlet() throws ServletException {
    }
}

從上邊代碼可以發現初始化工作核心方法是 initWebApplicationContext()和initFrameworkServlet(),但是initFrameworkServlet()方法是一個空方法,因此猜測核心代碼應該都在initWebApplicationContext()中實現,那么接着查看initWebApplicationContext()方法。

    /**
     * Initialize and publish the WebApplicationContext for this servlet.
     * <p>Delegates to {@link #createWebApplicationContext} for actual creation of the context. Can be overridden in subclasses.
     * @return the WebApplicationContext instance
     * @see #FrameworkServlet(WebApplicationContext)
     * @see #setContextClass
     * @see #setContextConfigLocation
     */
    protected WebApplicationContext initWebApplicationContext() {
    // 1)獲取由ContextLoaderListener創建的根IoC容器
    // 2)獲取根IoC容器有兩種方法,還可通過key直接獲取
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;

        if (this.webApplicationContext != null) {
            // A context instance was injected at construction time -> use it
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                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) { 
            // 如果當前Servelt存在一個WebApplicationContext即子IoC容器,
            // 並且上文獲取的根IoC容器存在,則將根IoC容器作為子IoC容器的父容器
                        // The context instance was injected without an explicit parent -> set
                        // the root application context (if any; may be null) as the parent
                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
      // 如果當前Servlet不存在一個子IoC容器則去查找一下
            // No context instance was injected at construction time -> see if one
            // has been registered in the servlet context. If one exists, it is assumed
            // that the parent context (if any) has already been set and that the
            // user has performed any initialization such as setting the context id
            wac = findWebApplicationContext();
        }
        if (wac == null) {
      // 如果仍舊沒有查找到子IoC容器則創建一個子IoC容器
            // No context instance is defined for this servlet -> create a local one
            wac = createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
      // 調用子類DispatcherServlet覆蓋的onRefresh方法完成“可變”的初始化過程
            // Either the context is not a ConfigurableApplicationContext with refresh
            // support or the context injected at construction time had already been
            // refreshed -> trigger initial onRefresh manually here.
            synchronized (this.onRefreshMonitor) {
                onRefresh(wac);
            }
        }

        if (this.publishContext) {
            // Publish the context as a servlet context attribute.
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
        }

        return wac;
    }
  
    /**
     * Template method which can be overridden to add servlet-specific refresh work.
     * Called after successful context refresh.
     * <p>This implementation is empty.
     * @param context the current WebApplicationContext
     * @see #refresh()
     */
    protected void onRefresh(ApplicationContext context) {
        // For subclasses: do nothing by default.
    }

通過函數名不難發現,該方法的主要作用同樣是創建一個WebApplicationContext對象,即Ioc容器每個Web應用最多只能存在一個根IoC容器,這里創建的則是特定Servlet擁有的子IoC容器。

如果當前Servlet存在一個IoC容器則為其設置根IoC容器作為其父類,並配置刷新該容器,用於構造其定義的Bean,這里的方法與前文講述的根IoC容器類似,同樣會讀取用戶在web.xml中配置的<servlet>中的<init-param>值,用於查找相關的xml配置文件用於構造定義的Bean,這里不再贅述了。如果當前Servlet不存在一個子IoC容器就去查找一個,如果仍然沒有查找到則調用 createWebApplicationContext()方法去創建一個,查看該方法的源碼如下圖所示:

    protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
        Class<?> contextClass = getContextClass();
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException(
                    "Fatal initialization error in servlet with name '" + getServletName() +
                    "': custom WebApplicationContext class [" + contextClass.getName() +
                    "] is not of type ConfigurableWebApplicationContext");
        }
        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

        wac.setEnvironment(getEnvironment());
        wac.setParent(parent);
        String configLocation = getContextConfigLocation();
        if (configLocation != null) {
            wac.setConfigLocation(configLocation);
        }
        configureAndRefreshWebApplicationContext(wac);

        return wac;
    }

該方法用於創建一個子IoC容器並將根IoC容器做為其父容器,接着進行配置和刷新操作用於構造相關的Bean。至此,根IoC容器以及相關Servlet的子IoC容器已經配置完成,子容器中管理的Bean一般只被該Servlet使用,因此,其中管理的Bean一般是“局部”的,如SpringMVC中需要的各種重要組件,包括Controller、Interceptor、Converter、ExceptionResolver等。相關關系如下圖所示:

上述調用邏輯中比較重要的就是FrameworkServlet抽象類中的initServletBean()方法、initWebApplicationContext()方法以及DispatcherServlet類中的onRefresh()方法。

DispatcherServlet#onRefresh()

當IoC子容器構造完成后調用了onRefresh()方法,該方法的調用與initServletBean()方法的調用相同,由父類調用但具體實現由子類覆蓋,調用onRefresh()方法時將前文創建的IoC子容器作為參數傳入,查看DispatcherServletBean類的onRefresh()方法源碼如下:

package org.springframework.web.servlet;
...
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
  ...
    /**
     * This implementation calls {@link #initStrategies}.
     */
    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

    /**
     * Initialize the strategy objects that this servlet uses.
     * <p>May be overridden in subclasses in order to initialize further strategy objects.
     */
    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }
    ...
}

onRefresh()方法直接調用了initStrategies()方法,源碼如上,通過函數名可以判斷,該方法用於初始化創建multipartResovle來支持圖片等文件的上傳、本地化解析器、主題解析器、HandlerMapping處理器映射器、HandlerAdapter處理器適配器、異常解析器、視圖解析器、flashMap管理器等,這些組件都是SpringMVC開發中的重要組件,相關組件的初始化創建過程均在此完成。

父子容器

在學習Spring時,我們都是從讀取xml配置文件來構造IoC容器,常用的類有ClassPathXmlApplicationContext類,該類存在一個初始化方法用於傳入xml文件路徑以及一個父容器,我們可以創建兩個不同的xml配置文件並實現如下代碼:

//applicationContext1.xml文件中配置一個id為baseBean的Bean
//applicationContext2.xml文件中配置一個id未subBean的Bean
public void testParentChhildrenIOC(){
  ApplicationContext baseContext = new ClassPathXmlApplicationContext("applicationContext1.xml");

  Object obj1 = baseContext.getBean("baseBean");
  System.out.println("baseContext Get Bean " + obj1);

  ApplicationContext subContext = new ClassPathXmlApplicationContext(new String[]{"applicationContext2.xml"}, baseContext);

  Object obj2 = subContext.getBean("baseBean");
  System.out.println("subContext get baseContext Bean " + obj2);

  Object obj3 = subContext.getBean("subBean");
  System.out.println("subContext get subContext Bean " + obj3);

  //拋出NoSuchBeanDefinitionException異常
  Object obj4 = baseContext.getBean("subBean");
  System.out.println("baseContext get subContext Bean " + obj4);
}

測試結果:

1)首先創建baseContext沒有為其設置父容器,接着可以成功獲取id為baseBean的Bean;

2)接着創建subContext並將baseContext設置為其父容器,subContext可以成功獲取baseBean以及subBean

3)最后試圖使用baseContext去獲取subContext中定義的subBean,此時會拋出異常NoSuchBeanDefinitionException。

由此可見,父子容器類似於類的繼承關系,子類可以訪問父類中的成員變量,而父類不可訪問子類的成員變量,同樣的,子容器可以訪問父容器中定義的Bean,但父容器無法訪問子容器定義的Bean。

 

參考

SpringMVC初始化流程

Spring 4.x源碼分析-BeanWrapper

第三章 DispatcherServlet詳解 ——跟開濤學SpringMVC

SpringMvc之DispatcherServlet詳解

Spring MVC入口Servlet詳解(HttpServletBean,FrameworkServlet,DispatcherServlet )

Spring容器 SpringMVC容器 web容器的關系

Tomcat源碼分析 (九)----- HTTP請求處理過程(二)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM