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等。