描述
對于一個
WEB
應用來說,它需要部署在WEB容器
中,且WEB容器
會提供一個全局的上下文環境ServletContext
,也就是SpringIOC容器
的宿主環境。WEB容器
啟動時,會加載web.xml
中提供的contextLoaderListener
監聽器,會觸發容器初始化事件,contextLoaderListener
會監聽到該事件,其contextInitialized
方法會被調用。在該方法中,Spring
會初始化一個啟動上下文
,也被稱為根上下文
,即WebApplicationContext
,確切說,其實際實現類是XmlWebApplicaitonContext
。其實它就是Spring IOC 容器
,其對應的Bean
定義配置,由web.xml
中context-param
標簽指定。在Spring IOC容器
初始化完畢后,Spring
以WebApplicationContext.ROOTWEBAPPLICATIONEXTATTRIBUTE
為屬性key,將其存儲到ServletContext
中servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
。
3.contextLoaderListener
監聽器初始化完畢后,開始初始化web.xml
中配置的Servlet
,且Servlet
可以配置多個,以最常見的DispatcherServlet
為例,這個Servlet
實際上是一個標準的前端控制器
,用以 轉發
匹配
處理
每個servlet 請求。
4.DispatcherServlet
上下文在初始化的時候會建立自己的 IOC容器上下文
,用以持有Spring MVC
相關的Bean
。在創建上下文時,會根據WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE
該Key
,從ServletContext
中獲取之前的根上下文
(WebApplicationContext
),來作為自己上下文的 Parent 上下文
。有了該Parent 上下文
后,再初始化自己持有的上下文。通過initStrategies
方法中可以看到,DispatcherServlet
初始化自己上下文
大概的工作,就是初始化處理器映射
視圖解析等等
,默認實現類也是 XmlWebApplicationContext
。
5.DispatcherServlet
上下文在初始化完成后,Spring
會將與Servlet
的名字相關的屬性,作為key,將其存到ServletContext
中。這樣每個 Servlet
都持有自己的上下文,即擁有自己獨立的 Bean
空間,同時各個Servlet
共享相同的Bean
,即根上下文定義的Bean
。
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register 控制臺信息打印 [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] :Mapped "{[/appEdition/list.do]}" onto public org.springframework.web.servlet.ModelAndView com.hengtn.jiang.controller.AppEditionController.list(com.hengtn.jiang.entity.query.AppEditionQuery,javax.servlet.http.HttpSession,java.lang.Integer)
Spring容器在Web容器中的創建及初始化
SpringIOC
是一個獨立的模塊,并不是直接在Web容器
中發揮作用的,假若要在Web
環境使用IOC容器
的話,需要Spring
為IOC
設計一個啟動過程,將IOC容器
導入,并在Web容器
中建立起來。
web.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher>
</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>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>180</session-timeout>
</session-config>
</web-app>
contextConfigLocation
對應的value
是Spring配置文件
的絕對路徑
ContextLoaderListener
監聽器主要用來對Servlet容器
(這里指Tomcat
)的行為進行監聽
ContextLoaderListener
源碼
/**
* Bootstrap listener to start up and shut down Spring's root {@link WebApplicationContext}.
* Simply delegates to {@link ContextLoader} as well as to {@link ContextCleanupListener}.
*
* <p>As of Spring 3.1, {@code ContextLoaderListener} supports injecting the root web
* application context via the {@link #ContextLoaderListener(WebApplicationContext)}
* constructor, allowing for programmatic configuration in Servlet 3.0+ environments.
* See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
*
* @author Juergen Hoeller
* @author Chris Beams
* @since 17.02.2003
* @see #setContextInitializers
* @see org.springframework.web.WebApplicationInitializer
*/
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
/**
* Create a new {@code ContextLoaderListener} that will create a web application
* context based on the "contextClass" and "contextConfigLocation" servlet
* context-params. See {@link ContextLoader} superclass documentation for details on
* default values for each.
* <p>This constructor is typically used when declaring {@code ContextLoaderListener}
* as a {@code <listener>} within {@code web.xml}, where a no-arg constructor is
* required.
* <p>The created application context will be registered into the ServletContext under
* the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}
* and the Spring application context will be closed when the {@link #contextDestroyed}
* lifecycle method is invoked on this listener.
* @see ContextLoader
* @see #ContextLoaderListener(WebApplicationContext)
* @see #contextInitialized(ServletContextEvent)
* @see #contextDestroyed(ServletContextEvent)
*/
public ContextLoaderListener() {
}
/**
* Create a new {@code ContextLoaderListener} with the given application context. This
* constructor is useful in Servlet 3.0+ environments where instance-based
* registration of listeners is possible through the {@link javax.servlet.ServletContext#addListener}
* API.
* <p>The context may or may not yet be {@linkplain
* org.springframework.context.ConfigurableApplicationContext#refresh() refreshed}. If it
* (a) is an implementation of {@link ConfigurableWebApplicationContext} and
* (b) has <strong>not</strong> already been refreshed (the recommended approach),
* then the following will occur:
* <ul>
* <li>If the given context has not already been assigned an {@linkplain
* org.springframework.context.ConfigurableApplicationContext#setId id}, one will be assigned to it</li>
* <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
* the application context</li>
* <li>{@link #customizeContext} will be called</li>
* <li>Any {@link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer}s
* specified through the "contextInitializerClasses" init-param will be applied.</li>
* <li>{@link org.springframework.context.ConfigurableApplicationContext#refresh refresh()} will be called</li>
* </ul>
* If the context has already been refreshed or does not implement
* {@code ConfigurableWebApplicationContext}, none of the above will occur under the
* assumption that the user has performed these actions (or not) per his or her
* specific needs.
* <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
* <p>In any case, the given application context will be registered into the
* ServletContext under the attribute name {@link
* WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and the Spring
* application context will be closed when the {@link #contextDestroyed} lifecycle
* method is invoked on this listener.
* @param context the application context to manage
* @see #contextInitialized(ServletContextEvent)
* @see #contextDestroyed(ServletContextEvent)
*/
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
/**
* Close the root web application context.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
由源碼得出,
ContextLoaderListener
繼承自ContextLoader
,并且還實現了ServletContextListener
,而且它的構造函數中,需要傳入了一個WebApplicationContext
,它是繼承自ApplicationContext
接口的高級IOC容器
ServletContextListener 源碼
public interface ServletContextListener extends EventListener {
/**
** Notification that the web application initialization process is starting.
* All ServletContextListeners are notified of context initialization before
* any filter or servlet in the web application is initialized.
* @param sce Information about the ServletContext that was initialized
*/
public void contextInitialized(ServletContextEvent sce);
/**
** Notification that the servlet context is about to be shut down. All
* servlets and filters have been destroy()ed before any
* ServletContextListeners are notified of context destruction.
* @param sce Information about the ServletContext that was destroyed
*/
public void contextDestroyed(ServletContextEvent sce);
}
ServletContextListener
是 Servlet
中比較重要的一個接口,用于監聽 Servlet
容器的啟動
銷毀
事件,所以在 ContextLoaderListener 中:
contextInitialized(ServletContextEvent sce)
:參數為所要監聽的ServletContextEvent
,也就是Tomcat
啟動加載完web.xml
會產生的事件,ServletContextEvent
持有從web.xml
加載的初始化配置ServletContext 上下文
contextDestroyed(ServletContextEvent sce)
:在Tomcat
關閉的時候執行該方法
啟動時,
ServletContextListener
的執行順序與web.xml
中的配置順序一致,停止時執行順序正相反
流程梳理
當
Servlet容器
啟動事件發生時,將被ContextLoaderLister
監聽,此時ContextLoaderListener
會調用contextInitialized(ServletContextEvent sce)
方法,該方法為ContextLoaderLister
實現ServletContextListener
接口的方法,并將在web.xml
加載初始化完成后,獲取的ServletContext
傳入initWebApplicationContext()
方法中,進行IoC容器的初始化
initWebApplicationContext()
方法從 ContextLoader
繼承而來,進入ContextLoader
源碼中查看
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
//private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
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
類中的靜態代碼塊
創建
ClassPathResource
對象,同時把ContextLoader.properties
作為參數傳入,易知ContextLoader.properties
文件與ContextLoader
類是在同一個目錄下,ContextLoader.properties
文件內容如下:org.springframework.web.context.WebApplicationContext=org.springframework. web.context.support.XmlWebApplicationContex
得到一個 Properties 對象,后面將根據類名來創建對應的 ApplicationContext 容器
因此可知Spring
默認初始化容器,是XmlWebApplicationContext
容器
initiWebApplicationContext()
方法
/**
* Initialize Spring's web application context for the given servlet context,
* using the application context provided at construction time, or creating a new one
* according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
* "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
* @param servletContext current servlet context
* @return the new WebApplicationContext
* @see #ContextLoader(WebApplicationContext)
* @see #CONTEXT_CLASS_PARAM
* @see #CONFIG_LOCATION_PARAM
*/
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
//從servletContext中獲取ApplicationContext容器,若容器存在,則拋處初始化失敗異常
//需要檢查web.xml中是否定義了多個IOC容器的加載器
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!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
//將容器存儲在本地變量,保證servletContext銷毀之后,還能獲取到它
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
//檢查創建的ApplicationContext實例,是否實現了ConfigurableWebApplicationContext接口
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);
}
}
//String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
//把當前容器設為根容器,并存放在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.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
initWebApplicationContext()流程梳理
1.當調用
ContextLoaderListener
中的initWebApplicationContext()
方法,并且將獲取到的servletContext
作為參數傳入。
2.initWebApplicationContext()
首先會嘗試從servletContext
中獲取根容器,如果容器不為空,則容器初始化失敗,因為web.xml
中可能定義了多個IOC容器
的加載器。
3.若此時容器還未初始化,則調用createWebApplicationContext()
方法創建一個容器。
4.創建完容器之后,將會調用一個非常重要的configureAndRefreshWebApplicationContext()
方法,在執行該方法的時候,會將從ApplicationContext.xml
配置文件中獲取的內容,配置到已經創建好了的XmlWebApplicationContext
容器中去,并調用refresh()
方法來完成容器的初始化。
5.將已經完成初始化的XmlWebApplicationContext
容器注冊到servletContext
中去
其實在Web容器中,ServletContext為Spring的IoC容器提供了宿主環境,對應的建立起一個IoC容器的體系。其中,首先需要建立的是根上下文,這個上下文持有的對象可以有業務對象、數據存取對象、資源、事務管理器等各種中間層對象。在這個上下文的基礎上,與Web MVC相關還會有一個上下文來保持控制器之類的MVC對象,這樣就構成了一個層次化的上下文結構。因為在initWebApplicationContext方法中我們可以看到其實創建ApplicationContext容器的工作是交由createWebApplicationContext方法來實現的,下面我們來看看這個方法
createWebApplicationContext源碼
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//要創建的ApplicationContext的類型
Class<?> contextClass = determineContextClass(sc);
//如果獲取到的ApplicationContext,沒有實現ConfigurableWebApplicationContext接口,那么容器創建失敗
//因此要創建的ApplicationContext,必須實現ConfigurableWebApplicationContext接口
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
//實例化Spring容器
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
功能:
決定要創建的ApplicationContext類型
實例化一個ApplicationContext
那么它是如何決定要創建的ApplicationContext類型的呢?
由determineContextClass()
方法決定的
protected Class<?> determineContextClass(ServletContext servletContext) {
//從web.xml獲取要創建的IOC容器名稱
//public static final String CONTEXT_CLASS_PARAM = "contextClass";
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
//如果獲取到的類名不為空,則創建該容器的class對象
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
//否則創建默認的容器Class對象,即org.springframework.web.context.support.XmlWebApplicationContext
//在創建ContextLoader時,defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
//已經準備好默認的容器類
else {
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);
}
}
}
完成IOC容器
的創建后,在initWebApplicationContext()
中將調用configureAndRefreshWebApplicationContext()
初始化該容器
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();
}
為創建好的
IOC容器
,設置Web應用
的上下文,以便二者整合
為同一個IOC容器
,設置配置文件的絕對路徑
調用IOC容器
的refresh()
函數對其進行初始化