【Spring】DispatcherServlet的啟動和初始化


使用過SpringMVC的都知道DispatcherServlet,下面介紹下該Servlet的啟動與初始化。作為Servlet,DispatcherServlet的啟動與Serlvet的啟動過程是相聯系的。在Serlvet的初始化過程程中,Serlvet的init方法會被調用,以進行初始化。DispatcherServlet的基類HttpServletBean中的這個初始化過程源碼如下:

public final void init() throws ServletException {
    if (logger.isDebugEnabled()) {
        logger.debug("Initializing servlet '" + getServletName() + "'");
    }

    // 獲取Servlet的初始化參數,對Bean屬性進行配置
    try {
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        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) {
        logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
        throw ex;
    }

    // 調用子類的initServletBean方法進行具體的初始化工作
    initServletBean();

    if (logger.isDebugEnabled()) {
        logger.debug("Servlet '" + getServletName() + "' configured successfully");
    }
}

// initServletBean這個初始化工作位於FrameworkServlet類中
protected final void initServletBean() throws ServletException {
    getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
    if (this.logger.isInfoEnabled()) {
        this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
    }
    long startTime = System.currentTimeMillis();

    // 這里初始化上下文
    try {
        this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();
    }
    catch (ServletException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }
    catch (RuntimeException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }

    if (this.logger.isInfoEnabled()) {
        long elapsedTime = System.currentTimeMillis() - startTime;
        this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
                elapsedTime + " ms");
    }
}

protected WebApplicationContext initWebApplicationContext() {
    // 調用WebApplicationContextUtils來得到根上下文,它保存在ServletContext中
    // 使用它作為當前MVC上下文的雙親上下文
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) {
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        onRefresh(wac);
    }

    // 把當前建立的上下文存到ServletContext中,使用的屬性名是跟當前Servlet名相關的
    if (this.publishContext) {
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                    "' as ServletContext attribute with name [" + attrName + "]");
        }
    }

    return wac;
}

在初始化開始時,需要讀取配置在ServletContext中的Bean屬性參數,這些屬性參數設置在web.xml的Web容器初始化參數中。

接着會執行DispatcherServlet持有的IOC容器的初始化過程,在這個過程中,一個新的上下文會被建立起來,這個DispatcherServlet持有的上下文被設置為根上下文的子上下文。可以這么理解,根上下文是和web應用相對應的一個上下文,而DispatcherServlet持有的上下文是和Servlet對應的一個上下文。在一個web應用中可以容納多個Servlet存在;對應的,對於應用在web容器中的上下文體系,一個根上下文可以作為許多Serlvet上下文的雙親上下文。對這點的理解有助於在web環境中IOC容器的Bean設置和檢索有所幫助,因為在向IOC容器getBean時,IOC容器會先向其雙親上下文去getBean,換句話說就是在根上下文中定義的Bean是可以被各個Servlet持有的上下文得到和共享的。DispatcherServlet持有的上下文被建立后,也需要和其他 IOC容器一樣完成初始化操作,這個初始化操作就是通過refresh方法完成的,最后DispatcherServlet再給自己持有的上下文命名並設置到web窗口的上下文中(即ServletContext),這個名稱和在web.xml中設置的DispatcherServlet的Servlet名稱有關,進而保證這個上下文在web環境上下文體系中的唯一性。

上面代碼執行后,這個Servlet的上下文就建立起來了,具體取得根上下文的過程是在WebApplicationContextUtils中實現的,源碼如下:

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
    // ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE這個屬性代表的根上下文
    // 在ContextLoaderListener初始化的過程中被建立,並設置到ServletContext中
    return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}

這個根上下文是ContextLoader設置到ServletContext中的,使用屬性ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,同時對這個IOC容器的Bean配置文件,ContextLoader也進行了設置,默認的位置是在/WEB-INF/applicationContext.xml文件中,由於這個根上下文是DispatcherServlet建立的上下文的雙親上下文,所以根上下文中管理的bean是可以被DispatcherServlet的上下文使用的,反過來則不行,通過getBean向IOC容器獲取bean時,會先到其雙親IOC容器嘗試獲取。

回到FrameworkServlet繼續看DispatcherServlet的上下文是怎樣建立的,源碼如下:

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
    Class<?> contextClass = getContextClass();
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Servlet with name '" + getServletName() +
                "' will try to create custom WebApplicationContext context of class '" +
                contextClass.getName() + "'" + ", using parent context [" + parent + "]");
    }
    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");
    }
    
    // 實例化需要的具體上下文對象,並為這個上下文對象設置屬性
    // 這里使用的是DEFAULT_CONTEXT_CLASS,這個DEFAULT_CONTEXT_CLASS被設置為XmlWebApplicationContext.class,
    // 所以在DispatcherServlet中使用的IOC容器是XmlWebApplicationContext
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    // 設置雙親上下文(也就是根上下文)
    wac.setParent(parent);
    wac.setConfigLocation(getContextConfigLocation());

    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

建立DispatcherServlet的上下文,需要把根上下文作為參數傳遞給它,再使用反射實例化上下文對象,並為它設置參數,按默認配置的話這個上下文對象就是XmlWebApplicationContext對象,這個類型是在DEFAULT_CONTEXT_CLASS參數中設置好並允許BeanUtils使用的,實例化結束之后,還需要為這個上下文對象設置好一些基本的配置,這些配置包括它的雙親上下文、Bean定義配置的文件位置等,完成配置后就通過調用IOC容器的refresh方法來完成IOC容器的最終初始化。

通過上面web容器一系列的操作后,在這個上下文體系建立和初始化完畢的基礎上,Spring MVC就可以發揮作用了。此時DispatcherServlet就持有一個以自己的Servlet名稱命名的IOC容器,這個IOC容器建立后,意味着DispatcherServlet擁有自己的Bean定義空間,這為使用各個獨立的XML文件來配置MVC中各個Bean創建了條件,初始化完成后,Spring MVC的具體實現和普通的Spring應用程序的實現並沒有太多差別,在DispatcherServlet的初始化過程中,以對HandlerMapping的初始化調用作為觸發點,可以看下圖Spring MVC模塊初始化的方法調用關系圖,

這個調用關系最初由HttpServletBean的init方法觸發,這個HttpServletBean是HttpServlet的子類,接着會在HttpServletBean的子類FrameworkServlet中對IOC容器完成初始化,在這個初始化方法中會調用DispatcherServlet的initStrategies方法,在這個initStrategies方法中,啟動整個Spring MVC框架的初始化工作。

從上面的方法調用關系圖也可以看出對MVC的初始化是在DispatcherServlet的initStrategies中完成的,包括對各種MVC框架的實現元素,比如國際化支持LocalResolver、視圖生成的ViewResolver等的初始化工作,源碼如下:

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

上面各個初始化方法的名稱應該比較好理解,這里以常見的HandlerMapping為例來說明initHandlerMappings()實現,Mapping映射的作用就是為HTTP請求找到相應的Controller控制器,HandlerMappings完成對MVC中Controller的定義和配置,DispatcherServlet中HandlerMappings初始化過程源碼如下:

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;

    // 這里導入所有的HandlerMapping Bean,這些Bean可以在當前的DispatcherServlet的IOC
    // 容器中,也可能在其雙親上下文中,這個detectAllHandlerMappings的默認值設為true,
    // 即默認地從所有的IOC容器中獲取
    if (this.detectAllHandlerMappings) {
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
            OrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        try {
            // 可以根據名稱從當前的IOC容器中通過getBean獲取HandlerMapping
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
        }
    }

    // 如果沒找到HandlerMappings,那么需要為Servlet設置默認的HandlerMappings,
    // 這些默認的值可以設置在DispatcherServlet.properties中
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
        }
    }
}

在HandlerMapping初始化的過程中,把在Bean配置文件中配置好的handlerMapping從IOC容器中取得。經過上面的讀取過程,handlerMappings變量就已經獲取了在BeanDefinition中配置好的映射關系。其他的初始化過程和handlerMappings比較類似,都是從IOC容器中讀入配置,所以說MVC初始化過程是建立在IOC容器已經初始化完成的基礎之上的。執行完其他的各個初始化操作后,整個初始化過程就基本完成了。

 


免責聲明!

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



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