SpringMVC源碼 3.1 DispatchServlet初始化

SpringMVC源碼 3.1 DispatchServlet初始化


上文總結:
前面兩篇筆記主要寫了ContextLoaderListener在SpringMVC中啓動的流程,已經通過ContextLoader完成了對WebApplicationContext(Spring容器)的創建。
還有Spring 在 web.xml中需要的配置相關的東西。
在Spring中,ContextLoaderListener只是一個輔助功能,用於創建WebApplicationContext。而真正的邏輯實現其實是在DispatchServlet中,DispatchServlet是Servlet接口的實現類。

關於Servlet的東西可以參考關於 Servlet XXX的筆記

節選Servlet的總結:
Servlet是運行在java 應用服務器上的程序,不能獨立運行,其操作完全由Servlet容器控制。
負責對http請求做相應。根據在web.xml對<servlet>的配置,一個servlet可以映射到一個或者多個請求中。

Servlet的基本信息保存在各自的ServletConfig中,在初始化時由servlet容器通過init()方法傳給servlet。

servlet主要負責三件事:
1.讀取Servlet容器(tomcat)轉發來的request信息。
2.在內部進行邏輯操作。
3.對操作結果寫入response中,返回給Servlet容器(tomcat)

servlet的聲明週期:
1.實例化(Servlet容器根據在web.xml配置的serlvet進行實例化,並將對應的信息保存在ServletConfig中)
2.初始化(Servlet容器調用init()方法,對其進行初始化)
3.提供服務(Servlet容器接收到請求時,根據映射指到對應的servlet程序,調用service()方法,進行服務)
4.銷燬(Servlet容器關閉或者重啓時調用destory()方法進行銷燬)
5.垃圾回收。
注意:init()、service()、destory()這些都是由Servlet容器進行調用的

Servlet容器爲了減少生產Servlet實例的開銷,對Servlet採用單例多線程的機制:在servlet的生命週期中,只存在一個實例。Servlet容器的調度線程,從線程池獲取一個線程去執行service()方法。在開發過程需要注意線程安全。
對於Tomcat可以在server.xml中通過<Connector>元素設置線程池中線程的數目。

1.DispatchServlet的初始化
通過上面對Servlet的描述中可以瞭解到,在servlet初始的時候,Servlet容器會調用init方法,所以首先我們需要找到DispatchServlet的init()方法。在父類HttpServletBean中找到了init()方法。
DispatchServlet繼承關係:DispatchServlet<-FrameworkServlet<-HttpServletBean<-HttpServlet。前面三個都是spring中的類,HttpServlet是javax Servlet API中的抽象類。

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

     // Set bean properties from init parameters.
     try {
           //解析init-param中的參數到PropertyValues對象中
           PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
           //將當前的Servlet(DispatchServlet)類轉化爲BeanWrapper,從而能夠以Spring的方式對init-param的值進行注入
           BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
           ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
           //註冊自定義的屬性編輯器,一旦遇到Resource類型的屬性會使用ResourceEditor記性解析
           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;
     }

     // Let subclasses do whatever initialization they like.
     // FramworkServlet進行了實現,加載DispatchServlet的spring-servletxml文件,初始化SpringMVC IoC容器。
    initServletBean();

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

DispatchServlet的參數初始化過程:
1.獲取當前Servlet的ServletConfig,將其封裝成PropertyValues。也就是web.xml中的<servlet>的<init-param>下的參數。主要的參數是contextConfigLocation,用於確定SpringMVC Ioc的配置文件路徑,(<context-param>中的contextConfigLocation中的參數是用於確定Spring Ioc容器的配置文件)。
2.將當前Servlet(也就是DispatchServlet)實例,轉化爲BeanWrapper類型實例,方便使用Spring提供的注入功能進行對應屬性的注入。
3.註冊相對於Resource的屬性編輯器。
4.通過BeanWrapper的setPropertyVaules方法,進行屬性注入,通過BeanWrapper可以將PropertyValues中的參數,以Setter注入到DispatchServlet的字段中。其實我們最常用的屬性注入無非是contextConfigLocation,contextAttribute,contextClass,nameSpace等屬性。
上面一大堆,說白了就是把ServletConfig中的參數,對應到DispatchServlet的定義的字段上。下面這些屬性都可以配置在web.xml中
/** ServletContext attribute to find the WebApplicationContext in */
private String contextAttribute; 用於通過這個屬性 去獲取servletContext中獲取對應key的WebApplicationContext。在initWebApplicationCOntext()方法中使用
/** WebApplicationContext implementation class to create */
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS; 通過這個屬性去創建對應類型WebApplicationContext,在createWebApplicationContext()方法使用,默認爲XMLWebApplicationContext.class。
/** WebApplicationContext id to assign */
private String contextId;          在configureAndRefreshWebApplicationContext()使用,確定contextId
/** Namespace for this servlet */
private String namespace;
/** Explicit context config location */
private String contextConfigLocation; 配置文件路徑,在createWebApplicationContext()方法中會將其set到WebApplicationContext中,然後初始化spring環境的時候會加載配置文件路徑。如果不存在會報錯
完成了參數的初始,開始對servletBean進行初始化。
如果沒有對應的參數,例如contextConfigLocation,在初始化參數的時候不會報錯,但是會在創建WebApplicationContext的時候拋出異常。

FrameWorkServlet.initServletBean()
在ContextLoader的時候已經完成了一個WebApplicationContext的實例,創建了SpringIoC的容器。而這個函數中是對容器的補充,創建一個SpringMVC IoC容器。可以看出FrameworkServlet中對這個方法做了重寫。函數中增加了時間戳來計算初始化的執行時間。
關鍵的初始化內容通過initWebApplicationContext()實現。並且添加了一個initFrameworkServlet()方法用於擴展,這個暫時是個空的方法。
@Override
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");
     }
}


2.WebApplicationContext的初始化

FramworkServlet.initWebApplicationContext()
protected WebApplicationContext initWebApplicationContext() {
     //從ServletContext取出父Context。在ContextLoader創建WebApplicationContext的時候,
     //在最後通過servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);存入ServletContext中。
     //這裏通過sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)
     WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
     WebApplicationContext wac = null;

     if (this.webApplicationContext != null) {
           // A context instance was injected at construction time -> use it 
           //如果context實例在構造函數中被注入
           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) {
                           // 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) {
           // 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
           // 通過contextAttribute屬性從ServletContext中加載WebApplicationContext
           wac = findWebApplicationContext();
     }
     if (wac == null) {
           // No context instance is defined for this servlet -> create a local one
           // 新建一個WebApplicationContext
           wac = createWebApplicationContext(rootContext);
     }

     if (!this.refreshEventReceived) {
           // 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.
           onRefresh(wac);
     }

     if (this.publishContext) {
           // Publish the context as a servlet context attribute.
           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;
}

尋找或創建對應的WebApplicationContext實例
WebApplicationContext實例的尋找及創建包括以下幾個步驟:
(1):通過構造函數的注入進行初始化
(2):通過contextAttribute進行初始化,
通過在web.xml文件中配置Servlet參數“contextAttribute”來查找ServletContext中對應的屬性。默認爲null,即不從ServletContext中去取。可以設置爲WebApplicationContext.class.getName()+".ROOT"。也就是在ContextLoader中創建的,並以WebApplicationContext.class.getName()+".ROOT"爲key存入ServletContext中的。
(3):重新創建WebApplicationContext實例
FramworkServlet.createWebApplicationContext ()
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
     return createWebApplicationContext((ApplicationContext) parent);
}

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
     //獲取servlet的初始化參數“contextClass”,如果在web.xml沒有配置,則默認爲XmlWebApplicationContext.class
     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");
     }
     //通過反射方式實例化 contextClass
     ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

     wac.setEnvironment(getEnvironment());
     //parent是ContextLoader中創建的實例
     wac.setParent(parent);
     //獲取contextConfigLocation屬性,配置在servlet初始化參數中
     wac.setConfigLocation(getContextConfigLocation());

     //初始化spring環境包括加載配置文件。
     configureAndRefreshWebApplicationContext(wac);

     return wac;
}

3.configureAndRefreshWebApplicationContext

無論是通過構造函數注入還是重新創建,都會需要調用configureAndRefreshWebApplicationContext方法,對已經創建的WebApplicationContext實例進行配置和刷新。

FramworkServlet.configureAndRefreshWebApplicationContext ()
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
     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
           if (this.contextId != null) {
                wac.setId(this.contextId);
           }
           else {
                // Generate default id...
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                          ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
           }
     }

     wac.setServletContext(getServletContext());
     wac.setServletConfig(getServletConfig());
     wac.setNamespace(getNamespace());
     wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

     // 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(getServletContext(), getServletConfig());
     }

     postProcessWebApplicationContext(wac);
     applyInitializers(wac);
     wac.refresh();
}
只要是Spring的ApplicationContext的使用,到最後都免不了使用公共父類AbstractApplicationContext提供的refresh()方法進行配置文件的加載


DispatchServlet中初始化已經差不多了,還剩下最後的刷新,onRefresh方法。在這個方法中主要初始了一些DispatchServlet中需要使用的一些組件。
例如各種Resolver、HandlerMapping、HandlerAdapter、HandlerExceptionResolvers等。

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