FrameworkServlet是Spring web框架的基本servlet實現類,通過JavaBean的方式集成了Application context,所有新實現的servlet最好都繼承於該類。該類提供了HttpServlet的所有接口實現,自帶了一個web容器,它實現了WebApplicationContextAware接口,所以能根據指定的容器配置文件,來初始化自己管理的容器。
FrameworkServlet提供兩個功能:
- 爲每個servlet管理一個WebApplicationContext實例(即每個servlet會自己的一個web容器),每個servlet都有自己的配置空間,相互獨立。(每一個<servlet> tag之間的配置屬於一個namespace, 配置一個application context。)
- 對每一個請求,無論是否成功處理,都會在每個請求上發佈事件。(這裏是不是可以做一些事情?)
FrameworkServlet初始化流程
FrameworkServlet初始化的過程其實就是WebApplication 配置文件加載(即容器初始化)的過程。
入口方法是initServletBean(),這個方法比較簡單,就是打印一些初始化必要信息,然後調用initWebApplicationContext()方法來初始化配置。
實現代碼如下:
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 {
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");
}
}
initWebApplicationContext()方法在初始化applicationContext時,會分幾步來執行:
- 獲取root applicationContext, 一般是web.xml文件對應的初始化配置。
- 如果當前servlet已經初始化了一個applicationContext, 那麼設置parent applicationContext爲第一步設置的context, 然後讀取配置文件。
- 如果applicationContext爲空,那麼根據配置文件中設置的contextAttribute值從ServletContext中查找;
- 如果沒有設置contextAttribute,那麼就調用createWebApplicationContext()方法創建一個新的applicationContext,並將root applicationContext作爲父容器。
實現代碼如下:
protected WebApplicationContext initWebApplicationContext() {
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) {
// 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
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
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.
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;
}
createWebApplicationContext()方法是最核心的代碼,創建XmlWebApplicationContext實例,並從xml配置文件中加載配置。
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");
}
// 真正初始化實現類,這個類就是通過contextClass來指定的。
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;
}
我們來看看核心的代碼,它會設置各種屬性參數,最後會調用refresh()方法,讀取配置。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
// 設置contextId屬性
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());
}
}
// 設置當前webApplicationContext所屬於的ServletContext
wac.setServletContext(getServletContext());
// 設置ServletConfig
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());
}
// 這是一個hook, 子類可以實現該方法
postProcessWebApplicationContext(wac);
applyInitializers(wac);
// 最終配置加載的地方
wac.refresh();
}
爲什麼FrameworkServlet的初始化入口是initServletBean()方法?
這就需要從FrameworkServlet的繼承關係來講解了。首先,必須明確的是,FrameworkServlet也是一個普普通通的servlet, 所以它自身也會有servlet定義的方法的實現:
public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
public String getServletInfo();
public void destroy();
下圖是類繼承關係鏈:
根據上圖繼承關係,我們可以理解初始化順序實際上是這樣的:
- web容器在實例化servlet時會調用Servlet的init(ServletConfig)方法,由於其子類GenericServlet實現了該方法,因而最終會調用GenericServlet.init(ServletConfig)方法。
- GenericServlet.init(ServletConfig)方法內部會調用其init()方法,由於該方法又是由其子類HttpServletBean實現,實際上是執行了HttpServletBean.init()方法。
- HttpServletBean.init()方法內部會調用其自定義的initServletBean()方法,但該方法實際上是由FrameworkServlet實現,實際上是執行了FrameworkServlet.initServletBean()方法。
- FrameworkServlet.initServletBean()方法內部真正初始化了applicationContext,並加載配置文件,最終調用其自定義的initFrameworkServlet()方法。
至此,當容器初始化該servlet時,通過調用init()方法,最終達到了初始化該servlet的容器的目的。
其實Servlet定義的接口很簡單,子類通過不斷繼承,利用Java的繼承和多態特性,完成了很多自定義的邏輯實現。簡單的接口,完成了重要的任務。真是佩服至極!!!
如何使用FrameworkServlet類
從上面的代碼分析,我們可以看到,其實只需要實現FrameworkServlet中的幾個方法即可:
- initFrameworkServlet()方法
- doService()方法
- postProcessWebApplicationContext()方法
其中,子類必須實現FrameworkServlet.doService()方法,該方法用來處理所有的請求。另外兩個方法可以不必實現。具體實現可以參考DispatcherSerlvet的實現。
Servlet init-param有哪些?分別是什麼作用?
- contextConfigLocation
- contextClass
- contextInitializerClasses
- contextAttribute
- namespace
- publishContext
其實還有一些其他屬性,採用默認值就行,在此就j不一一羅列了。
contextConfigLocation屬性用來告訴FrameworkServlet容器配置的完整路徑,該路徑是基於WEB-INF/目錄。但是如果不指定該屬性,默認的路徑是什麼呢?
- 首先看當前servlet有沒有定義namespace屬性,如果有則用namespace.xml作爲文件名。
- 如果沒有,則用<servlet-name>-servlet作爲namespace,比如namespace是spring-mvc-test-servlet。
- 那麼完整的配置文件名就是spring-mvc-test-servlet.xml.完整路徑就是/WEB-INF/spring-mvc-test-servlet.xml.
contextConfigLocation支持同時指定多個配置文件,文件之間用逗號或空格隔開。如果多個文件中定義有相同的bean, 後面的會覆蓋前面的定義。如果想覆蓋前面文件定義的bean, 這是一個方法。
contextClass用來制定用哪個實現類來加載這個容器資源,這些容器管理類的基類是ConfigurableWebApplicationContext, 子類必須繼承實現它。通常使用比較多的是XmlWebApplicationContext,這也是spring mvc默認的實現類。
contextInitializerClasses用來指定多個ApplicationContextInitializer類,初始化這個容器,對其中的屬性會做一些加工。
namespace就是當前servlet的命名空間,用來標明容器的配置文件名稱和配置屬性所在的空間範圍。
contextAttribute用來標明當前的ApplicationContext在ServletContext中的名稱,因爲ServletContext中維護的對象實際上存在了Map<String, Object>的對象裏,所以必須知道對應的是哪個key, 這樣就可以通過servletContext.get()或servletContext.set()獲取會設置該容器了。該值是SERVLET_CONTEXT_PREFIX + servlet-name兩部分拼接而成,完整名是:org.springframework.web.servlet.FrameworkServlet.CONTEXT.servlet-name。
publishContext用來在初始化時告訴FrameworkServlet是否將applicationContext放到ServletContext裏,以便後續可以在其他地方訪問。
看源碼我們的印象會更深一些。
/**
* Suffix for WebApplicationContext namespaces. If a servlet of this class is
* given the name "test" in a context, the namespace used by the servlet will
* resolve to "test-servlet".
*/
public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet"; // 如果沒有顯式地指定namespace, 那麼namespace是以-sevlet做後綴。
/**
* Default context class for FrameworkServlet.
* @see org.springframework.web.context.support.XmlWebApplicationContext
*/
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class; // 缺省的容器配置類
/**
* Prefix for the ServletContext attribute for the WebApplicationContext.
* The completion is the servlet name.
*/
public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT."; // WebpplicationContext在ServletContext中的屬性名。
/**
* Any number of these characters are considered delimiters between
* multiple values in a single init-param String value.
*/
private static final String INIT_PARAM_DELIMITERS = ",; \t\n"; // 多個值的分隔符
/** ServletContext attribute to find the WebApplicationContext in. */
@Nullable
private String contextAttribute;
/** WebApplicationContext implementation class to create. */
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS; // 缺省的WebApplicationContext實現類